Tingkat perbesar tampilan dan kisi petak peta

Azure Peta menggunakan sistem koordinat proyeksi Spherical Mercator (EPSG:3857). Proyeksi adalah model matematika yang digunakan untuk mengubah bola dunia yang bulat menjadi peta datar. Proyeksi Spherical Mercator merentangkan peta pada kutub untuk membuat peta persegi. Proyeksi ini secara signifikan mengubah skala dan area peta, tetapi memiliki dua properti penting yang lebih baik dari distorsi ini:

  • Proyeksi ini adalah proyeksi konformal, yang mempertahankan bentuk dari objek yang relatif kecil. Mempertahankan bentuk dari benda kecil sangat penting ketika menampilkan citra udara. Misalnya, kita ingin menghindari perubahan bentuk pada bangunan. Bangunan persegi harus tampak persegi, bukan persegi panjang.
  • Proyeksi ini disebut proyeksi silinder. Utara dan selatan selalu berada di atas dan bawah, sementara barat dan timur selalu berada di kiri dan kanan.

Untuk mengoptimalkan performa pengambilan dan tampilan, peta dibagi menjadi petak peta persegi. Azure Maps SDK menggunakan petak peta dengan ukuran 512 x 512 piksel untuk peta jalan, dan yang lebih kecil 256 x 256 piksel untuk citra satelit. Azure Maps menyediakan petak peta raster dan vektor untuk 23 tingkat perbesar tampilan, bernomor 0 hingga 22. Pada tingkat perbesar tampilan 0, seluruh dunia masuk ke dalam satu petak peta:

Petak peta dunia

Tingkat perbesar tampilan 1 menggunakan empat petak peta untuk membuat dunia: berbentuk persegi 2 x 2

Tata letak petak peta 2x2

Setiap tingkat perbesar tampilan tambahan membagi empat petak peta yang sebelumnya, membuat kisi perbesar tampilan 2 x perbesar tampilan 2. Tingkat perbesar tampilan 22 adalah kisi 222 x 222, atau petak peta 4.194.304 x 4.194.304 (total 17.592.186.044.416).

Kontrol peta interaktif Azure Maps untuk web dan Android mendukung 25 tingkat perbesar tampilan, dengan nomor 0 hingga 24. Meskipun data jalan hanya tersedia pada tingkat perbesar tampilan saat petak peta tersedia.

Tabel berikut ini menyediakan daftar lengkap nilai untuk tingkat zoom di mana ukuran petak peta adalah 256 piksel persegi:

Tingkat perbesar tampilan Meter/piksel Meter/sisi petak peta
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

Koordinat piksel

Setelah memilih proyeksi dan skala untuk digunakan di setiap tingkat perbesar tampilan, kita dapat mengonversi koordinat geografis menjadi koordinat piksel. Lebar dan tinggi piksel penuh pada gambar peta dunia untuk tingkat perbesar tampilan tertentu dihitung sebagai:

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

var mapHeight = mapWidth;

Karena lebar dan tinggi peta berbeda pada setiap tingkat perbesar tampilan, perbedaan ini berlaku juga pada koordinat piksel. Piksel di sudut kiri atas peta selalu memiliki koordinat piksel (0, 0). Piksel di sudut kanan bawah peta memiliki koordinat piksel (width-1, height-1), atau mengacu pada persamaan di bagian sebelumnya, (tileSize * 2 zoom–1, ukuran ubin * 2zoom–1). Misalnya, saat menggunakan petak peta persegi 512 pada tingkat 2, koordinat piksel berkisar dari (0, 0) hingga (2047, 2047), seperti ini:

Peta menampilkan dimensi piksel

Mengingat garis lintang dan garis bujur dalam derajat, serta tingkat kedetailannya, maka koordinat piksel XY dihitung sebagai berikut:

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

Nilai garis lintang dan garis bujur diasumsikan berada pada datum WGS 84. Meskipun Azure Maps menggunakan proyeksi bulat, penting untuk mengonversi semua koordinat geografis menjadi datum umum. WGS 84 adalah datum yang dipilih. Nilai garis bujur diasumsikan berkisar dari -180 derajat hingga +180 derajat, dan nilai garis lintang harus dipotong berkisar dari -85,05112878 hingga 85,05112878. Mematuhi nilai-nilai ini menghindari singularitas pada kutub, dan memastikan bahwa peta yang diproyeksikan berbentuk persegi.

Koordinat petak peta

Untuk mengoptimalkan kinerja pengambilan dan tampilan peta, peta yang dibuat dipotong menjadi petak peta. Jumlah piksel dan jumlah petak peta berbeda pada setiap tingkatan perbesar tampilan:

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

var numberOfTilesHigh = numberOfTilesWide;

Setiap petak peta diberikan koordinat XY mulai dari (0, 0) di bagian kiri atas hingga (2perbesar tampilan–1, 2perbesar tampilan–1) di bagian kanan bawah. Misalnya, pada tingkat perbesar tampilan 3, koordinat petak peta berkisar dari (0, 0) hingga (7, 7) sebagai berikut:

Peta koordinat petak peta

Dengan melihat sepasang koordinat piksel XY, Anda dapat dengan mudah menentukan koordinat petak peta XY pada petak peta yang berisi piksel tersebut:

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

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

Petak peta dipanggil menurut tingkat perbesar tampilan. Koordinat x dan y sesuai dengan posisi petak peta pada kisi untuk tingkat perbesar tampilan tersebut.

Saat menentukan tingkat perbesar tampilan yang akan digunakan, ingatlah setiap lokasi berada dalam posisi tetap pada petak petanya. Akibatnya, jumlah petak peta yang diperlukan untuk menampilkan hamparan wilayah yang diberikan tergantung pada penempatan spesifik dari kisi perbesar tampilan pada peta dunia. Misalnya, jika ada dua titik terpisah 900 meter, mungkin hanya perlu tiga petak peta untuk menampilkan rute di antaranya pada tingkat perbesar tampilan 17. Namun, jika titik barat berada di sebelah kanan petak petanya, dan titik timur di sebelah kiri petak petanya, maka mungkin memerlukan empat petak peta:

Skala demo perbesar tampilan

Setelah tingkat perbesar tampilan ditentukan, nilai x dan y dapat dihitung. Petak peta kiri atas pada setiap kisi perbesar tampilan adalah x=0, y=0; petak peta kanan bawah ada di x=2perbesar tampilan-1, y=2perbesar tampilan-1.

Berikut kisi zoom untuk zoom tingkat 1:

Kisi perbesar tampilan untuk perbesar tampilan 1

Indeks quadkey

Beberapa platform pemetaan menggunakan konvensi quadkey penamaan pengindeksan yang menggabungkan koordinat petak peta ZY ke dalam untai satu dimensi yang disebut kunci quadtree atau quadkeys untuk singkatannya. Masing-masing quadkey secara unik mengidentifikasi petak peta tunggal pada tingkat kedetailan tertentu, dan dapat digunakan sebagai kunci dalam indeks B-tree database umum. Azure Maps SDK mendukung hamparan lapisan petak peta yang menggunakan konvensi penamaan quadkey selain konvensi penamaan lainnya seperti yang didokumentasikan dalam dokumen Tambahkan lapisan petak peta.

Catatan

Konvensi penamaan quadkeys hanya berfungsi untuk tingkat perbesar tampilan satu atau yang lebih besar. Azure Maps SDK mendukung tingkat perbesar tampilan 0 yang merupakan petak peta tunggal untuk seluruh dunia.

Untuk mengonversi koordinat ubin menjadi quadkey, bit koordinat Y dan X saling terkait, dan hasilnya diinterpretasikan sebagai angka dasar 4 (dengan nol di depan dipertahankan) dan dikonversi menjadi untai. Misalnya, mengingat koordinat ubin XY (3, 5) pada tingkat 3, maka quadkey ditentukan sebagai berikut:

tileX = 3 = 011 (base 2)

tileY = 5 = 101 (base 2)

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

Qquadkeys memiliki beberapa properti yang menarik. Pertama, panjang quadkey (jumlah digit) sama dengan tingkat perbesar tampilan petak peta yang sesuai. Kedua, quadkey dari setiap petak peta dimulai dengan quadkey dari petak peta induknya (petak peta yang terisi pada tingkat sebelumnya). Seperti yang ditunjukkan dalam contoh berikut, petak 2 adalah induk petak peta 20 hingga 23:

Piramida petak peta quadkey

Terakhir, quadkeys menyediakan kunci indeks satu dimensi yang biasanya mempertahankan kedekatan petak peta di ruang XY. Dengan kata lain, dua petak peta yang memiliki koordinat XY terdekat biasanya memiliki quadkeys yang relatif berdekatan. Hal ini penting untuk mengoptimalkan performa database, karena petak peta yang berdekatan sering diminta dalam kelompok, dan dianjurkan untuk menyimpan petak peta tersebut pada blok disk yang sama untuk meminimalkan jumlah disk yang dibaca.

Kode sumber matematika petak peta

Sampel kode berikut ini menggambarkan cara mengimplementasikan fungsi yang dijelaskan dalam dokumen ini. Fungsi-fungsi ini dapat dengan mudah diterjemahkan ke dalam bahasa pemrograman lain sesuai kebutuhan.

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

Catatan

Kontrol peta interaktif pada Azure Maps SDK memiliki fungsi pembantu untuk mengonversi antara posisi geospasial dan piksel viewport.

Langkah berikutnya

Akses petak peta secara langsung dari layanan REST Azure Maps:

Pelajari selengkapnya tentang konsep geospasial: