Mouse Coordinates To Lat/Long

This post is long overdue – a number
of people have asked me how to convert between lat/long and mouse coordinates
on an HTML page. First, let’s define some terms since otherwise is
easy to get confused:

  • Geodetic coordinate system- A geodetic
    coordinate system
    based on the WGS84 datum.
  • World coordinate system – The projected coordinate
    system – for example, the Mercator projection used by Google.
  • Document coordinate system – The browsers coordinate system, units
    are in pixels.
  • View coordinate system – The coordinate system of the HTML element,
    such as a div tag, that contains the map. Units are in pixels.

 

Since writing out coordinate system gets tedious pretty quickly, I’ll
abbreviate it as CS.

A Series of Transformations

Transformations are used to convert coordinates between different coordinate
systems. There are two types of transformations commonly used
in mapping.

The first is projection transformations which convert to
and from a geodetic CS to a projected CS like Mercator or UTM. Projection
transformations are often non-linear and require fairly complex math.

The second is affine tranformations. Affine
transformations are used to translate (i.e., move), scale, rotate and skew
features. Affine transformations are linear and are used by all drawing
programs and are often used in games (pick up any book about math for
game developers to learn more about affine transformations). They are easy to implement using linear algebra.

So back to the problem at hand. To get from a lat/long value to a pixel
value requires a series of transformations:

  1. Geodetic CS -> World CS (projection)
  2. World CS -> View CS (affine)
  3. View CS -> Document CS (affine)

We’ve already know how to transform a
point between the Geodetic CS and World CS. And step 3 is easy. But how do we do step #2?

Its done like this:

  1. Translate the world CS center to 0,0
  2. Rotate the map if needed
  3. Scale the map to the view CS
  4. Translate from 0,0 in the view CS to the center of the view CS.

Why the translation to 0,0 first? The image below from O’Reilly’s SVG
Essentials
book shows the problem. The small rectangle’s top left
is at 5,5. Now let’s scale the rectangle by a factor of 2 to make the
bigger rectangle. Note that the new rectangle is not only larger,
its top left corner has also shifted to 10,10.

Scale

Image courtesy of O’Reilly (taken from SVG Essentials)

In addition, if you want to rotate the map, you want it to rotate around
the center of the map not the current 0,0 position.

Scale Factor

Assuming you don’t want to rotate the map, the next step is to scale the
world CS to the view CS. How do we determine the scaling factor? This is
done by introducing another concept, unit size.

The unit
size is the physical world distance in centimeters (you can use any unit
system you like) represented by one unit of a coordinate system.
For the view CS, it is the number of centimeters on the ground represented
by one pixel. For the world CS, it is the number of centimeters on the
ground represented by one world CS unit (which could be inches or meters
or kilometers, etc.). Here is some pseudo code:

// How many centimeters in the physical world does one pixel cover?
var elementUnitDistance = (1/scale) * (1/PIXELS_PER_CM)

// How many centimeters in the physical world does one world unit cover?
var displayUnitDistance = worldCS.getDisplayUnitSize()

var scalingFactor = elementUnitDistance/displayUnitDistance

We make an assumption that there are 72 pixels per inch – to calculate
pixels per centimeter divide by 2.54. Note that this assumption
will always be off to some degree. If you are using SVG, it provides an
API that will return the exact number.

For Google’s Mercator projection, we can figure the unit size like this:


// What is the circumference of the earth at the current latitude in cm?
var latRadians = Math.degreesToRadians(lat)
var circumference = 2 * Math.PI * (Math.cos(latRadians) * EARTH_RADIUS_IN_CM) 

// What is the circumference of the earth in worldCS units?
var worldDistance = 2 * Math.PI * mercator_cs_radius

// Calculate the unit size
var unitSize = earthDistance/worldDistance

Note that Math.degreesToRadians is a custom extension to Javascript’s Math object.

As I discussed previously,
the scaling factor for Google Maps is always 1. Why is that? Because the
Google Mercator projection is also in pixels, so obviously one pixel equals
one pixel. The biggest advantage of doing this is that Google can pre-render map
tiles to greatly improve performance. It also avoids having to pick a value for pixels per inch. The disadvantage is that you cannot
zoom to an arbritrary scale on a map – you can only go to Google’s predefined
zoom levels.

If you use other projections, or other GIS systems, then you’ll have
to calculate the scaling factor.

An Example

Let’s work through an example. We want to render a map centered on the
Colorado State Capital building in downtown Denver at Google’s zoom level
16 (which means a preset scale of 1/5206.6). The map should be shown in
a div tag which is 500 pixels wide and 500 pixels high. The top left of
the div tag is located at 100,100 in the document CS.

The longitude/latitude of the Colorado state capital is -104.985 and 39.739
(I picked these values off a map so the may be a bit inaccurate).
From what we learned before, we can quickly calculate this to be an x value
of 3495974 and a y value of 6367308 in Google’s Mercator projection for
zoom level 16.

So, what is the latitude and longitude of the top left of the div tag?
We just reverse the steps described above.

// starting point in document CS
var x = 100
var y = 100

// Convert from document CS to view CS
x = x - 100
y = y - 100

// Translate view CS center to 0,0
x = x - 250
y = y - 250

// Scale from view CS to document CS
var viewScale = 1 // always for Google
x = x * viewScale
y = y * viewScale

// Translate 0,0 in world CS to world CS center
x = x + 3495974
y = y + 6367308

// Convert from world CS to geodetic CS
var long = convertMercatorToWGS84Long(x)
var lat = convertMercatorToWGS84(y)

// answer is:
// long = -104.99
// lat = 39.74
  1. Laurent Jégou
    July 1, 2006

    Thanks for this very clear tutorial 🙂

    Reply
  2. Charlie
    July 1, 2006

    Thanks Laurent and Mateusz. And thanks for the bookmarks, those are some good references.

    Reply
  3. Kai
    July 26, 2008

    How do the convertMercatorToWGS84-functions work?

    Reply
  4. barry@pocket-link.com
    August 24, 2008

    Can you please explain some of these to me: If google zoom level = 16 how do you get to the scale of 1/5206.6?

    Also how to you calculate from -104.985 and 39.739 to x value of 3495974 and a y value of 6367308?

    Thanks

    Reply
  5. September 2, 2008

    hey ))
    its very interesting article.
    Good post.
    realy gj

    thank you 😉

    Reply
  6. david
    October 16, 2008

    The elementUnitDistance assignment isn’t very clear. It doesn’t explicated say what scale is assigned to. I assume 1?

    Reply

Leave a Reply

Your email address will not be published.

Top