Information and advice for healthcare consumers.

Web/Tech Mobile, social & web news for clinics

How to make Google static maps interactive (Part 2)

Following on from his post last week, Tim is back to with a little bit more explanation and some code to show you how to implement his method of making Google static maps interactive.

Add Marker divs to your static maps (Part 2)

Lets start by handling a set of points. We can easily loop over them to find the max and min latitudes and longitudes of the set.

Getting the centre
To find the longitude centre is easy: sum the max and min longitudes and divide by 2. The centre latitude is harder – put the max and min latitudes through our Mercator equation for latitude that we used last time:

y = ln( (1+sinO)/(1-sinO)) / 2

Add the results together and divide by 2 – then invert the function, which is Math.Atan(Math.Sinh( RESULT )) – where RESULT is (ln((1+sin(radians(maxLatitude))/(1-radians(maxLatitude))) / 2 + ln((1+sin(radians(minLatitude))/(1- adians(minLatitude)))) /2

We’ll have been working in radians so multiply this result by 180.00 / Math.PI to get the centre Latitude in degrees.

Calculate the zoom level
To find the zoom level necessary to contain our set of points we first calculate the span of degrees of the latitudes and longitudes. Subtract the minimums from the maximums to give us the largest spans by degrees for our latitude and longitude. If we zoom to a set level on a Google map: at any given zoom level anywhere on the earth the difference
between the maximum longitude and the minimum longitude remains the same. – If we return to our discussion of zoom levels from last week and work backwards… we can see that once we know a longitude span we can use it to find an appropriate zoom level. The equation ends up being:

log base2 of (180o / longitudeSpano)

That all required that the longitude was the span we were interested in… Now – if we look at a square map on a page, the central x-axis span does not equal the y-axis span (except at the equator) – Longitude is greater. This means that even if the longitude span is greater than the latitude span, if we set our zoom by longitude all our points’ latitude
values do not necessarily fit inside our returned map. Luckily, there is a consistent relationship between these spans that depends on latitude – as we get nearer to the equator our latitude spans get closer and closer to our longitude spans (on our handy square map), and by dividing our latitude span by the cos of the centre latitude we’ll get the value of our longitude span. This means we can use the zoom level calculation above – using max( longitude span , adjusted latitude span ) as our value.

As if that all wasn’t enough, the old problem crops up: that the map only fits the world perfectly at zoom level 0 if it is 256 pixels wide. To account for this before we take our log, divide by 256 and multiply by the width of the map in pixels (at 256pixels wide we get back to where we started).

Well that about wraps it up! As promised, it is possible, maybe even advisable to ignore my convoluted explanation and just use some preprepared code snippets instead!

MAP_SIZE = 256;

private double atanh(double rad)

{
return Math.Log(((1 + rad) / (1 – rad)), Math.E) / 2;
}
private double getZoom(double span)
{
double zoom = (180.00/span) * (MAP_SIZE/256.00);
zoom = Math.Log(zoom, 2);
return Math.Floor(zoom);
}
yourReturnType createDivs(double m_maxLatitude, double m_minLatitude, double m_maxLongitude , double m_minLongitude, List m_markerIList){
/**
* find our centre – we can reuse some of these variable later
*/
double atanhsinO = atanh(Math.Sin(m_maxLatitude * Math.PI / 180.00));
double atanhsinD = atanh(Math.Sin(m_minLatitude * Math.PI / 180.00));
double atanhCentre = (atanhsinD + atanhsinO) / 2;
double radianOfCentreLatitude = Math.Atan(Math.Sinh(atanhCentre));
double centreLatitude = radianOfCentreLatitude * 180.00 / Math.PI; //turn it to degrees
double centreLongitude = (m_maxLongitude + m_minLongitude) / 2;
// zoom is decided by the max span of longitude and an adjusted latitude span
// the relationship between the latitude span and the longitude span is /cos
double latitudeSpan = m_maxLatitude – m_minLatitude;
latitudeSpan = latitudeSpan / Math.Cos(radianOfCentreLatitude);
double longitudeSpan = m_maxLongitude – m_minLongitude;
double zoom = getZoom(Math.Max(longitudeSpan, latitudeSpan)) + 1;
/**
* create the x,y co-ordinates for the centre as they would appear on a map of the earth
*/
double power = Math.Pow(2.00, zoom);
double realWidth = 256.00 * power;
// ** result 1 – pixel size of a degree **
double oneDegree = realWidth / 360.00;
double radianLength = realWidth / (2.00 * Math.PI);
// ** result 2 ** the centre on our virtual map
double centreY = radianLength * atanhCentre;
/**
* now we go though the providers creating the x,y’s and adjusting them to the virtual frame of our
* map using our centreX,Y values
*/
for (IEnumerator enumerator = m_markerIList.GetEnumerator();
enumerator.MoveNext(); ) // for(Iterator<LaitutudeLongitude>
latitudeLongitudeIterator = etc.. for java heads
{
DataRow markerDetails= (DataRow)(enumerator.Current);
// LaitutudeLongitude markerDetails
= latitudeLongitudeIterator.next(); for java heads, you get the idea
double currentLatitude = double.Parse(markerDetails["Lat"].ToString());
double currentLongitude = double.Parse(markerDetails["Lng"].ToString());
double pixelLongitude = (currentLongitude – centreLongitude) * oneDegree;
double pixelLatitudeRadians = currentLatitude * Math.PI / 180.00;
double localAtanh = atanh(Math.Sin(pixelLatitudeRadians));
double realPixelLatitude = radianLength * localAtanh;
double pixelLatitude = centreY – realPixelLatitude; // convert from our virtual map to the displayed portion
pixelLongitude = pixelLongitude + (MAP_SIZE/2);
pixelLatitude = pixelLatitude + (MAP_SIZE/2);
int roleOverX = (int)(Math.Floor(pixelLongitude)) ;
int roleOverY = (int)(Math.Floor(pixelLatitude));
// now create whatever div you want with the given roleOverX and roleOverY so they overlay the map
// add them to a List or just concatenate a string in this loop and then return .
e.g -
String roleOverDiv = “<div name=’” + markerName + “‘
id=’id” + id + “‘ ” + ROLLOVER_STYLE_STRING + ” left:” + roleOverX +
“px; top:” + roleOverY + “px; ‘></div>”;
returnString += roleOverDiv
}
return returnString; //if using the example above
}
Enjoy.

4 Responses to “How to make Google static maps interactive (Part 2)”

  1. Ben Holland says:

    What language is your code example in? Your explanation is helpful but I’m not quite sure how to use your code snippets. I’m interested in making an atlas style map using a static Google map. Where columns are labeled as A,B,C and rows are 1,2,3. Then you could identify a marker as being in [A,3] for example. Any tips?

  2. Ben Holland says:

    Ah, sorry I see your code is more of a pseudo code language.

    Is it possible to use this method with non square maps? I am using a map that is 500×300 px. If I change the MAP_SIZE variable to 500 I get the wrong zoom level (off by one too big).

  3. Ben Holland says:

    Hi, I got it working.

    I found a related post (http://www.appelsiini.net/2008/6/clickable-markers-with-google-static-maps) which was able to fill in some of the gaps for me.

    Thanks again for the post!

Comment

[Read More Web/Tech Posts]