Livelli di zoom e griglia riquadri

Mappe di Azure usare il sistema di coordinate di proiezione Spherical Mercator (EPSG:3857). Una proiezione è il modello matematico usato per trasformare il globo sferico in una mappa piatta. La proiezione Spherical Mercator estende la mappa ai poli per creare una mappa quadrata. Questa proiezione distorce significativamente la scala e l'area della mappa, ma ha due proprietà importanti che superano questa distorsione:

  • Si tratta di una proiezione conforme, il che significa che mantiene la forma di oggetti relativamente piccoli. Conservare la forma di oggetti piccoli è particolarmente importante quando si mostrano immagini aeree. Ad esempio, vogliamo evitare di distorcere la forma degli edifici. Gli edifici quadrati dovrebbero apparire quadrati, non rettangolari.
  • È una proiezione cilindrica. Nord e sud sono sempre su e giù, e ovest e est sono sempre sinistra e destra.

Per ottimizzare le prestazioni del recupero e della visualizzazione della mappa, la mappa è suddivisa in riquadri quadrati. Il Mappe di Azure SDK usa riquadri con dimensioni di 512 x 512 pixel per le mappe stradali e 256 x 256 pixel per le immagini satellitari. Mappe di Azure fornisce riquadri raster e vettoriali per 23 livelli di zoom, numerati da 0 a 22. Al livello di zoom 0 il mondo è incluso in un unico riquadro:

Riquadro mappa globale

Per il rendering del mondo il livello di zoom 1 usa quattro riquadri, ovvero un quadrato 2 x 2

Layout riquadro mappa 2x2

Ogni livello di zoom aggiuntivo divide i riquadri del precedente, creando una griglia di zoom x 2zoom x 2zoom. Il livello di zoom 22 è una griglia di 222 x 222 ovvero 4.194.304 x 4.194.304 riquadri (per un totale di 17.592.186.044.416 riquadri).

Il Mappe di Azure controlli mappa interattivi per Web e Android supporta 25 livelli di zoom, numerati da 0 a 24. Anche se i dati stradali sono disponibili solo a livello di zoom quando i riquadri sono disponibili.

La tabella seguente fornisce l'elenco completo dei valori per i livelli di zoom in cui la dimensione del riquadro è di 256 pixel quadrati:

Livello di zoom Metri/pixel Metri/lato riquadro
0 156543 40075017
1 78271,5 20037508
2 39135,8 10018754
3 19567.88 5009377.1
4 9783.94 2504688.5
5 4891.97 1252344.3
6 2445.98 626172.1
7 1222.99 313086.1
8 611,5 156543
9 305.75 78271,5
10 152.87 39135,8
11 76.44 19567,9
12 38.219 9783.94
13 19.109 4891.97
14 9.555 2445.98
15 4.777 1222.99
16 2.3887 611.496
17 1.1943 305.748
18 0.5972 152.874
19 0.2986 76.437
20 0.14929 38.2185
21 0.074646 19.10926
22 0.037323 9.55463
23 0.0186615 4.777315
24 0.00933075 2.3886575

Coordinate pixel

Dopo aver scelto la proiezione e la scala da usare a ogni livello di zoom, è possibile convertire le coordinate geografiche in coordinate pixel. La larghezza e l'altezza full pixel di un'immagine mappa del mondo per un particolare livello di zoom vengono calcolati come segue:

var mapWidth = tileSize * Math.pow(2, zoom);

var mapHeight = mapWidth;

Poiché la larghezza e l'altezza della mappa sono diverse a ogni livello di zoom, quindi sono le coordinate pixel. Il pixel nell'angolo superiore sinistro della mappa ha sempre coordinate pixel (0, 0). Il pixel nell'angolo inferiore destro della mappa ha coordinate pixel (width-1, height-1) o facendo riferimento alle equazioni nella sezione precedente, (tileSize * 2zoom-1, tileSize * 2zoom-1). Ad esempio, quando si usano 512 riquadri quadrati al livello 2, le coordinate pixel vanno da (0, 0) a (2047, 2047), come segue:

Mappa che mostra le dimensioni dei pixel

Data latitudine e longitudine in gradi e il livello di dettaglio, le coordinate XY pixel vengono calcolate come segue:

var sinLatitude = Math.sin(latitude * Math.PI/180);

var pixelX = ((longitude + 180) / 360) * tileSize * Math.pow(2, zoom);

