Plotting Google Maps Lat and Long values as x and y Coordinates in an Arbitrary Element

Recently I had the occasion to revisit something I did for a client a few months back and thought it might make a swell blog post. Well, that and the fact that a major factor in starting this blog was to document solutions I'd come up with, and as a side benefit, to hopefully help anyone else who might be looking for solutions to similar problems.

In any case, the project involved plotting points on a custom map of the state of Virginia. Something like so:

final map

In addition, the map points needed to have some sort of popup window which would contain any information that related to the plotted points, e.g.:

final map with popovers

So when I was asked to revisit it, my first thought was "Hm. How did I do that again?!"

Although I initially looked into stuff like custom tiles with google maps, leafletjs, mapbox, and the like, I ultimately (since the client had already seen a designer's version of the map and had expectations about how it would look and work) decided to go with a custom container with a background of the state with individual points placed within it. This would give me total control over how it looked and worked.

The content management system that we've developed over the years contains a geocoding module where our clients can enter a series of addresses and have their latitude and longitude set automatically. In addition, they can enter a title, description, associate images, etc. for each. So the problem became one of how to take those lat/long coordinates, and convert them to xy coordinates in an arbitrary element like a div or whatever (and that would have an arbitrary width and height).

So as it turns out that the first step was to get a map of Virginia, which I found at wikimedia commons

In taking a look at the svg file though, I didn't want the thick outer border:

Border before

So I opened svg and poked around until I found the node that controlled the stroke width:

SVG before

And thinned it out:

SVG after

That's better:

Border after

Map firmly in hand, I converted it to a png and then looked around on google until I found some code (courtesy of the almost always awesomely amazing stackoverflow) that seemed to do pretty much what I wanted: Mercator longitude and latitude calculations to x and y on a cropped map (of the UK). Update: The getXY method now uses code cribbed from this post on stackoverflow, which I've found to be a little more accurate (within the caveats noted at the post).

Looking at the code, I figured the first thing that I needed was the bounding coordinates of the state of Virginia. A quick trip to http://maps.googleapis.com/maps/api/geocode/json?address=Virginia,%20USA&sensor=false got me exactly what I needed:

google maps results for virginia

Next up, I translated from the stack overflow post to javascript. And since we've been using boostrap on the front end of our sites, I rolled a quick jquery plugin to grab all selected elements, calculate their width and height, make an ajax request to the server to grab all the plotted points for the map, and place them on the map.

Note that the jquery plugin contains a few extra options like the type of event to plot on the map or the number of events to show. It also will throw in an image column with an img tag if image data comes back with the data. All this is set up in our content management system, and you can safely ignore it. I left it in just in case you're looking to do something similar.

Lastly, I needed to initialize the plugin on the page and point it at a div with an id of "map" and a background of the image of Virginia:

<script>  
    $(function() {
        $('#map').map({
            'datasource': '/path/to/datasource?method=getMapPoints',
            'bounds': {
                'lat': {
                    'from': 39.4660120,
                    'to': 36.54075890
                },
                'lng': {
                    'from': -83.6754150,
                    'to': -75.24215719999999
                }
            },
            'numEvents': 10,
            'typeEvents': 'custom-map-points',
            'doLinks': false,
            'markerOffset': {
                'x': 7,
                'y': 7
            }
        });
    });
</script>  

Lastly, the map plugin uses a subclassed version of the bootstrap 2.3.x popover class for the popup window for each point, which basically just includes a close button: