Given these conditions:

  • a scale like 1:50000
  • the center of the viewport is 100°E, 20°N
  • the size of the viewport is 400x600

How can I calculate the bounding box of the viewport?

The geographic coordinate system of the map is EPSG:4490.

We want to display them in a different projection like Mercator or latitude_longitude_projection(maybe this is the so-said un-projected).

The size of the viewport is in pixels.

  • What projection are your map in? Web Mercator? Or do you display a unprojected map in lat/lon? And the size of your viewport, is that in pixels? Is the scale in piels? Do you use any mapping framework/library that might have a function for this? – atlefren May 15 '13 at 6:18
  • What GIS software and version are you using to try and do this? – PolyGeo May 15 '13 at 6:58
  • @atlefren: I upadte the post. – giser May 15 '13 at 7:00
  • @PolyGeo: None, we tried to display the map data in android. And we have not find any library. – giser May 15 '13 at 7:01
  • 1
    Android, are you using the Google Maps API or just plain displaying an image? I Would also recommend against using an unprojected coordinate system to display your map, as it will look heavily distorted. – atlefren May 15 '13 at 7:43

Ok, with some initial issues cleared out the task is relatively simple.

Scale, prepresented as f.ex 1:50000 means that one unit on the map corresponds to 50.000 units in the real world.

For a paper map printed a scale of 1:50000 this means that 1 meter on the map corresponds to 50.000 meters in the real world, or to make it easier: 1 cm on the map corresponds to 50 meter in the real world. So far, so good.

When computer (or phone screens) enter the show it's much more difficult: the unit of measure on a screen is a pixel, which doesn't map directly to centimeters. OpenLayers are (or at least where) using the "Dots per inch", and assumed that one inch corresponds to 72 pixels (this makes some sense on 72 dpi screens, but is wrong on i.e Retina Displays. But for now, lets stick to 72 dpi (as this is what most mapping libraries does (I think, corrections welcome)).

OpenLayers has a function OpenLayers.Util.getResolutionFromScale (see source):

OpenLayers.Util.getResolutionFromScale = function (scale, units) {
    var resolution;
    if (scale) {
        if (units == null) {
            units = "degrees";
        var normScale = OpenLayers.Util.normalizeScale(scale);
        resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
                                        * OpenLayers.DOTS_PER_INCH);
    return resolution;
  • With units="degrees" (which EPSG:4490 is, from what I gather) we get inches_per unit = 4374754 (OpenLayers.INCHES_PER_UNIT["degrees"])

  • a scale of 1:50000 (which corresponds to 1/50000 = 0.00002) (this is what penLayers.Util.normalizeScale computes) gives normScale = 0.00002

  • OpenLayers.DOTS_PER_INCH = 72

We can then calculate resolution as

1 / (0.00002 * 4374754 * 72) = 0.00015873908440210453

Knowing the center point (lon=100, lat=30), the pixel size of the viewport(w=400, h=600) and the resolution we can then use the calculateBounds function from OpenLayers.Map (see source):

calculateBounds: function(center, resolution) {

        var extent = null;

        if (center == null) {
            center = this.getCachedCenter();
        if (resolution == null) {
            resolution = this.getResolution();

        if ((center != null) && (resolution != null)) {
            var halfWDeg = (this.size.w * resolution) / 2;
            var halfHDeg = (this.size.h * resolution) / 2;

            extent = new OpenLayers.Bounds(center.lon - halfWDeg,
                                           center.lat - halfHDeg,
                                           center.lon + halfWDeg,
                                           center.lat + halfHDeg);

        return extent;

which we can reduce to:

function calculateBounds(center, resolution, size) {       
    var halfWDeg = (size.w * resolution) / 2;
    var halfHDeg = (size.h * resolution) / 2;
    return {
        "left": center.lon - halfWDeg,
        "bottom": center.lat - halfHDeg,
        "right": center.lon + halfWDeg,
        "top": center.lat + halfHDeg

Calling this with our values gives:

calculateBounds({"lon": 100, "lat": 30}, 0.00015873908440210453, {"w": 400, "h":600});
    left: 99.96825218311957, 
    bottom: 29.95237827467937,
    right: 100.03174781688043,
    top: 30.04762172532063

We can then combine all this to a function that works for degrees with scale denominator given:

function calculateBounds(center, scaleDenominator, size) {       
    var resolution = 1 / ((1 / scaleDenominator) * 4374754 * 72)
    var halfWDeg = (size.w * resolution) / 2;
    var halfHDeg = (size.h * resolution) / 2;
    return {
        "left": center.lon - halfWDeg,
        "bottom": center.lat - halfHDeg,
        "right": center.lon + halfWDeg,
        "top": center.lat + halfHDeg
  • Wonderful answer. In fact, it seems that the key point here is the dpi value. Isn't it? – giser May 15 '13 at 9:01
  • Oh, how about the projection? It seems that the above codes is used for caculate the degree directly. – giser May 15 '13 at 9:07
  • Yes, that is correct. As long as your users doesn't expect to use the map in the same fashion as a paper map (i.e measuring on it) any value can be used (and I think most mapping libraries use 72 or 96) – atlefren May 15 '13 at 9:08
  • the only thing you need to know about the projection is its units, OpenLayers uses the OpenLayers.INCHES_PER_UNIT[units] array in Utils.js. web mercator uses meters as unit, which means 39.37 instead of 4374754 to calculate resolution – atlefren May 15 '13 at 9:10
  • @atlerfren: Hi, it seems that you are an experter of openlayers, I meet some problems related to it. Can you spare some time to have a checek to this post? gis.stackexchange.com/q/61256/12681 – giser May 20 '13 at 6:01

Your Answer