var pixelY = (0.5 – Math.log((1 + sinLatitude) / (1 – sinLatitude)) / (4 * Math.PI)) * tileSize * Math.pow(2, zoom);

Si presuppone che i valori di latitudine e longitudine siano nel datum WGS 84. Anche se Mappe di Azure usa una proiezione sferica, è importante convertire tutte le coordinate geografiche in un datum comune. WGS 84 è il datum selezionato. Si presuppone che il valore della longitudine sia compreso tra -180 gradi e +180 gradi e il valore di latitudine deve essere ritagliato in intervallo compreso tra -85,05112878 e 85,05112878. L'adesione a questi valori evita una singolarità ai poli e garantisce che la mappa proiettata sia una forma quadrata.

Coordinate dei riquadri

Per ottimizzare le prestazioni del recupero e della visualizzazione della mappa, la mappa sottoposta a rendering viene ritagliata in riquadri. Il numero di pixel e il numero di riquadri differiscono a ogni livello di zoom:

var numberOfTilesWide = Math.pow(2, zoom);

var numberOfTilesHigh = numberOfTilesWide;

A ogni riquadro sono date coordinate XY comprese tra (0, 0) in alto a sinistra (2zoom-1, 2zoom-1) in basso a destra. Ad esempio, al livello di zoom 3, le coordinate del riquadro vanno da (0, 0) a (7, 7) come indicato di seguito:

Mappa delle coordinate dei riquadri

Dato una coppia di coordinate XY pixel, è possibile determinare facilmente le coordinate XY del riquadro contenente il pixel:

var tileX = Math.floor(pixelX / tileSize);

var tileY = Math.floor(pixelY / tileSize);

I riquadri vengono chiamati dal livello di zoom. Le coordinate x e y corrispondono alla posizione del riquadro sulla griglia per il livello di zoom.

Quando si determina il livello di zoom da usare, tenere presente che ogni posizione si trova in una posizione fissa nel riquadro. Di conseguenza, il numero di riquadri necessari per visualizzare una determinata distesa di territorio dipende dalla posizione specifica della griglia di zoom sulla mappa globale. Ad esempio, se sono presenti due punti distanti 900 metri l'uno dall'altro, potrebbe essere necessario usare solo tre riquadri per visualizzare il percorso tra i due punti con un livello di zoom 17. Se tuttavia il punto occidentale si trova sulla destra del relativo riquadro e il punto orientale si trova sulla sinistra del relativo riquadro, potrebbero essere necessari quattro riquadri:

Scala di zoom di esempio

Dopo aver definito il livello di zoom, sarà possibile calcolare i valore x e y. Il riquadro in alto a sinistra in ogni griglia di zoom è x=0, y=0; il riquadro in basso a destra è in corrispondenza di x=2zoom-1, y=2zoom-1.

Ecco la griglia di zoom per il livello di zoom 1:

Griglia di zoom per il livello di zoom 1

Indici Quadkey

Alcune piattaforme di mapping usano una quadkey convenzione di denominazione di indicizzazione che combina le coordinate ZY del riquadro in una stringa di una dimensione denominata quadtree chiavi o quadkeys per brevità. Ogni quadkey elemento identifica in modo univoco un singolo riquadro a un particolare livello di dettaglio e può essere usato come chiave negli indici comuni dell'albero B del database. Gli SDK Mappe di Azure supportano la sovrapposizione dei livelli di riquadri che usano quadkey la convenzione di denominazione oltre ad altre convenzioni di denominazione, come documentato nel documento Aggiungi un livello riquadro.

Nota

La quadkeys convenzione di denominazione funziona solo per i livelli di zoom di uno o più. Il livello di zoom 0 del Mappe di Azure SDK è un singolo riquadro mappa per l'intero mondo.

Per convertire le coordinate dei riquadri in un quadkeyoggetto , i bit delle coordinate Y e X vengono interleaved e il risultato viene interpretato come un numero base-4 (con zeri iniziali mantenuti) e convertito in una stringa. Ad esempio, date le coordinate XY del riquadro di (3, 5) al livello 3, viene quadkey determinato come segue:

tileX = 3 = 011 (base 2)

tileY = 5 = 101 (base 2)

quadkey = 100111 (base 2) = 213 (base 4) = "213"

