Skip to content

Commit eb11a5e

Browse files
authored
Merge pull request #125939 from rbrundritt/patch-17
Update zoom-levels-and-tile-grid.md
2 parents 547cd80 + 765cc2e commit eb11a5e

File tree

1 file changed

+128
-84
lines changed

1 file changed

+128
-84
lines changed

articles/azure-maps/zoom-levels-and-tile-grid.md

Lines changed: 128 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -494,51 +494,76 @@ namespace AzureMaps
494494
/// <param name="bounds">A bounding box defined as an array of numbers in the format of [west, south, east, north].</param>
495495
/// <param name="mapWidth">Map width in pixels.</param>
496496
/// <param name="mapHeight">Map height in pixels.</param>
497-
/// <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>
498-
/// <param name="tileSize">The size of the tiles in the tile pyramid.</param>
499497
/// <param name="latitude">Output parameter receiving the center latitude coordinate.</param>
500498
/// <param name="longitude">Output parameter receiving the center longitude coordinate.</param>
501499
/// <param name="zoom">Output parameter receiving the zoom level</param>
502-
public static void BestMapView(double[] bounds, double mapWidth, double mapHeight, int padding, int tileSize, out double centerLat, out double centerLon, out double zoom)
500+
/// <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. Default: 0</param>
501+
/// <param name="tileSize">The size of the tiles in the tile pyramid. Default: 512</param>
502+
/// <param name="maxZoom">Optional maximum zoom level to return. Useful when the bounding box represents a very small area. Default: 24</param>
503+
/// <param name="allowFloatZoom">Specifies if the returned zoom level should be a float or rounded down to an whole integer zoom level. Default: true</param>
504+
public static void BestMapView(BoundingBox bounds, double mapWidth, double mapHeight, out double centerLat, out double centerLon, out double zoom, int padding = 0, int tileSize = 512, double maxZoom = 24, bool allowFloatZoom = true)
503505
{
504-
if (bounds == null || bounds.Length < 4)
505-
{
506-
centerLat = 0;
507-
centerLon = 0;
508-
zoom = 1;
509-
return;
510-
}
511-
512-
double boundsDeltaX;
513-
514-
//Check if east value is greater than west value which would indicate that bounding box crosses the antimeridian.
515-
if (bounds[2] > bounds[0])
516-
{
517-
boundsDeltaX = bounds[2] - bounds[0];
518-
centerLon = (bounds[2] + bounds[0]) / 2;
519-
}
520-
else
521-
{
522-
boundsDeltaX = 360 - (bounds[0] - bounds[2]);
523-
centerLon = ((bounds[2] + bounds[0]) / 2 + 360) % 360 - 180;
524-
}
525-
526-
var ry1 = Math.Log((Math.Sin(bounds[1] * Math.PI / 180) + 1) / Math.Cos(bounds[1] * Math.PI / 180));
527-
var ry2 = Math.Log((Math.Sin(bounds[3] * Math.PI / 180) + 1) / Math.Cos(bounds[3] * Math.PI / 180));
528-
var ryc = (ry1 + ry2) / 2;
529-
530-
centerLat = Math.Atan(Math.Sinh(ryc)) * 180 / Math.PI;
531-
532-
var resolutionHorizontal = boundsDeltaX / (mapWidth - padding * 2);
533-
534-
var vy0 = Math.Log(Math.Tan(Math.PI * (0.25 + centerLat / 360)));
535-
var vy1 = Math.Log(Math.Tan(Math.PI * (0.25 + bounds[3] / 360)));
536-
var zoomFactorPowered = (mapHeight * 0.5 - padding) / (40.7436654315252 * (vy1 - vy0));
537-
var resolutionVertical = 360.0 / (zoomFactorPowered * tileSize);
538-
539-
var resolution = Math.Max(resolutionHorizontal, resolutionVertical);
540-
541-
zoom = Math.Log(360 / (resolution * tileSize), 2);
506+
centerLat = 0;
507+
centerLon = 0;
508+
zoom = 0;
509+
510+
if (bounds != null && mapWidth > 0 && mapHeight > 0)
511+
{
512+
//Ensure padding is valid.
513+
padding = Math.Abs(padding);
514+
515+
//Ensure max zoom is within valid range.
516+
maxZoom = Clip(maxZoom, 0, 24);
517+
518+
//Do pixel calculations at zoom level 24 as that will provide a high level of visual accuracy.
519+
int pixelZoom = 24;
520+
521+
//Calculate mercator pixel coordinate at zoom level 24.
522+
var wnPixel = PositionToGlobalPixel(new double[] { bounds[0], bounds[3] }, pixelZoom, tileSize);
523+
var esPixel = PositionToGlobalPixel(new double[] { bounds[2], bounds[1] }, pixelZoom, tileSize);
524+
525+
//Calculate the pixel distance between pixels for each axis.
526+
double dx = esPixel[0] - wnPixel[0];
527+
double dy = esPixel[1] - wnPixel[1];
528+
529+
//Calculate the average pixel positions to get the visual center.
530+
double xAvg = (esPixel[0] + wnPixel[0]) / 2;
531+
double yAvg = (esPixel[1] + wnPixel[1]) / 2;
532+
533+
//Determine if the bounding box crosses the antimeridian. (West pixel will be greater than East pixel).
534+
if (wnPixel[0] > esPixel[0])
535+
{
536+
double mapSize = MapSize(24, tileSize);
537+
538+
//We are interested in the opposite area of the map. Calculate the opposite area and visual center.
539+
dx = mapSize - Math.Abs(dx);
540+
541+
//Offset the visual center by half the global map width at zoom 24 on the x axis.
542+
xAvg += mapSize / 2;
543+
}
544+
545+
//Convert visual center pixel from zoom 24 to lngLat.
546+
center = GlobalPixelToPosition(new Pixel(xAvg, yAvg), pixelZoom, tileSize);
547+
548+
//Calculate scale of screen pixels per unit on the Web Mercator plane.
549+
double scaleX = (mapWidth - padding * 2) / Math.Abs(dx) * Math.Pow(2, pixelZoom);
550+
double scaleY = (mapHeight - padding * 2) / Math.Abs(dy) * Math.Pow(2, pixelZoom);
551+
552+
//Calculate zoom levels based on the x/y scales. Choose the most zoomed out value.
553+
zoom = Math.Max(0, Math.Min(maxZoom, Math.Log2(Math.Abs(Math.Min(scaleX, scaleY)))));
554+
555+
//Round down zoom level if float values are not desired.
556+
if (!allowFloatZoom)
557+
{
558+
zoom = Math.Floor(zoom);
559+
}
560+
}
561+
562+
return new CameraOptions
563+
{
564+
Center = center,
565+
Zoom = zoom
566+
};
542567
}
543568
}
544569
}
@@ -873,51 +898,70 @@ module AzureMaps {
873898
* @param mapHeight Map height in pixels.
874899
* @param 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.
875900
* @param tileSize The size of the tiles in the tile pyramid.
901+
* @param maxZoom Optional maximum zoom level to return. Useful when the bounding box represents a very small area.
902+
* @param allowFloatZoom Specifies if the returned zoom level should be a float or rounded down to an whole integer zoom level.
876903
* @returns The center and zoom level to best position the map view over the provided bounding box.
877904
*/
878-
public static BestMapView(bounds: number[], mapWidth: number, mapHeight: number, padding: number, tileSize: number): { center: number[], zoom: number } {
879-
if (bounds == null || bounds.length < 4) {
880-
return {
881-
center: [0, 0],
882-
zoom: 1
883-
};
884-
}
885-
886-
var boundsDeltaX: number;
887-
var centerLat: number;
888-
var centerLon: number;
889-
890-
//Check if east value is greater than west value which would indicate that bounding box crosses the antimeridian.
891-
if (bounds[2] > bounds[0]) {
892-
boundsDeltaX = bounds[2] - bounds[0];
893-
centerLon = (bounds[2] + bounds[0]) / 2;
894-
}
895-
else {
896-
boundsDeltaX = 360 - (bounds[0] - bounds[2]);
897-
centerLon = ((bounds[2] + bounds[0]) / 2 + 360) % 360 - 180;
898-
}
899-
900-
var ry1 = Math.log((Math.sin(bounds[1] * Math.PI / 180) + 1) / Math.cos(bounds[1] * Math.PI / 180));
901-
var ry2 = Math.log((Math.sin(bounds[3] * Math.PI / 180) + 1) / Math.cos(bounds[3] * Math.PI / 180));
902-
var ryc = (ry1 + ry2) / 2;
903-
904-
centerLat = Math.atan(Math.sinh(ryc)) * 180 / Math.PI;
905-
906-
var resolutionHorizontal = boundsDeltaX / (mapWidth - padding * 2);
907-
908-
var vy0 = Math.log(Math.tan(Math.PI * (0.25 + centerLat / 360)));
909-
var vy1 = Math.log(Math.tan(Math.PI * (0.25 + bounds[3] / 360)));
910-
var zoomFactorPowered = (mapHeight * 0.5 - padding) / (40.7436654315252 * (vy1 - vy0));
911-
var resolutionVertical = 360.0 / (zoomFactorPowered * tileSize);
912-
913-
var resolution = Math.max(resolutionHorizontal, resolutionVertical);
914-
915-
var zoom = Math.log2(360 / (resolution * tileSize));
916-
917-
return {
918-
center: [centerLon, centerLat],
919-
zoom: zoom
920-
};
905+
public static BestMapView(bounds: number, mapWidth: number, mapHeight: number, padding: number = 0, tileSize: number = 512, maxZoom: number = 24, allowFloatZoom: boolean = true): { center: number[], zoom: number } {
906+
//Ensure valid bounds and map dimensions are provided.
907+
if (bounds == null || bounds.length < 4 || !mapWidth || !mapHeight || mapWidth <= 0 || mapHeight <= 0) {
908+
return {
909+
center: [0, 0],
910+
zoom: 0
911+
};
912+
}
913+
914+
//Ensure padding is valid.
915+
padding = Math.abs(padding || 0);
916+
917+
//Ensure max zoom is within valid range.
918+
maxZoom = this.Clip(maxZoom, 0, 24);
919+
920+
//Do pixel calculations at zoom level 24 as that will provide a high level of visual accuracy.
921+
const pixelZoom = 24;
922+
923+
//Calculate mercator pixel coordinate at zoom level 24.
924+
const wnPixel = this.PositionToGlobalPixel([bounds[0], bounds[3]], pixelZoom, tileSize);
925+
const esPixel = this.PositionToGlobalPixel([bounds[2], bounds[1]], pixelZoom, tileSize);
926+
927+
//Calculate the pixel distance between pixels for each axis.
928+
let dx = esPixel[0] - wnPixel[0];
929+
const dy = esPixel[1] - wnPixel[1];
930+
931+
//Calculate the average pixel positions to get the visual center.
932+
let xAvg = (esPixel[0] + wnPixel[0]) / 2;
933+
const yAvg = (esPixel[1] + wnPixel[1]) / 2;
934+
935+
//Determine if the bounding box crosses the antimeridian. (West pixel will be greater than East pixel).
936+
if(wnPixel[0] > esPixel[0]) {
937+
const mapSize = this.MapSize(24, tileSize);
938+
939+
//We are interested in the opposite area of the map. Calculate the opposite area and visual center.
940+
dx = mapSize - Math.abs(dx);
941+
942+
//Offset the visual center by half the global map width at zoom 24 on the x axis.
943+
xAvg += mapSize / 2;
944+
}
945+
946+
//Convert visual center pixel from zoom 24 to lngLat.
947+
const centerLngLat = this.GlobalPixelToPosition([xAvg, yAvg], pixelZoom, tileSize);
948+
949+
//Calculate scale of screen pixels per unit on the Web Mercator plane.
950+
const scaleX = (mapWidth - padding * 2) / Math.abs(dx) * Math.pow(2, pixelZoom);
951+
const scaleY = (mapHeight - padding * 2) / Math.abs(dy) * Math.pow(2, pixelZoom);
952+
953+
//Calculate zoom levels based on the x/y scales. Choose the most zoomed out value.
954+
let zoom = Math.max(0, Math.min(maxZoom, Math.log2(Math.abs(Math.min(scaleX, scaleY)))));
955+
956+
//Round down zoom level if float values are not desired.
957+
if(!allowFloatZoom) {
958+
zoom = Math.floor(zoom);
959+
}
960+
961+
return {
962+
center: centerLngLat,
963+
zoom
964+
};
921965
}
922966
}
923967
}

0 commit comments

Comments
 (0)