Wednesday, October 07, 2009

Circle overlay on Google Map

This is my first "Zoom around Town"-related technical post. This first post looks at the use of GGroundOverlay for rendering circles on Google Maps.

To represent the current search areas in Zoom around Town, I decided to overlay two radar-style circles, on the start point map and on the end point map. These circles have to increase and decrease in diameter depending on the user's range selections. See this page in Zoom around Town for a demonstration. When you change the values of the +/- distance dropdowns, and update the maps, you should see the two circles change in size. The radius of the circle corresponds to the selected +/- value.

While the Google Map API (v2) provides GPolyline and GPolygon for drawing lines and polygons on Google Map, it does not provide an overlay class for drawing circles.

One solution is to create a multi-point GPolygon to approximate a circle. Check out this article for an explanation on how this can be done.

A better (in my opinion) solution is to instead use a GGroundOverlay. GGroundOverlay provides a means to overlay an image on top of the map. The image will be scaled automatically as the user zooms the map, so that the image will always cover the same geography area on the map. So, to create a radar-style circle, one only has to find a transparent circle png file, locate the point on the map where the circle should be centred upon, and specify the southwest corner coordinate and the northeast corner coordinate of the imaginery square that bounds this circle (i.e. the GLatLngBounds). Sounds simple, does'nt it? Well almost, to work out the GLatLngBounds coordinates, we need a bit of good old trigonometry.

Pythagoras told us that, for a 2km-radius circle, the four corners of this bounding square will be sqrt(2*2^2) km from the centre of the circle. So, to draw a 2km-radius circle centred upon a specific point, we need the southwest corner coordinate, which is sqrt(2*2^2) at 225 degrees from the original point and the northeast corner coordinate, which is the same distance at 45 degrees from the original point.

Coordinate of a destination point given distance and bearing from a start point

Unfortunately, as the globe is a sphere, working out a destination point coordinate given the distance and the bearing from a start point is no straightforward maths. Fortunately, the good people at Movable Type Ltd. (not to be confused with published this informative page. So based on the formula and the Javascript given on the page, I created the Javascript function:
function getDestLatLng(latLng, bearing, distance) {
var lat1 = latLng.latRadians();
var lng1 = latLng.lngRadians();
var brng = bearing*Math.PI/180;
var dDivR = distance/EARTH_RADIUS;
var lat2 = Math.asin( Math.sin(lat1)*Math.cos(dDivR) + Math.cos(lat1)*Math.sin(dDivR)*Math.cos(brng) );
var lng2 = lng1 + Math.atan2(Math.sin(brng)*Math.sin(dDivR)*Math.cos(lat1), Math.cos(dDivR)-Math.sin(lat1)*Math.sin(lat2));
return new GLatLng(lat2/ Math.PI * 180, lng2/ Math.PI * 180);

Now, the rest is easy:
var EARTH_RADIUS = 6378.137; //in kilometres

function drawCircle(map, centrePt, rangeValue) {
var boundaries = getBoundaries(centrePt, rangeValue);
var circle = new GGroundOverlay("/images/map_overlays/circle.png", boundaries);

function getBoundaries(centrePt, radius) {
var hypotenuse = Math.sqrt(2 * radius * radius);
var sw = getDestLatLng(centrePt, 225, hypotenuse);
var ne = getDestLatLng(centrePt, 45, hypotenuse);
return new GLatLngBounds(sw, ne);


JF said...

Any ideas for creating multiple overlays (in the neighborhood of 200) without bogging it down? This method seems to be somewhat inefficient..

See Wah Cheng said...

umm... I suppose if you know all the points already, you can do the calculation beforehand (potentially using a server-side script) and store the boundary points in a database?

Otherwise just go for the multi-point GPolygon approach, but then I have not done any performance testing with GPolygon. Not sure about the performance implications of creating 200 GPolygon though...

selloutsatirist said...

Thanks for the idea. I was able to overlay a delivery radius on a Google Map with just a few adjustments. My client is quite happy with the initial result.

Now I have to figure out how to shoehorn that into his wordpress blog. Wish me luck!

See Wah Cheng said...

Cool I am glad you found it useful! Not familiar with wordpress I am afraid...

Camilo said...
This comment has been removed by the author.
Camilo said...

Thanks for your post! It was very usefull to me. I reuse your JavaScript code to do a Java class in order to use it with GWT. If someone is interested here is a jar. I attached the sources inside.

I try to do it as generic and parametrable as i could.


ps: JavaDoc and comments are in French =P i'll try to translate it if someone ever uses it =P