Poziomy powiększenia i siatka kafelków

Usługa Azure Mapy używać układu współrzędnych projekcji Spherical Mercator (EPSG:3857). Projekcja to model matematyczny używany do przekształcania kulistego globusa w płaską mapę. Projekcja Spherical Mercator rozciąga mapę na biegunach, aby utworzyć mapę kwadratową. Ta projekcja znacząco zniekształca skalę i obszar mapy, ale ma dwie ważne właściwości, które przewyższają to zniekształcenie:

  • Jest to zgodne projekcje, co oznacza, że zachowuje kształt stosunkowo małych obiektów. Zachowanie kształtu małych obiektów jest szczególnie ważne podczas wyświetlania zdjęć lotniczych. Chcemy na przykład uniknąć zniekształcania kształtu budynków. Budynki kwadratowe powinny wydawać się kwadratowe, a nie prostokątne.
  • Jest to projekcja walcowa. Północ i południe są zawsze w górę i w dół, a zachód i wschód są zawsze w lewo i w prawo.

Aby zoptymalizować wydajność pobierania i wyświetlania mapy, mapa jest podzielona na kafelki kwadratowe. Zestaw SDK platformy Azure Mapy używa kafelków o rozmiarze 512 x 512 pikseli na potrzeby map dróg i mniejszych 256 x 256 pikseli dla obrazów satelitarnych. Usługa Azure Mapy udostępnia kafelki rastrowe i wektorowe dla 23 poziomów powiększenia, ponumerowanych od 0 do 22. Na poziomie powiększenia 0 cały świat mieści się na jednym kafelku:

Kafelek mapy świata

Poziom powiększenia 1 używa czterech kafelków do renderowania świata: 2 x 2 kwadraty

Układ kafelka mapy 2x2

Każdy dodatkowy poziom powiększenia dzieli kafelki poprzedniego, tworząc siatkę 2powiększenia x 2powiększenie. Poziom powiększenia 22 to siatka 22 x 22, czyli 4194 304 x 4 194 304 kafelki (łącznie 17 592 186 044 416 kafelków).

Kontrolki interaktywnej mapy platformy Azure Mapy dla sieci Web i systemu Android obsługują 25 poziomów powiększenia, ponumerowanych od 0 do 24. Chociaż dane drogowe są dostępne tylko na poziomach powiększenia, gdy kafelki są dostępne.

Poniższa tabela zawiera pełną listę wartości poziomów powiększenia, w których rozmiar kafelka wynosi 256 pikseli kwadratowych:

Poziom powiększenia Mierniki/piksel Mierniki/strona kafelka
0 156543 40075017
1 78271.5 20037508
2 39135.8 10018754
3 19567.88 5009377.1
100 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

Współrzędne pikseli

Po wybraniu projekcji i skali do użycia na każdym poziomie powiększenia możemy przekonwertować współrzędne geograficzne na współrzędne pikseli. Pełną szerokość i wysokość obrazu mapy świata dla określonego poziomu powiększenia są obliczane jako:

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

var mapHeight = mapWidth;

Ponieważ szerokość i wysokość mapy różnią się na każdym poziomie powiększenia, więc są współrzędnymi pikseli. Piksel w lewym górnym rogu mapy zawsze ma współrzędne pikseli (0, 0). Piksel w prawym dolnym rogu mapy ma współrzędne pikseli (width-1, height-1) lub odwołujące się do równań w poprzedniej sekcji (tileSize * 2zoom–1, tileSize * 2 zoom–1, tileSize * 2zoom–1). Na przykład w przypadku używania 512 kafelków kwadratowych na poziomie 2 współrzędne pikseli wahają się od (0, 0) do (2047, 2047), w następujący sposób:

Mapa przedstawiająca wymiary pikseli

Biorąc pod uwagę szerokość geograficzną i długość geograficzną w stopniach oraz poziom szczegółowości, współrzędne XY pikseli są obliczane w następujący sposób:

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

Przyjmuje się, że wartości szerokości i długości geograficznej znajdują się w datum WGS 84. Mimo że usługa Azure Mapy używa projekcji sfericznej, ważne jest, aby przekonwertować wszystkie współrzędne geograficzne na wspólną elementy dat. WGS 84 jest wybraną datą. Przyjmuje się, że wartość długości geograficznej waha się od -180 stopni do +180 stopni, a wartość szerokości geograficznej musi zostać obcięta do zakresu od -85,05112878 do 85,0512878. Przestrzeganie tych wartości pozwala uniknąć pojedynczej liczby biegunów i gwarantuje, że rzutowana mapa jest kształtem kwadratowym.

Współrzędne kafelka

Aby zoptymalizować wydajność pobierania i wyświetlania mapy, renderowana mapa jest wycinana na kafelki. Liczba pikseli i liczba kafelków różnią się na każdym poziomie powiększenia:

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

var numberOfTilesHigh = numberOfTilesWide;

Każdy kafelek otrzymuje współrzędne XY od (0, 0) w lewym górnym rogu do (2powiększenie–1, 2powiększenie–1) w prawym dolnym rogu. Na przykład na poziomie powiększenia 3 współrzędne kafelka wahają się od (0, 0) do (7, 7, 7) w następujący sposób:

Mapa współrzędnych kafelka

Biorąc pod uwagę parę współrzędnych XY pikseli, można łatwo określić współrzędne XY kafelka zawierającego ten piksel:

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

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

Kafelki są wywoływane przez poziom powiększenia. Współrzędne x i y odpowiadają pozycji kafelka na siatce dla tego poziomu powiększenia.

Podczas określania, który poziom powiększenia ma być używany, należy pamiętać, że każda lokalizacja znajduje się w stałej pozycji na kafelku. W rezultacie liczba kafelków potrzebnych do wyświetlenia danego obszaru zależy od konkretnego położenia siatki powiększenia na mapie świata. Na przykład, jeśli istnieją dwa punkty 900 metrów od siebie, może to potrwać tylko trzy kafelki, aby wyświetlić trasę między nimi na poziomie powiększenia 17. Jeśli jednak punkt zachodni znajduje się po prawej stronie kafelka, a wschodni punkt po lewej stronie kafelka, może to potrwać cztery kafelki:

Skalowanie pokazowe powiększenia

Po określeniu poziomu powiększenia można obliczyć wartości x i y. Lewy górny kafelek w każdej siatce powiększenia to x=0, y=0; prawy dolny kafelek znajduje się na x=2zoom-1, y=2zoom-1.

Oto siatka powiększenia dla poziomu powiększenia 1:

Siatka powiększenia dla poziomu powiększenia 1

Indeksy quadkey

Niektóre platformy mapowania używają quadkey konwencji nazewnictwa indeksowania, która łączy współrzędne ZY kafelka w ciąg jednowymiarowy nazywany quadtree kluczami lub quadkeys krótko. Każdy quadkey unikatowy identyfikuje pojedynczy kafelek na określonym poziomie szczegółów i może być używany jako klucz w typowych indeksach drzewa B bazy danych. Zestawy SDK usługi Azure Mapy obsługują nakładanie warstw kafelków, które używają quadkey konwencji nazewnictwa oprócz innych konwencji nazewnictwa, jak opisano w dokumencie Dodawanie warstwy kafelka.

Uwaga

Konwencja quadkeys nazewnictwa działa tylko dla poziomów powiększenia jednego lub większego. Zestaw SDK platformy Azure Mapy obsługuje poziom powiększenia 0, który jest pojedynczym kafelkiem mapy dla całego świata.

Aby przekonwertować współrzędne kafelka na quadkeywartość , bity współrzędnych Y i X są przeplatane, a wynik jest interpretowany jako liczba base-4 (z zerami wiodącymi utrzymywanymi) i konwertowany na ciąg. Na przykład podane współrzędne XY kafelka (3, 5) na poziomie 3 quadkey są określane w następujący sposób:

tileX = 3 = 011 (base 2)

tileY = 5 = 101 (base 2)

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

Qquadkeys mają kilka interesujących właściwości. Najpierw długość quadkey (liczby cyfr) jest równa poziomowi powiększenia odpowiedniego kafelka. Po drugie, dowolny quadkey kafelek zaczyna się od quadkey kafelka nadrzędnego (zawierającego kafelek na poprzednim poziomie). Jak pokazano w poniższym przykładzie, kafelek 2 jest elementem nadrzędnym kafelków od 20 do 23:

Ostrosłup kafelka quadkey

quadkeys Na koniec podaj jednowymiarowy klucz indeksu, który zwykle zachowuje bliskość kafelków w przestrzeni XY. Innymi słowy, dwa kafelki, które mają pobliskie współrzędne XY, zwykle mają quadkeys stosunkowo blisko siebie. Jest to ważne w celu optymalizacji wydajności bazy danych, ponieważ sąsiadujące kafelki są często wymagane w grupach i pożądane jest zachowanie tych kafelków w tych samych blokach dysku, aby zminimalizować liczbę odczytów dysków.

Kod źródłowy matematyczny kafelka

Poniższy przykładowy kod ilustruje sposób implementowania funkcji opisanych w tym dokumencie. Te funkcje można łatwo przetłumaczyć na inne języki programowania w razie potrzeby.

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

Uwaga

Kontrolki mapy interakcyjnej w zestawie SDK usługi Azure Mapy mają funkcje pomocnicze do konwertowania między położeniami geoprzestrzennych i pikselami widoku.

Następne kroki

Bezpośredni dostęp do kafelków mapy z usług REST platformy Azure Mapy:

Dowiedz się więcej na temat pojęć geoprzestrzennych: