Zoomniveaus en tegelraster

Azure Kaarten het Spherical Mercator-projectiecoördinaatsysteem (EPSG:3857) gebruiken. Een projectie is het wiskundige model dat wordt gebruikt om de bolvormige wereldbol te transformeren in een platte kaart. De Spherical Mercator-projectie strekt de kaart aan de polen uit om een vierkante kaart te maken. Deze projectie vervormt de schaal en het gebied van de kaart aanzienlijk, maar heeft twee belangrijke eigenschappen die opwegen tegen deze vervorming:

  • Het is een conforme projectie, wat betekent dat de vorm van relatief kleine objecten behouden blijft. Het behouden van de vorm van kleine objecten is vooral belangrijk bij het weergeven van luchtfoto's. We willen bijvoorbeeld voorkomen dat de vorm van gebouwen wordt vervormd. Vierkante gebouwen moeten vierkant worden weergegeven, niet rechthoekig.
  • Het is een cilindrische projectie. Noord en zuid zijn altijd omhoog en omlaag, en west en oost zijn altijd links en rechts.

De kaart is onderverdeeld in vierkante tegels om de prestaties van het ophalen en weergeven van kaarten te optimaliseren. De Azure Kaarten SDK gebruikt tegels met een grootte van 512 x 512 pixels voor wegenkaarten en kleiner dan 256 x 256 pixels voor satellietbeelden. Azure Kaarten biedt raster- en vectortegels voor 23 zoomniveaus, genummerd 0 tot en met 22. Op zoomniveau 0 past de hele wereld op één tegel:

Wereldkaarttegel

Zoomniveau 1 maakt gebruik van vier tegels om de wereld weer te geven: een 2 x 2 vierkant

2x2 kaarttegelindeling

Elke extra zoomniveau quad-verdeelt de tegels van de vorige, waardoor een raster van 2zoom x 2zoomen wordt gemaakt. Zoomniveau 22 is een raster 2 22 x 22, of 4.194.304 x 4.194.304 tegels (17.592.186.044.416 tegels in totaal).

De Azure Kaarten interactieve kaartbesturingselementen voor web- en Android-ondersteuning voor 25 zoomniveaus, genummerd 0 tot en met 24. Hoewel weggegevens alleen beschikbaar zijn op de zoomniveaus wanneer de tegels beschikbaar zijn.

De volgende tabel bevat de volledige lijst met waarden voor zoomniveaus waarbij de tegelgrootte 256 pixels vierkant is:

Zoomniveau Meters/pixel Meters/tegelzijde
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

Pixelcoördinaten

Nadat we de projectie en schaal hebben gekozen die op elk zoomniveau moeten worden gebruikt, kunnen we geografische coördinaten converteren naar pixelcoördinaten. De volledige pixelbreedte en hoogte van een kaartafbeelding van de wereld voor een bepaald zoomniveau wordt berekend als:

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

var mapHeight = mapWidth;

Omdat de breedte en hoogte van de kaart op elk zoomniveau verschillen, zijn dit de pixelcoördinaten. De pixel in de linkerbovenhoek van de kaart heeft altijd pixelcoördinaten (0, 0). De pixel in de rechterbenedenhoek van de kaart heeft pixelcoördinaten (breedte-1, hoogte-1) of verwijst naar de vergelijkingen in de vorige sectie (tileSize * 2zoom-1, tileSize * 2zoom-1). Wanneer u bijvoorbeeld 512 vierkante tegels op niveau 2 gebruikt, variëren de pixelcoördinaten van (0, 0) tot (2047, 2047), zoals:

Kaart met pixeldimensies

De pixel XY-coördinaten worden als volgt berekend op basis van de breedtegraad en lengtegraad in graden en het detailniveau:

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);

De waarden voor breedtegraad en lengtegraad worden verondersteld op de WGS 84-datum te staan. Hoewel Azure Kaarten gebruikmaakt van een bolvormige projectie, is het belangrijk om alle geografische coördinaten te converteren naar een gemeenschappelijke datum. WGS 84 is de geselecteerde datum. De waarde van de lengtegraad wordt ervan uitgegaan dat deze varieert van -180 graden tot +180 graden en dat de breedtegraad moet worden geknipt tot een bereik van -85,05112878 tot 85,05112878. Als u zich aan deze waarden houdt, voorkomt u een singulariteit op de polen en zorgt u ervoor dat de geprojecteerde kaart een kwadratische vorm is.

Tegelcoördinaten

Om de prestaties van het ophalen en weergeven van kaarten te optimaliseren, wordt de gerenderde kaart in tegels gesneden. Het aantal pixels en het aantal tegels verschillen op elk zoomniveau:

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

var numberOfTilesHigh = numberOfTilesWide;

