Как рассчитать оптимальный уровень масштабирования для отображения двух или более точек на карте

12

Мы хотим отобразить несколько маркеров на статической карте и рассчитать оптимальный уровень масштабирования, как это делают Карты Google. Мы уже вычислили ограничивающий прямоугольник и центральную точку карты, но теперь нам сложно вычислить правильный уровень масштабирования для отображения всего ограничивающего прямоугольника. Может кто-нибудь, пожалуйста, укажите нам правильное направление?

Тобиас Шварц
источник
Это не очень сложно, но почему бы просто не умножить MBR на 120% и увеличить его? Что-то еще зависит от вашего определения «оптимальный», не так ли?
Том
+1 к идее Тома. Это именно то, что делает ArcGIS.
Раги Язер Бурхум
1
Извините, но что это за MBR?
Родриго

Ответы:

5

Чтобы получить уровень масштабирования, вам необходимо знать размеры вашей карты в пикселях. Вы также должны будете сделать свою математику в сферических координатах Меркатора.

  1. Перевести широту, долготу в сферический меркатор x, y.
  2. Получите расстояние между вашими двумя точками в сферическом меркаторе.
  3. Проекция экватора составляет около 40 метров, а плитки - 256 пикселей, поэтому длина пикселя этой карты при заданном уровне масштабирования составляет примерно 256 * расстояние / 40000000 * 2 ^ увеличение . Попробуйте zoom = 0, zoom = 1, zoom = 2 до тех пор, пока расстояние не станет слишком длинным для измерений в пикселях вашей карты.
Михал Мигурски
источник
1
Надеюсь, я отдаленно понял, о чем ты на самом деле спрашиваешь. = \
Михал Мигурски
Нашел этот вопрос, ища этот ответ, спасибо.
BenjaminGolder
@MichalMigurski Вы можете объяснить, как рассчитать расстояние между 2 точками в сферическом меркаторе? особенно координаты х ... я застрял, спасибо.
otmezger
5

Это код C #, который я использую в Maperitive :

    public void ZoomToArea (Bounds2 mapArea, float paddingFactor)
    {
        double ry1 = Math.Log((Math.Sin(GeometryUtils.Deg2Rad(mapArea.MinY)) + 1) 
            / Math.Cos(GeometryUtils.Deg2Rad(mapArea.MinY)));
        double ry2 = Math.Log((Math.Sin(GeometryUtils.Deg2Rad(mapArea.MaxY)) + 1) 
            / Math.Cos(GeometryUtils.Deg2Rad(mapArea.MaxY)));
        double ryc = (ry1 + ry2) / 2;
        double centerY = GeometryUtils.Rad2Deg(Math.Atan(Math.Sinh(ryc)));

        double resolutionHorizontal = mapArea.DeltaX / Viewport.Width;

        double vy0 = Math.Log(Math.Tan(Math.PI*(0.25 + centerY/360)));
        double vy1 = Math.Log(Math.Tan(Math.PI*(0.25 + mapArea.MaxY/360)));
        double viewHeightHalf = Viewport.Height/2.0f;
        double zoomFactorPowered = viewHeightHalf
            / (40.7436654315252*(vy1 - vy0));
        double resolutionVertical = 360.0 / (zoomFactorPowered * 256);

        double resolution = Math.Max(resolutionHorizontal, resolutionVertical) 
            * paddingFactor;
        double zoom = Math.Log(360 / (resolution * 256), 2);
        double lon = mapArea.Center.X;
        double lat = centerY;

        CenterMapOnPoint(new PointD2(lon, lat), zoom);
    }
  • mapArea: ограничивающий прямоугольник в координатах long / lat (x = long, y = lat)
  • paddingFactor: это может быть использовано для получения эффекта "120%", на который ссылается ThomM. Значение 1,2 даст вам 120%.

Обратите внимание, что в моем случае zoomможет быть действительным числом. В случае веб-карт вам нужно целочисленное значение масштабирования, поэтому вы должны использовать что-то вроде (int)Math.Floor(zoom)этого.

Конечно, этот код относится только к проекции Web Mercator.

Игорь Брейц
источник
1
Спасибо за это. Код для CenterMapOnPoint доступен?
mcintyre321
Отлично! Я использовал это, чтобы вычислить масштабирование BoundingBox в OsmDroid SDK, и это работает :)
Billda
Где я могу найти эти библиотеки? Я не могу найти сборки GeometryUtils или Bounds2. Нужно ли что-то скачивать с maperitive? Я вижу только приложение там.
Доулерс
@Dowlers GeometryUtilsпросто используется здесь для преобразования градусов в радианы и обратно, это простая математическая формула. Bounds2в основном просто прямоугольная структура. Этот код был предоставлен скорее как псевдокод, а не как что-то непосредственно копируемое.
Игорь Брейц
1
Это потрясающе @ IgorBrejc - спасибо большое! Я конвертировал его в python здесь на тот случай, если другие пишут код на python: gist.github.com/mappingvermont/d534539fa3ebe4a1e242644e528bf7b9
Чарли Хофманн
1

Если вы используете OpenLayers, то Map.getZoomForExtentрассчитаете максимальный уровень масштабирования, который может соответствовать всему экстенту на карте. В extentпотребности быть в проекции карты. Вы также можете использовать, fill_factorчтобы избежать отображения точек на краю карты и max_zoomограничить возможное увеличение:

extent = extent.scale(1/fill_ratio);
var zoom = Math.min(map.getZoomForExtent(map_extent), max_zoom);
map.setCenter(extent.getCenterLonLat(), zoom);
Алекс Морега
источник
это не решило мою проблему, но привело меня к поиску метода fitBounds от Leaflet, который спас мой день.
SamuelDev