Qquadkeys hanno diverse proprietà interessanti. Prima di tutto, la lunghezza di un quadkey oggetto (il numero di cifre) è uguale al livello di zoom del riquadro corrispondente. In secondo luogo, l'oggetto quadkey di qualsiasi riquadro inizia con l'oggetto quadkey del relativo riquadro padre (il riquadro contenitore a livello precedente). Come illustrato nell'esempio seguente, il riquadro 2 è l'elemento padre dei riquadri da 20 a 23:

Piramide dei riquadri quadkey

Infine, quadkeys fornire una chiave di indice unidimensionale che in genere mantiene la prossimità dei riquadri nello spazio XY. In altre parole, due riquadri con coordinate XY vicine in genere sono quadkeys relativamente vicine. Questo aspetto è importante per ottimizzare le prestazioni del database, perché i riquadri adiacenti vengono spesso richiesti in gruppi ed è consigliabile mantenere tali riquadri sugli stessi blocchi di disco, per ridurre al minimo il numero di letture su disco.

Codice sorgente matematico dei riquadri

Il codice di esempio seguente illustra come implementare le funzioni descritte in questo documento. Queste funzioni possono essere facilmente tradotte in altri linguaggi di programmazione in base alle esigenze.

using System;
using System.Text;

namespace AzureMaps
{
    /// <summary>
    /// Tile System math for the Spherical Mercator projection coordinate system (EPSG:3857)
    /// </summary>
    public static class TileMath
    {
        //Earth radius in meters.
        private const double EarthRadius = 6378137;

        private const double MinLatitude = -85.05112878;
        private const double MaxLatitude = 85.05112878;
        private const double MinLongitude = -180;
        private const double MaxLongitude = 180;

        /// <summary>
        /// Clips a number to the specified minimum and maximum values.
        /// </summary>
        /// <param name="n">The number to clip.</param>
        /// <param name="minValue">Minimum allowable value.</param>
        /// <param name="maxValue">Maximum allowable value.</param>
        /// <returns>The clipped value.</returns>
        private static double Clip(double n, double minValue, double maxValue)
        {
            return Math.Min(Math.Max(n, minValue), maxValue);
        }

        /// <summary>
        /// Calculates width and height of the map in pixels at a specific zoom level from -180 degrees to 180 degrees.
        /// </summary>
        /// <param name="zoom">Zoom Level to calculate width at</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>Width and height of the map in pixels</returns>
        public static double MapSize(double zoom, int tileSize)
        {
            return Math.Ceiling(tileSize * Math.Pow(2, zoom));
        }

        /// <summary>
        /// Calculates the Ground resolution at a specific degree of latitude in meters per pixel.
        /// </summary>
        /// <param name="latitude">Degree of latitude to calculate resolution at</param>
        /// <param name="zoom">Zoom level to calculate resolution at</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>Ground resolution in meters per pixels</returns>
        public static double GroundResolution(double latitude, double zoom, int tileSize)
        {
            latitude = Clip(latitude, MinLatitude, MaxLatitude);
            return Math.Cos(latitude * Math.PI / 180) * 2 * Math.PI * EarthRadius / MapSize(zoom, tileSize);
        }

        /// <summary>
        /// Determines the map scale at a specified latitude, level of detail, and screen resolution.
        /// </summary>
        /// <param name="latitude">Latitude (in degrees) at which to measure the map scale.</param>
        /// <param name="zoom">Level of detail, from 1 (lowest detail) to 23 (highest detail).</param>
        /// <param name="screenDpi">Resolution of the screen, in dots per inch.</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>The map scale, expressed as the denominator N of the ratio 1 : N.</returns>
        public static double MapScale(double latitude, double zoom, int screenDpi, int tileSize)
        {
            return GroundResolution(latitude, zoom, tileSize) * screenDpi / 0.0254;
        }

        /// <summary>
        /// Global Converts a Pixel coordinate into a geospatial coordinate at a specified zoom level. 
        /// Global Pixel coordinates are relative to the top left corner of the map (90, -180)
        /// </summary>
        /// <param name="pixel">Pixel coordinates in the format of [x, y].</param>  
        /// <param name="zoom">Zoom level</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>A position value in the format [longitude, latitude].</returns>
        public static double[] GlobalPixelToPosition(double[] pixel, double zoom, int tileSize)
        {
            var mapSize = MapSize(zoom, tileSize);

            var x = (Clip(pixel[0], 0, mapSize - 1) / mapSize) - 0.5;
            var y = 0.5 - (Clip(pixel[1], 0, mapSize - 1) / mapSize);

            return new double[] {
                360 * x,    //Longitude
                90 - 360 * Math.Atan(Math.Exp(-y * 2 * Math.PI)) / Math.PI  //Latitude
            };
        }

        /// <summary>
        /// Converts a point from latitude/longitude WGS-84 coordinates (in degrees) into pixel XY coordinates at a specified level of detail.
        /// </summary>
        /// <param name="position">Position coordinate in the format [longitude, latitude]</param>
        /// <param name="zoom">Zoom level.</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param> 
        /// <returns>A global pixel coordinate.</returns>
        public static double[] PositionToGlobalPixel(double[] position, int zoom, int tileSize)
        {
            var latitude = Clip(position[1], MinLatitude, MaxLatitude);
            var longitude = Clip(position[0], MinLongitude, MaxLongitude);

            var x = (longitude + 180) / 360;
            var sinLatitude = Math.Sin(latitude * Math.PI / 180);
            var y = 0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);

            var mapSize = MapSize(zoom, tileSize);

            return new double[] {
                 Clip(x * mapSize + 0.5, 0, mapSize - 1),
                 Clip(y * mapSize + 0.5, 0, mapSize - 1)
            };
        }

        /// <summary>
        /// Converts pixel XY coordinates into tile XY coordinates of the tile containing the specified pixel.
        /// </summary>
        /// <param name="pixel">Pixel coordinates in the format of [x, y].</param>  
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <param name="tileX">Output parameter receiving the tile X coordinate.</param>
        /// <param name="tileY">Output parameter receiving the tile Y coordinate.</param>
        public static void GlobalPixelToTileXY(double[] pixel, int tileSize, out int tileX, out int tileY)
        {
            tileX = (int)(pixel[0] / tileSize);
            tileY = (int)(pixel[1] / tileSize);
        }

        /// <summary>
        /// Performs a scale transform on a global pixel value from one zoom level to another.
        /// </summary>
        /// <param name="pixel">Pixel coordinates in the format of [x, y].</param>  
        /// <param name="oldZoom">The zoom level in which the input global pixel value is from.</param>  
        /// <returns>A scale pixel coordinate.</returns>
        public static double[] ScaleGlobalPixel(double[] pixel, double oldZoom, double newZoom)
        {
            var scale = Math.Pow(2, oldZoom - newZoom);

            return new double[] { pixel[0] * scale, pixel[1] * scale };
        }

        /// <summary>
        /// Performs a scale transform on a set of global pixel values from one zoom level to another.
        /// </summary>
        /// <param name="pixels">A set of global pixel value from the old zoom level. Points are in the format [x,y].</param>
        /// <param name="oldZoom">The zoom level in which the input global pixel values is from.</param>
        /// <param name="newZoom">The new zoom level in which the output global pixel values should be aligned with.</param>
        /// <returns>A set of global pixel values that has been scaled for the new zoom level.</returns>
        public static double[][] ScaleGlobalPixels(double[][] pixels, double oldZoom, double newZoom)
        {
            var scale = Math.Pow(2, oldZoom - newZoom);

            var output = new System.Collections.Generic.List<double[]>();
            foreach (var p in pixels)
            {
                output.Add(new double[] { p[0] * scale, p[1] * scale });
            }

            return output.ToArray();
        }

        /// <summary>
        /// Converts tile XY coordinates into a global pixel XY coordinates of the upper-left pixel of the specified tile.
        /// </summary>
        /// <param name="tileX">Tile X coordinate.</param>
        /// <param name="tileY">Tile Y coordinate.</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <param name="pixelX">Output parameter receiving the X coordinate of the point, in pixels.</param>  
        /// <param name="pixelY">Output parameter receiving the Y coordinate of the point, in pixels.</param>  
        public static double[] TileXYToGlobalPixel(int tileX, int tileY, int tileSize)
        {
            return new double[] { tileX * tileSize, tileY * tileSize };
        }

        /// <summary>
        /// Converts tile XY coordinates into a quadkey at a specified level of detail.
        /// </summary>
        /// <param name="tileX">Tile X coordinate.</param>
        /// <param name="tileY">Tile Y coordinate.</param>
        /// <param name="zoom">Zoom level</param>
        /// <returns>A string containing the quadkey.</returns>
        public static string TileXYToQuadKey(int tileX, int tileY, int zoom)
        {
            var quadKey = new StringBuilder();
            for (int i = zoom; i > 0; i--)
            {
                char digit = '0';
                int mask = 1 << (i - 1);
                if ((tileX & mask) != 0)
                {
                    digit++;
                }
                if ((tileY & mask) != 0)
                {
                    digit++;
                    digit++;
                }
                quadKey.Append(digit);
            }
            return quadKey.ToString();
        }

