When you visualize geographic data, it helps to have a map for a background. To coordinate the map to your data, you need the latitudes and longitudes of the map’s boundaries. And to maximize the space, the map should be padded in only one direction, to make your boundaries fit the shape of the image. There are a few obstacles with getting the perfect map. To make it easier to find the perfect map, I’ve created a tool you can use to specify the perfect bounding box and image size.
I want to be able to specify two things:
- The bounding box (north, south, east, west)
- The image size (width and height, in pixels)
In addition to getting a map in return, I also need to know what the result’s bounding box is. It will probably be bigger than what I requested, unless the image size has the exact same ratio as my bounding box. That’s highly unlikely. To avoid stretching either the latitudes or longitudes, I need to add padding to either the left and right, or the top and bottom. Once the padding is added, the bounding box will grow either horizontally or vertically.
The image below illustrates the request’s parameters and the necessary adjustments.
The purple square in the center represents the requested bounding box, inside a 500-by-200 image as requested. In order to give the bounding box the same ratio as the 5:2 ratio of the image, the cyan-colored regions are added to pad the sides.
I assume I’ll need to scale the padded bounding box to match the requested image size. My preference is to reduce an image when resizing, so I get a sharper image.
Searching for the ideal map source
- Google brands even external maps, with a faint “Google” logo in the lower-left-hand corner, and that “terms of service” link in the lower-right-hand corner. Even though I know I have the rights to make derivitive works from OpenStreetMap maps, I don’t like seeing that terms of service warning.
In order to get a map that I can manipulate and provide to the user, I need to use a static map API, which puts the map directly into an image object and avoids the prickly canvas element. Google has a separate API for static maps that is easy to use. Their Static Maps API doesn’t require any programming; you just plop the parameters into the URL query string of the image. However, it doesn’t meet my needs because while you can specify a center location, you can’t specify a bounding box without resorting to placing a marker on each corner. You can specify a size for the image, but there’s no way to know the coordinates of the entire map that it returns.
Microsoft’s Bing Services also has an API to Get a Static Map, but with the same problems. You can specify a bounding box and an image size, but you can’t figure out the bounds of what it returns. (Plus a key is required, embedded in the URL.)
Mapquest also has a Static Map Service. You can add a “bestfit” parameter to a map’s URL to have it fit a bounding box into the image, and they even have a “margin” parameter. But like the others, there no way to know the bounds of the result. (And Mapquest also requires a key.)
The MapBox Static API produces beautiful maps, but is designed for center points and not bounding boxes.
I then stumbled across Pawel’s OSM Static Maps API. It uses the OpenStreetMaps maps. Its key feature is it never pads the result, because instead of specifying an image size, you specify a zoom level. The result is as big as needed for that zoom level.
That introduces a new problem: how do I know what the right zoom level is? If I use one too small, the scaled image will look pixelated and illegible. If I use one too big, the resulting image will be huge and take forever to process.
Implementing the solution
I can still use Google’s API to get two important bits of information:
- The minimum zoom level
- The coordinates of the padded bounding box
The API will always return a zoom level resulting in the bounding box being smaller than the requested image size. The way maps work, they have a set of tiles for every zoom level, and the tiles are stitched together and cropped to give you the custom map. For every increase in zoom level, the tiles are based on a doubled magnification. Because Google’s API includes a margin (allegedly 45 pixels) which the bounding box will never be drawn past, I might have to double the zoom level more than once in order to make my bounding box larger than the image size.
To figure out how big the padding needs to be, I need to know the dimensions of the bounding box. Because latudinal degrees are not the same as longitudinal degrees, they can’t be mixed together to get a ratio. Instead, I need to know the pixel dimensions. Once the points are mapped, I can use the MapCanvasProjection object’s fromLatLngToContainerPixel() method to get the ratio of the requested area, which I then stretch out to make it match the image’s ratio.
I send to Pawel’s API the padded bounding box’s coordinates, and the larger zoom level.
Because images from Pawel’s API are also considered “tainted” when used in the canvas, I still can’t manipulate the image in the browser. To get around that, I created a PHP proxy service which does the scaling for me.
Even the OpenStreetsMap map is branded with a logo in the lower-left-hand corner, and a scale in the upper-left-hand corner, neither of which are optional.
How to make your own custom maps
To use the tool, just plug your parameters into the URL:
The order of the parameters doesn’t matter. Here’s an example:
The program will show first the Google map, which you can’t copy, but then at the bottom of the page you’ll soon see the OpenStreetMaps map, which you can right-click to either copy to the clipboard or save as a file.
The purple rectangle in the center represents the requested bounding box. You can modify this region by dragging the control points, then hitting the Recalculate button.
Pawel’s OSM Static Maps API is running on a development server and not designed for heavy usage, but it should be sufficient for this little program.
I hope my map-making tool will make it easier and faster to get precisely-measured map images.