Centering a map when you don't know where to center it

Wow, that’s a pretty bad title, but hopefully things will be a bit clearer in a minute. A reader, Matt, wrote in with a few interesting cfmap problems. First - how would you center a map “best” when you aren’t sure where your map items will be placed? The second problem is how best to zoom the map. By best we mean getting in as close as possible and still seeing all the map items. What follows is my solution, but the road to this solution was a bit rough. As always, if you have suggestions please share them.

First - let's look at how I tried to solve this with a basic cfmap. I created 4 long/lat points (in the west) and centered the map in St. Louis. Here is the code:

<cfset data = [ {latitude="38.685510",longitude="-122.167969"}, {latitude="41.705729",longitude="-119.794922"}, {latitude="39.027719",longitude="-116.894531"}, {latitude="35.173808",longitude="-117.685547"}]> <cfmap width="500" height="500" centerAddress="St. Louis, MO" name="mainMap" showcentermarker="false"> <cfloop index="mi" array="#data#"> <cfmapitem longitude="#mi.longitude#" latitude="#mi.latitude#" > </cfloop> </cfmap>

And it renders like so:

Ok, so let's focus on the centering first - because this is where we will run into our first big problem. The Google Maps API provides a cool little object called a bounds. You can think of a bounds object as a box that contains all your data. So given the fact that we know our points, we can make a bounds object using a little bit of JavaScript. I began by adding this:

<cfset ajaxOnLoad("init")>

And then added this JavaScript function:

<script> function init() { console.log('ready'); var gbounds = new GLatLngBounds(); <cfloop index="mi" array="#data#"> <cfoutput> var p = new GLatLng(#mi.latitude#,#mi.longitude#); gbounds.extend(p); </cfoutput> </cfloop> center = gbounds.getCenter(); var centerOb = {longitude:center.lng(), latitude:center.lat()}; ColdFusion.Map.setCenter("mainMap", centerOb); } </script>

Ok - so you can see where I create by bounds object (documented here) and loop over my data. For each piece of data I create a GLatLng object and extend my bounds. Cool, right? I can then get the center, create a new structure from that, and use it in the API ColdFusion provides. Woot. Except - it doesn't do anything. I'd post a screen shot but it is the exact same as the one above. I'm not quite sure why it fails - but it seems as if the map created by ColdFusion "locks" onto the center address and doesn't let you move away from it.

So I gave up. I admit it. But I thought - why not take a look and see how hard it would be to make the map the "native" way. I knew that Google was up to v3, one version ahead of the API used in ColdFusion, and with that I headed over to the documentation. I began with my data again.

<cfset data = [ {latitude="38.685510",longitude="-122.167969"}, {latitude="41.705729",longitude="-119.794922"}, {latitude="39.027719",longitude="-116.894531"}, {latitude="35.173808",longitude="-117.685547"}]>

And then loaded my libraries. Obviously only the Google Map library was required - I brought in jQuery just because.

<html> <head> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script> $(document).ready(function() {

Ok, so let's get our bounds object again and populate it.

var gbounds = new google.maps.LatLngBounds();

And then loop over our data and extend it again.

<cfloop index="mi" array="#data#"> <cfoutput> //make the point var point = new google.maps.LatLng(#mi.latitude#,#mi.longitude#); gbounds.extend(point); </cfoutput> </cfloop>

Creating a map manually means pointing to a div (a blank div) and passing in options. These options match up to what you would have used with cfmap - so things like zoom, map type, and the center.

var options = { zoom:3, mapTypeId:google.maps.MapTypeId.ROADMAP, center:gbounds.getCenter() }; var map = new google.maps.Map(document.getElementById('mainMap'), options);

The result? Looks right:

Ok, but what about those markers? That's pretty simple too. Marker objects can be as simple as a longitude and latitude. Google supports much more complex markers of course but for now, we can take the base default. I'm going to start by modifying my initial loop to also create the markers:

$(document).ready(function() { var gbounds = new google.maps.LatLngBounds(); var markers = []; <cfloop index="mi" array="#data#"> <cfoutput> //make the point var point = new google.maps.LatLng(#mi.latitude#,#mi.longitude#); gbounds.extend(point); //make the marker var marker = new google.maps.Marker({position: point}); markers[markers.length] = marker; </cfoutput> </cfloop>

And then I added the markers. Now remember I don't have a map yet in the loop there so I need to wait till after that map is done:

for(var i=0; i<markers.length; i++) markers[i].setMap(map);

And the result...

Woot! I'd call that just about right. It seems centered right. We've got our markers. The only thing not done yet is the "best" zoom. I can bet this is going to involve quite a bit of math and other work. Wrong. Apparently you can just tell the map to fit to the bounds. Geeze, that's it?

map.fitBounds(gbounds);

And the result:

All in all - not so much work. I spent more time trying to get the cfmap version working then the v3 "native" one. Here is the entire template. Feel free to rip it apart and make it better.

<cfset data = [ {latitude="38.685510",longitude="-122.167969"}, {latitude="41.705729",longitude="-119.794922"}, {latitude="39.027719",longitude="-116.894531"}, {latitude="35.173808",longitude="-117.685547"}]> <html> <head> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script> $(document).ready(function() { var gbounds = new google.maps.LatLngBounds(); var markers = []; <cfloop index="mi" array="#data#"> <cfoutput> //make the point var point = new google.maps.LatLng(#mi.latitude#,#mi.longitude#); gbounds.extend(point); //make the marker var marker = new google.maps.Marker({position: point}); markers[markers.length] = marker; </cfoutput> </cfloop> var options = { zoom:3, mapTypeId:google.maps.MapTypeId.ROADMAP, center:gbounds.getCenter() }; var map = new google.maps.Map(document.getElementById('mainMap'), options); map.fitBounds(gbounds); for(var i=0; i<markers.length; i++) markers[i].setMap(map); }); </script> <style> #mainMap { width: 400px; height: 400px; } </style> </head> <body> <div id="mainMap"></div> </body> </html>

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Comments