Niveaux de zoom et grille mosaïque
Azure Maps utilise le système de coordonnées de projection Spherical Mercator (EPSG:3857). Une projection est le modèle mathématique utilisé pour transformer le globe sphérique en une carte plate. La projection Spherical Mercator étire la carte au niveau des pôles afin de créer une carte carrée. Cette projection déforme significativement l’échelle et la zone de la carte, mais deux propriétés importantes l’emportent sur cette distorsion :
- Il s’agit d’une projection conforme, ce qui signifie qu’elle conserve la forme des objets relativement petits. Conserver la forme des petits objets est d'autant plus important dans le cas de l'imagerie aérienne. À titre d'exemple, il est important d'éviter la déformation des bâtiments. Les bâtiments carrés doivent apparaître carrés, pas rectangulaires.
- Il s'agit d'une projection cylindrique. Le nord et le sud se trouvent toujours en haut et en bas, et l'ouest et l'est toujours à gauche et à droite.
Pour optimiser les performances de récupération et d’affichage de la carte, celle-ci est divisée en mosaïques carrées. Le kit de développement logiciel (SDK) Azure Maps utilise des mosaïques d’une taille de 512 x 512 pixels pour les cartes routières, et de 256 x 256 pixels pour les images satellites. Azure Maps fournit des mosaïques Raster et Vecteur pour 23 niveaux de zoom, numérotés de 0 à 22. Au niveau de zoom 0, le monde entier tient dans une seule mosaïque :
Le niveau de zoom 1 utilise quatre mosaïques pour afficher le monde : un carré de 2 x 2
Chaque niveau de zoom supplémentaire divise en quatre les mosaïques du niveau précédent, en créant une grille de 2zoom x 2zoom. Le niveau de zoom 22 est une grille de 222 x 222 ou de 4 194 304 x 4 194 304 mosaïques (17 592 186 044 416 mosaïques au total).
Les contrôles de la carte interactive Azure Maps pour le web et Android prennent en charge 25 niveaux de zoom, numérotés de 0 à 24. Toutefois, les données de route ne sont disponibles qu’au niveau de zoom lorsque les mosaïques sont disponibles.
Le tableau suivant fournit la liste complète des valeurs pour les niveaux de zoom où la taille de la vignette est un carré de 256 pixels de côté :
Niveau de zoom | Compteurs/pixel | Compteurs/côté mosaïque |
---|---|---|
0 | 156543 | 40 075 017 |
1 | 78271.5 | 20 037 508 |
2 | 39135.8 | 10 018 754 |
3 | 19 567,88 | 5 009 377,1 |
4 | 9 783,94 | 2 504 688,5 |
5 | 4 891,97 | 1 252 344,3 |
6 | 2 445,98 | 626 172,1 |
7 | 1 222,99 | 313 086,1 |
8 | 611.5 | 156543 |
9 | 305,75 | 78271.5 |
10 | 152,87 | 39135.8 |
11 | 76,44 | 19567.9 |
12 | 38,219 | 9 783,94 |
13 | 19,109 | 4 891,97 |
14 | 9,555 | 2 445,98 |
15 | 4,777 | 1 222,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 |
Coordonnées des pixels
Après avoir choisi la projection et l’échelle à utiliser à chaque niveau de zoom, nous pouvons convertir les coordonnées géographiques en coordonnées de pixels. La largeur et la hauteur en pixels de l’image d’une carte du monde pour un niveau de zoom particulier sont calculées comme suit :
var mapWidth = tileSize * Math.pow(2, zoom);
var mapHeight = mapWidth;
Étant donné que la largeur et la hauteur de la carte sont différentes à chaque niveau de zoom, il en est de même pour les coordonnées des pixels. Le pixel situé dans le coin supérieur gauche de la carte possède toujours les coordonnées suivantes (0, 0). Le pixel situé dans le coin inférieur droit de la carte possède des coordonnées (largeur-1, hauteur-1) ou fait référence aux équations de la section précédente (tileSize * 2zoom–1, tileSize * 2zoom–1). Par exemple, lors de l’utilisation de 512 mosaïques carrées au niveau 2, les coordonnées de pixels sont comprises entre (0, 0) et (2047, 2047), comme suit :
Avec une latitude et une longitude en degrés données, et le niveau de détail, les coordonnées XY du pixel sont calculées comme suit :
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);
Les valeurs de latitude et de longitude sont supposées être sur la référence WGS 84. Même si Azure Maps utilise une projection sphérique, il est important de convertir toutes les coordonnées géographiques en une donnée commune. WGS 84 correspond à la donnée sélectionnée. La valeur de longitude est supposée être comprise entre - 180 degrés et + 180 degrés, et la valeur de latitude doit être découpée pour être comprise entre - 85,05112878 et 85,05112878. Adhérer à ces valeurs permet d'éviter toute singularité au niveau des pôles, et garantit à la carte projetée une forme carrée.
Coordonnées de mosaïque
Pour optimiser les performances de récupération et d’affichage de la carte, la carte rendue est divisée en mosaïques. Le nombre de pixels et le nombre de mosaïques diffèrent à chaque niveau de zoom :
var numberOfTilesWide = Math.pow(2, zoom);
var numberOfTilesHigh = numberOfTilesWide;
Chaque mosaïque reçoit des coordonnées XY allant de (0,0) dans le coin supérieur gauche à (2zoom–1, 2zoom–1) dans le coin inférieur droit. Par exemple, au niveau de zoom 3, les coordonnées de la mosaïque vont de (0, 0) à (7, 7) comme suit :
Avec une paire de coordonnées XY de pixels, vous pouvez facilement déterminer les coordonnées XY de la mosaïque contenant ce pixel :
var tileX = Math.floor(pixelX / tileSize);
var tileY = Math.floor(pixelY / tileSize);
Les mosaïques sont appelées selon le niveau de zoom. Les coordonnées x et y correspondent à la position de la mosaïque sur la grille pour ce niveau de zoom.
Quand vous déterminez le niveau de zoom à utiliser, n’oubliez pas que chaque emplacement correspond à une position fixe sur sa mosaïque. Ainsi, le nombre de mosaïques nécessaires pour afficher l’étendue donnée d’un territoire dépend du placement spécifique de la grille de zoom sur la carte du monde. Par exemple, si deux points sont éloignés l’un de l’autre de 900 mètres, trois mosaïques peuvent suffire pour afficher un itinéraire entre eux au niveau de zoom 17. Toutefois, si le point occidental se trouve à droite de sa mosaïque et que le point oriental se trouve à gauche, on obtient quatre mosaïques :
Lorsque le niveau de zoom est déterminé, les valeurs x et y peuvent être calculées. La mosaïque en haut à gauche de chaque grille de zoom correspond à x=0, y=0 ; la mosaïque en bas à droite correspond à x=2zoom -1, y=2zoom-1.
La grille de zoom pour le niveau de zoom 1 est proposée ici :
Index Quadkey
Certaines plateformes de mappage utilisent une convention d’affectation de noms d’indexation quadkey
qui associe les coordonnées ZY d’une mosaïque dans une chaîne à une seule dimension appelée clés quadtree
ou quadkeys
en abrégé. Chaque quadkey
identifie de façon unique une seule mosaïque à un niveau de détail particulier et elle peut être utilisée comme clé dans les index B-Tree des bases de données courantes. Les kits de développement logiciel (SDK) Azure Maps prennent en charge la superposition des couches de mosaïques qui utilisent la Convention d’affectation de noms quadkey
en plus des autres conventions d’affectation de noms, décrites dans le document quadkey
.
Notes
La convention d’affectation de noms quadkeys
fonctionne uniquement pour les niveaux de zoom de niveau un ou plus. Le Kit de développement logiciel (SDK) Azure Maps prend en charge le niveau de zoom 0, qui constitué d’une seule mosaïque pour l’ensemble du monde.
Pour convertir des coordonnées de mosaïque en une quadkey
, les bits des coordonnées Y et X sont entrelacés, et le résultat est interprété comme un nombre en base 4 (avec des zéros non significatifs) et converti en chaîne. Par exemple, avec les coordonnées XY (3, 5) d’une mosaïque donnée au niveau 3, la quadkey
est déterminée comme suit :
tileX = 3 = 011 (base 2)
tileY = 5 = 101 (base 2)
quadkey = 100111 (base 2) = 213 (base 4) = "213"
Les Qquadkeys
possèdent plusieurs propriétés intéressantes. Tout d’abord, la longueur d’une quadkey
(le nombre de chiffres) est égale au niveau de zoom de la mosaïque correspondante. Deuxièmement, la quadkey
de toute mosaïque commence par la quadkey
de sa mosaïque parente (la mosaïque conteneur au niveau précédent). Comme indiqué dans l’exemple suivant, la mosaïque 2 est le parent des mosaïques 20 à 23 :
Enfin, les quadkeys
fournissent une clé d’index unidimensionnelle qui préserve habituellement la proximité des mosaïques dans l’espace XY. En d’autres termes, deux mosaïques ayant des coordonnées XY proches ont généralement des quadkeys
relativement proches les unes des autres. Cela est important pour optimiser les performances de la base de données, car les mosaïques voisines font souvent l’objet de requêtes dans des groupes et il est souhaitable de les conserver sur les mêmes blocs de disque, afin de réduire le nombre de lectures.
Code source mathématique de mosaïque
L’exemple de code suivant montre comment implémenter les fonctions décrites dans ce document. Ces fonctions peuvent être facilement traduites dans d’autres langages de programmation selon les besoins.
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);
}
}
}
Notes
Les contrôles de carte interactive dans le kit de développement logiciel (SDK) Azure Maps ont des fonctions d’assistance pour la conversion entre des positions géospatiales et des pixels de la fenêtre d’affichage.
Étapes suivantes
Accéder directement aux mosaïques à partir des services REST Azure Maps :
En savoir plus sur les concepts géospatiales :