Elke tegel krijgt XY-coördinaten van (0, 0) in de linkerbovenhoek tot (2zoom-1, 2zoom-1) in de rechterbenedenhoek. Op zoomniveau 3 variëren de tegelcoördinaten bijvoorbeeld van (0, 0) tot (7, 7) als volgt:

Kaart van tegelcoördinaten

Met een paar pixel XY-coördinaten kunt u eenvoudig de tegel XY-coördinaten van de tegel met die pixel bepalen:

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

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

Tegels worden aangeroepen op zoomniveau. De x- en y-coördinaten komen overeen met de positie van de tegel in het raster voor dat zoomniveau.

Wanneer u bepaalt welk zoomniveau moet worden gebruikt, onthoudt u dat elke locatie zich op een vaste positie op de tegel bevindt. Als gevolg hiervan is het aantal tegels dat nodig is om een bepaalde oppervlakte van het gebied weer te geven, afhankelijk van de specifieke plaatsing van het zoomraster op de wereldkaart. Als er bijvoorbeeld twee punten 900 meter uit elkaar liggen, kan het slechts drie tegels duren om een route tussen deze tegels weer te geven op zoomniveau 17. Als het westelijke punt zich echter rechts van de tegel bevindt en het oostelijke punt aan de linkerkant van de tegel, kan het vier tegels duren:

Zoomdemoschaal

Zodra het zoomniveau is bepaald, kunnen de x- en y-waarden worden berekend. De tegel linksboven in elk zoomraster is x=0, y=0; de tegel rechtsonder bevindt zich op x=2zoom-1, y=2zoom-1.

Dit is het zoomraster voor zoomniveau 1:

Zoomraster voor zoomniveau 1

Quadkey-indexen

Sommige toewijzingsplatformen gebruiken een quadkey indexeringsnaamconventie waarmee de tegel-ZY-coördinaten worden gecombineerd tot een tekenreeks met één dimensie die sleutels of quadkeys kortom wordt genoemdquadtree. Elk quadkey uniek identificeert één tegel op een bepaald detailniveau en kan worden gebruikt als een sleutel in algemene database B-tree-indexen. De Azure Kaarten SDK's ondersteunen het overlayen van tegellagen die quadkey gebruikmaken van naamconventie naast andere naamconventies, zoals beschreven in het document Een tegellaag toevoegen.

Notitie

De quadkeys naamconventie werkt alleen voor zoomniveaus van een of meer. De azure Kaarten SDK biedt ondersteuning voor zoomniveau 0. Dit is één kaarttegel voor de hele wereld.

Als u tegelcoördinaten wilt converteren naar een quadkey, worden de bits van de Y- en X-coördinaten met elkaar verbonden en wordt het resultaat geïnterpreteerd als een getal met grondtal 4 (waarbij voorloopnullen behouden blijven) en geconverteerd naar een tekenreeks. De opgegeven tegel-XY-coördinaten van (3, 5) op niveau 3 worden quadkey bijvoorbeeld als volgt bepaald:

tileX = 3 = 011 (base 2)

tileY = 5 = 101 (base 2)

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

Qquadkeys hebben verschillende interessante eigenschappen. Ten eerste is de lengte van een quadkey (het aantal cijfers) gelijk aan het zoomniveau van de bijbehorende tegel. Ten tweede begint de quadkey tegel met de quadkey bovenliggende tegel (de bijbehorende tegel op het vorige niveau). Zoals in het volgende voorbeeld wordt weergegeven, is tegel 2 het bovenliggende element van tegels 20 tot en met 23:

Quadkey tegel piramide

quadkeys Geef ten slotte een eendimensionale indexsleutel op die meestal de nabijheid van tegels in de XY-ruimte behoudt. Met andere woorden, twee tegels met nabijgelegen XY-coördinaten hebben quadkeys meestal relatief dicht bij elkaar. Dit is belangrijk voor het optimaliseren van de databaseprestaties, omdat aangrenzende tegels vaak in groepen worden aangevraagd en het wenselijk is om deze tegels op dezelfde schijfblokken te houden, om het aantal leesbewerkingen van schijven te minimaliseren.

Broncode voor wiskundige tegels

In de volgende voorbeeldcode ziet u hoe u de functies implementeert die in dit document worden beschreven. Deze functies kunnen naar behoefte eenvoudig worden vertaald in andere programmeertalen.

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);
        }
    }
}

Notitie

De interactieve kaartbesturingselementen in de Azure Kaarten SDK's hebben helperfuncties voor het converteren tussen georuimtelijke posities en viewport pixels.

Volgende stappen

Rechtstreeks toegang tot kaarttegels vanuit de Azure Kaarten REST-services:

Meer informatie over georuimtelijke concepten: