Comment calculer le niveau de zoom optimal pour afficher deux points ou plus sur une carte

12

Nous voulons afficher plusieurs marqueurs sur une carte statique et calculer le niveau de zoom optimal comme le fait Google Maps. Nous avons déjà calculé le rectangle de délimitation et le point central de la carte, mais nous avons maintenant du mal à calculer le niveau de zoom correct pour afficher tout le rectangle de délimitation. Quelqu'un peut-il nous indiquer la bonne direction?

Tobias Schwarz
la source
Ce n'est pas très sophistiqué, mais pourquoi ne pas simplement multiplier le MBR par 120% et zoomer sur cela? Tout le reste dépend de votre définition de "optimal", n'est-ce pas?
ThomM
+1 à l'idée de ThomM. C'est exactement ce que fait ArcGIS.
Ragi Yaser Burhum
1
Désolé, mais quel est ce MBR?
Rodrigo

Réponses:

5

Pour obtenir le niveau de zoom, vous devez connaître les dimensions en pixels de votre carte. Vous devrez également faire vos calculs en coordonnées sphériques de mercator.

  1. Convertissez la latitude, la longitude en mercator sphérique x, y.
  2. Obtenez la distance entre vos deux points dans mercator sphérique.
  3. L'équateur mesure environ 40 mètres de long et les tuiles ont une largeur de 256 pixels, de sorte que la longueur en pixels de cette carte à un niveau de zoom donné est d'environ 256 * distance / 40000000 * 2 ^ zoom . Essayez zoom = 0, zoom = 1, zoom = 2 jusqu'à ce que la distance soit trop longue pour les dimensions en pixels de votre carte.
Michal Migurski
la source
1
J'espère avoir compris à distance ce que vous demandez réellement. = \
Michal Migurski
J'ai trouvé cette question en cherchant cette réponse, merci.
BenjaminGolder
@MichalMigurski pouvez-vous expliquer comment calculer la distance entre 2 points en mercator sphérique? spécialement les coordonnées x ... je suis coincé merci.
otmezger
5

Voici le code C # que j'utilise dans 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: cadre de délimitation en coordonnées long / lat (x = long, y = lat)
  • paddingFactor: ceci peut être utilisé pour obtenir l'effet "120%" auquel ThomM fait référence. Une valeur de 1,2 vous permettrait d'obtenir les 120%.

Notez que dans mon cas, il zoompeut s'agir d'un nombre réel. Dans le cas des cartes Web, vous avez besoin d'une valeur de zoom entière, vous devez donc utiliser quelque chose comme (int)Math.Floor(zoom)pour l'obtenir.

Bien sûr, ce code ne s'applique qu'à la projection Web Mercator.

Igor Brejc
la source
1
Merci pour cela. Le code de CenterMapOnPoint est-il disponible?
mcintyre321
Parfait! Je l'ai utilisé pour calculer le zoom de BoundingBox dans le SDK OsmDroid et cela fonctionne :)
Billda
Où puis-je trouver ces bibliothèques? Je ne trouve pas d'assemblys GeometryUtils ou Bounds2. Dois-je télécharger quelque chose depuis maperitive? Je ne vois qu'une application là-bas.
Dowlers
@Dowlers GeometryUtilsest juste utilisé ici pour convertir des degrés en radians et inversement , c'est une formule mathématique simple. Bounds2est fondamentalement juste une structure rectangulaire. Ce code a été fourni plus comme un pseudocode que comme quelque chose directement copiable-collable.
Igor Brejc
1
C'est génial @ IgorBrejc - merci beaucoup! Je l'ai converti en python ici au cas où d'autres écrivent du code python: gist.github.com/mappingvermont/d534539fa3ebe4a1e242644e528bf7b9
Charlie Hofmann
1

Si vous utilisez OpenLayers, alors Map.getZoomForExtentcalculera le niveau de zoom le plus élevé qui peut s'adapter à toute l'étendue sur la carte. Le extentdoit être dans la projection de la carte. Vous pouvez également utiliser un fill_factorpour éviter d'afficher des points sur le bord de la carte et max_zoompour limiter le zoom possible:

extent = extent.scale(1/fill_ratio);
var zoom = Math.min(map.getZoomForExtent(map_extent), max_zoom);
map.setCenter(extent.getCenterLonLat(), zoom);
Alex Morega
la source
cela n'a pas résolu mon problème, mais cela m'a conduit à trouver la méthode fitBounds de Leaflet, qui m'a sauvé la journée.
SamuelDev