        /// <summary>
        /// Converts a quadkey into tile XY coordinates.
        /// </summary>
        /// <param name="quadKey">Quadkey of the tile.</param>
        /// <param name="tileX">Output parameter receiving the tile X coordinate.</param>
        /// <param name="tileY">Output parameter receiving the tile Y coordinate.</param>
        /// <param name="zoom">Output parameter receiving the zoom level.</param>
        public static void QuadKeyToTileXY(string quadKey, out int tileX, out int tileY, out int zoom)
        {
            tileX = tileY = 0;
            zoom = quadKey.Length;
            for (int i = zoom; i > 0; i--)
            {
                int mask = 1 << (i - 1);
                switch (quadKey[zoom - i])
                {
                    case '0':
                        break;

                    case '1':
                        tileX |= mask;
                        break;

                    case '2':
                        tileY |= mask;
                        break;

                    case '3':
                        tileX |= mask;
                        tileY |= mask;
                        break;

                    default:
                        throw new ArgumentException("Invalid QuadKey digit sequence.");
                }
            }
        }

        /// <summary>
        /// Calculates the XY tile coordinates that a coordinate falls into for a specific zoom level.
        /// </summary>
        /// <param name="position">Position coordinate in the format [longitude, latitude]</param>
        /// <param name="zoom">Zoom level</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <param name="tileX">Output parameter receiving the tile X position.</param>
        /// <param name="tileY">Output parameter receiving the tile Y position.</param>
        public static void PositionToTileXY(double[] position, int zoom, int tileSize, out int tileX, out int tileY)
        {
            var latitude = Clip(position[1], MinLatitude, MaxLatitude);
            var longitude = Clip(position[0], MinLongitude, MaxLongitude);

            var x = (longitude + 180) / 360;
            var sinLatitude = Math.Sin(latitude * Math.PI / 180);
            var y = 0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI);

            //tileSize needed in calculations as in rare cases the multiplying/rounding/dividing can make the difference of a pixel which can result in a completely different tile. 
            var mapSize = MapSize(zoom, tileSize);
            tileX = (int)Math.Floor(Clip(x * mapSize + 0.5, 0, mapSize - 1) / tileSize);
            tileY = (int)Math.Floor(Clip(y * mapSize + 0.5, 0, mapSize - 1) / tileSize);
        }

        /// <summary>
        /// Calculates the tile quadkey strings that are within a specified viewport.
        /// </summary>
        /// <param name="position">Position coordinate in the format [longitude, latitude]</param>
        /// <param name="zoom">Zoom level</param>
        /// <param name="width">The width of the map viewport in pixels.</param>
        /// <param name="height">The height of the map viewport in pixels.</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>A list of quadkey strings that are within the specified viewport.</returns>
        public static string[] GetQuadkeysInView(double[] position, int zoom, int width, int height, int tileSize)
        {
            var p = PositionToGlobalPixel(position, zoom, tileSize);

            var top = p[1] - height * 0.5;
            var left = p[0] - width * 0.5;

            var bottom = p[1] + height * 0.5;
            var right = p[0] + width * 0.5;

            var tl = GlobalPixelToPosition(new double[] { left, top }, zoom, tileSize);
            var br = GlobalPixelToPosition(new double[] { right, bottom }, zoom, tileSize);

            //Boudning box in the format: [west, south, east, north];
            var bounds = new double[] { tl[0], br[1], br[0], tl[1] };

            return GetQuadkeysInBoundingBox(bounds, zoom, tileSize);
        }

        /// <summary>
        /// Calculates the tile quadkey strings that are within a bounding box at a specific zoom level.
        /// </summary>
        /// <param name="bounds">A bounding box defined as an array of numbers in the format of [west, south, east, north].</param>
        /// <param name="zoom">Zoom level to calculate tiles for.</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>A list of quadkey strings.</returns>
        public static string[] GetQuadkeysInBoundingBox(double[] bounds, int zoom, int tileSize)
        {
            var keys = new System.Collections.Generic.List<string>();

            if (bounds != null && bounds.Length >= 4)
            {
                PositionToTileXY(new double[] { bounds[3], bounds[0] }, zoom, tileSize, out int tlX, out int tlY);
                PositionToTileXY(new double[] { bounds[1], bounds[2] }, zoom, tileSize, out int brX, out int brY);

                for (int x = tlX; x <= brX; x++)
                {
                    for (int y = tlY; y <= brY; y++)
                    {
                        keys.Add(TileXYToQuadKey(x, y, zoom));
                    }
                }
            }

            return keys.ToArray();
        }

        /// <summary>
        /// Calculates the bounding box of a tile.
        /// </summary>
        /// <param name="tileX">Tile X coordinate</param>
        /// <param name="tileY">Tile Y coordinate</param>
        /// <param name="zoom">Zoom level</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <returns>A bounding box of the tile defined as an array of numbers in the format of [west, south, east, north].</returns>
        public static double[] TileXYToBoundingBox(int tileX, int tileY, double zoom, int tileSize)
        {
            //Top left corner pixel coordinates
            var x1 = (double)(tileX * tileSize);
            var y1 = (double)(tileY * tileSize);

            //Bottom right corner pixel coordinates
            var x2 = (double)(x1 + tileSize);
            var y2 = (double)(y1 + tileSize);

            var nw = GlobalPixelToPosition(new double[] { x1, y1 }, zoom, tileSize);
            var se = GlobalPixelToPosition(new double[] { x2, y2 }, zoom, tileSize);

            return new double[] { nw[0], se[1], se[0], nw[1] };
        }

        /// <summary>
        /// Calculates the best map view (center, zoom) for a bounding box on a map.
        /// </summary>
        /// <param name="bounds">A bounding box defined as an array of numbers in the format of [west, south, east, north].</param>
        /// <param name="mapWidth">Map width in pixels.</param>
        /// <param name="mapHeight">Map height in pixels.</param>
        /// <param name="padding">Width in pixels to use to create a buffer around the map. This is to keep markers from being cut off on the edge</param>
        /// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
        /// <param name="latitude">Output parameter receiving the center latitude coordinate.</param>
        /// <param name="longitude">Output parameter receiving the center longitude coordinate.</param>
        /// <param name="zoom">Output parameter receiving the zoom level</param>
        public static void BestMapView(double[] bounds, double mapWidth, double mapHeight, int padding, int tileSize, out double centerLat, out double centerLon, out double zoom)
        {
            if (bounds == null || bounds.Length < 4)
            {
                centerLat = 0;
                centerLon = 0;
                zoom = 1;
                return;
            }

            double boundsDeltaX;

            //Check if east value is greater than west value which would indicate that bounding box crosses the antimeridian.
            if (bounds[2] > bounds[0])
            {
                boundsDeltaX = bounds[2] - bounds[0];
                centerLon = (bounds[2] + bounds[0]) / 2;
            }
            else
            {
                boundsDeltaX = 360 - (bounds[0] - bounds[2]);
                centerLon = ((bounds[2] + bounds[0]) / 2 + 360) % 360 - 180;
            }

            var ry1 = Math.Log((Math.Sin(bounds[1] * Math.PI / 180) + 1) / Math.Cos(bounds[1] * Math.PI / 180));
            var ry2 = Math.Log((Math.Sin(bounds[3] * Math.PI / 180) + 1) / Math.Cos(bounds[3] * Math.PI / 180));
            var ryc = (ry1 + ry2) / 2;

            centerLat = Math.Atan(Math.Sinh(ryc)) * 180 / Math.PI;

            var resolutionHorizontal = boundsDeltaX / (mapWidth - padding * 2);

            var vy0 = Math.Log(Math.Tan(Math.PI * (0.25 + centerLat / 360)));
            var vy1 = Math.Log(Math.Tan(Math.PI * (0.25 + bounds[3] / 360)));
            var zoomFactorPowered = (mapHeight * 0.5 - padding) / (40.7436654315252 * (vy1 - vy0));
            var resolutionVertical = 360.0 / (zoomFactorPowered * tileSize);

            var resolution = Math.Max(resolutionHorizontal, resolutionVertical);

            zoom = Math.Log(360 / (resolution * tileSize), 2);
        }
    }
}

Nota

I controlli mappa interattivi nell'SDK di Mappe di Azure dispongono di funzioni helper per la conversione tra posizioni geospaziali e pixel del riquadro di visualizzazione.

Passaggi successivi

Accedere direttamente ai riquadri della mappa dai servizi REST Mappe di Azure:

Altre informazioni sui concetti geospaziali: