Azure Maps - generate static map optimized for pushpins

Ryan Brothers 0 Reputation points
2025-02-22T03:00:36.3266667+00:00

I am looking to migrate from Bing Maps to Azure Maps to generate a static map.

In Bing Maps, I pass a list of pushpins, and Bing Maps creates an optimized map based on the pushpins I passed. Bing Maps auto-selects the best zoom level to fit all the pushpins and returns an image in the width and height I passed:

https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-a-static-map#get-a-map-with-pushpins-that-does-not-specify-a-center-point-or-map-area

Get a map with pushpins that does not specify a center point or map area

If you do not specify a center point or map area, the map area is chosen to optimize the display of the pushpins.

Is there a way to make an equivalent call to Azure Maps? From what I see, I need to pass Azure Maps either bbox or center, and also pass a zoom level, but with Bing Maps, it calculated all of that for me and returned an optimized map. I just had to pass it the pushpins and the width and height that I wanted. Is there a way to calculate those above parameters to match what Bing Maps was doing for me?

Thanks for your help.

Ryan

Azure Maps
Azure Maps
An Azure service that provides geospatial APIs to add maps, spatial analytics, and mobility solutions to apps.
794 questions
{count} votes

Accepted answer
  1. rbrundritt 19,501 Reputation points Microsoft Employee
    2025-02-24T18:01:12.3966667+00:00

    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.

    2 people found this answer helpful.

1 additional answer

Sort by: Most helpful
  1. VSawhney 110 Reputation points Microsoft Vendor
    2025-02-25T01:26:19.7166667+00:00

    Hello Ryan Brothers,

    You can calculate the parameters (bounding box, center point, and zoom level) for Azure Maps to match what Bing Maps was doing for you. Here's a step-by-step guide to achieve this:

    1. Calculate the Bounding Box:    - Determine the minimum and maximum latitude and longitude values from your list of pushpins.    - For example, if your pushpins have the following coordinates: [(lat1, lon1), (lat2, lon2), (lat3, lon3)], find the minimum and maximum latitude and longitude values:      - min_lat = min(lat1, lat2, lat3)      - max_lat = max(lat1, lat2, lat3)      - min_lon = min(lon1, lon2, lon3)      - max_lon = max(lon1, lon2, lon3)
    2. Calculate the Center Point:    - The center point is the average of the minimum and maximum latitude and longitude values:      - center_lat = (min_lat + max_lat) / 2      - center_lon = (min_lon + max_lon) / 2      - Combine these to form the center point: center = (center_lat, center_lon)
    3. Calculate the Zoom Level:    - The zoom level calculation can be complex, as it depends on the map size (width and height) and the desired level of detail. However, you can use the formula or an external library to assist with this calculation. Here's a simplified approach:      - Calculate the difference in latitude and longitude:        - delta_lat = max_lat - min_lat        - delta_lon = max_lon - min_lon      - Use these differences to determine the zoom level. There are online calculators and libraries available to help with this calculation, such as leaflet for JavaScript.
    4. Make the API Call:    - Use the calculated parameters (bbox, center, and zoom) to request the static map from Azure Maps.

    Here's an example of the Azure Maps API call using these parameters:

    ''''http GET https://atlas.microsoft.com/map/static/png?api-version=1.0&bbox={min_lon},{min_lat},{max_lon},{max_lat}&center={center_lon},{center_lat}&zoom={zoom}&height={height}&width={width}&pins={pins} ''''

    For more detailed calculations and examples, you can refer to the Azure Maps documentation.](https://learn.microsoft.com/en-us/rest/api/maps/render/get-map-static-image?view=azure-maps-2024-04-01)."https://learn.microsoft.com/en-us/rest/api/maps/render/get-map-static-image?view=azure-maps-2024-04-01).")

    I hope this helps! If you need further assistance with the calculations or making the API call, feel free to ask.

    Thank you!

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.