The Azure Maps Static Map service does not have a feature that automatically calculates the best view for a set of points. In Bing Maps this wasn't a feature that was used much, so it wasn't carried over to Azure Maps.
The best center and zoom level for your data that fits within your desired image size can be calculated programmatically. An algorithm for this is documented in this document: https://learn.microsoft.com/en-us/azure/azure-maps/zoom-levels-and-tile-grid?tabs=csharp (code available in C# and Typescript). Here is the code for the specific functions:
/// <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);
}
/**
* Calculates the best map view (center, zoom) for a bounding box on a map.
* @param bounds A bounding box defined as an array of numbers in the format of [west, south, east, north].
* @param mapWidth Map width in pixels.
* @param mapHeight Map height in pixels.
* @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.
* @param tileSize The size of the tiles in the tile pyramid.
* @returns The center and zoom level to best position the map view over the provided bounding box.
*/
public static BestMapView(bounds: number[], mapWidth: number, mapHeight: number, padding: number, tileSize: number): { center: number[], zoom: number } {
if (bounds == null || bounds.length < 4) {
return {
center: [0, 0],
zoom: 1
};
}
var boundsDeltaX: number;
var centerLat: number;
var centerLon: number;
//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);
var zoom = Math.log2(360 / (resolution * tileSize));
return {
center: [centerLon, centerLat],
zoom: zoom
};
}
Here is a quick summary of how to use this function:
- Calculate the bounding box of your points (simply calculate the min/max latitude and longitude values). The bounding box can be passed a simple array with the format
[west, south, east, north]
or[minLongitude, minLatitude, maxLongitude, maxLatitude]
. - Pass in your desired map width/height as integer pixels. You will want a padding since pushpins are rendered using an icon that covers more area than the single point they represent. Generally, a value between 40 and 80 is good.
- This code is designed to be usable with different map scales and handles this based on the tile size. With the static map service I believe you will want to set this to 256. If that doesn't look right, then try 512.
The output from this function will be the recommended center coordinate ([longitude, latitude]
) and zoom level to use in your request to the static map service.