Note! Code shown is now included in Simple Static Maps PHP class. This blog entry is still good for understanding how it was done. Demo code can be considered deprecated.

Static map is one big image. Markers are embedded inside the image. You can not use traditional <a href=”#”> tags around separate markers. Binding onclick event to separate marker images wont work either. There are no separate markers. Just one large image.

With imagemaps you can specify arbitary areas inside an image which links to given url. Area can be circle, rectangle or polygon. Simple imagemap could look like following:

<map name="marker_map">
  <area shape="circle" coords="75,103,12" href="#">
  <area shape="circle" coords="122,105,12" href="#">
</map>

With imagemaps we can create clickable markers for Google Static Maps. We need to position an imagemap area over each marker. Problem is how to calculate x and y pixel coordinates for each marker.

For this tutorial lets start with with static map you see above. It is created with following PHP code.

require_once 'Net/URL.php';
require_once 'Google/Maps.php';

$map_width  = 512;
$map_height = 300;
$map_size   = $map_width . 'x' . $map_height;
$zoom       = 13;
$markers    = '58.378700,26.731110,green|58.368488,26.768908,green|
               58.379646,26.764090,green';

$api_key = trim(file_get_contents('api_key.txt'));

/* Build the URL's for Static Maps API. */
$url = new Net_URL('http://maps.google.com/staticmap');
$url->addQueryString('center',  $center);  
$url->addQueryString('zoom',    $zoom);
$url->addQueryString('markers', $markers);
$url->addQueryString('size',    $map_size);
$url->addQueryString('key',     $api_key);
$demo_map = $url->getUrl();
 

Calculating Pixel Coordinates for Markers

For imagemap we need to know where markers are located in static map image. To calculate pixel location of marker we need to know the following things:

  • Latitude and longitude of the center of the map.
  • Current zoom level.
  • Size of the static map in pixels.

Static Maps API can automatically calculate needed zoom level and center position of the map. However there is no way of receiving this information back. Instead we have to calculate center latitude and longitude ourselves.

Calculating Map Center

There are several ways of calculating geographic center of two or more points on earth surface. First is to calculate geographic midpoint in cartesian coordinates. Second is to find center of distance. Center of distance is point which has smallest possible distance from all given points. Both these methods give accurate results but require pretty complex calculations.

Easiest way to find map center is to use average latitude and longitude of all given markers. Result is close enough approximation for our needs.

/* Calculate average lat and lon of markers. */
$marker_array = explode('|', $markers);
foreach ($marker_array as $marker) {
    list($lat, $lon, $col) = explode(',', $marker); 
    $lat_sum += $lat;
    $lon_sum += $lon;
}
$lat_avg = $lat_sum / count($marker_array);
$lon_avg = $lon_sum / count($marker_array);

/* Set map to calculated center. */
$url->addQueryString('center',  $center);  

We also have to convert center latitude and longitude to pixel coordinates in world map. For this we use previously created Google_Maps PHP class.

/* Calculate center as pixel coordinates in world map. */
$center_x = Google_Maps::LonToX($lon_avg); 
$center_y = Google_Maps::LatToY($lat_avg);

While were at it lets also calculate center as pixel coordinates in map image. This is really easy. We only need to divide image width and height by two.

/* Calculate center as pixel coordinates in image. */
$center_offset_x = round(512 / 2);
$center_offset_y = round(300 / 2);

Now we know three things about demo map center. Latitude and longitude coordinates are 58.3756113333,26.7547026667. This equals to 308334961,160637460 in world map pixel coordinates. Which in turn equals 256,150 in image pixels coordinates.

To prove our point let’s add a blue marker in our calculated center of map.

$markers .= sprintf('|%s,%s,blue', $lat_avg, $lon_avg);
$url->addQueryString('markers', $markers);

$map_with_center = $url->getUrl();  

We still need to make markers clickable…

Calculating Marker Pixel Positions

For each marker we know their latitude and longitude. To find their location on map image following calculations have to be done.

  • Convert latitude and longitude to pixel x and y coordinates.
  • Calculate difference between above and map center x and y coordinates.
  • Convert above difference to match current zoom level.
  • Add above difference to center pixel coordinates in image.

PHP code below does all above. Note how shift right operator >> is used to convert the difference between center and marker pixel coordinates for current zoom. Maximum zoom is 21. Variable $zoom is the current zoom level. For zoom level 13 we would be doing delta >> (21-13) which equals delta >> 8 which equals delta / 2^8 which in turn equals delta / 256.

foreach ($marker_array as $marker) {
   list($lat, $lon, $col) = explode(',', $marker); 
   $target_y = Google_Maps::LatToY($lat); 
   $target_x = Google_Maps::LonToX($lon); 
   $delta_x  = ($target_x - $center_x) >> (21 - $zoom);
   $delta_y  = ($target_y - $center_y) >> (21 - $zoom);
   $marker_x = $center_offset_x + $delta_x;
   $marker_y = $center_offset_y + $delta_y;
}
 

Generate Imagemap for Markers

Now we know marker pixel locations on static map. This enables us to generate imagemap for them. We will create clickable circles with radius of 12 pixels. Lets modify code we just wrote. Note that calculated marker location points to markers foot. We want the round head of marker be clickable. That is done by adjusting y coordinate with -20 pixels.

$imagemap = '<map name="marker_map">';
foreach ($marker_array as $marker) {
  ...
  $marker_y = $center_offset_y + $delta_y - 20;   
  $imagemap .= sprintf('<area shape="circle" coords="%d,%d,12" href="#">',
                        $marker_x, $marker_y);
}
$imagemap .= '</map>';

For demo’s sake lets also add some jQuery code to notify us when marker is clicked.

$(function() {
    $('map area').bind('click', function() {
        alert('You clicked on marker.');
        return false;
    });
});

Click on green markers.

Conclusion

It takes a bit work but you can have clickable markers with static map. Yes I used JavaScript to indicate when marker was clicked. But that was not the point. Static maps are usually faster to load than full Google Map JavaScript API. You can also create gracefully degrading Google Map for those users with JavaScript disabled. Ability to click marker also gives you ability to open infobubble without JavaScript. That is left for the next part on series of blog posts about Google Maps.

You can find source code to the working demo from svn.

Related entries: Google Maps Without JavaScript Part 1, Google Maps Without JavaScript Part 2, Infowindows With Google Static Maps.


19 Responses to “Clickable Markers With Google Static Maps”

  1. Andreas Stephan says:

    Awesome tutorial! Thanks a lot. Looking forward to trying it out and to seeing more of these quality posts.

  2. Walter says:

    Does this work ? I clicked serveral types but didn’t make a difference.

  3. tommy says:

    How do create driving directions without using javascript? Thanks

  4. Robs says:

    It’ doesn’t work…

  5. Robs says:

    Uhm.., I’m sorry. Mea culpa… It really works Thanks a lot!

  6. Mika Tuupola says:

    Robs: You tried to click on the map on top of page?

  7. Mika Tuupola says:

    Tommy: Haven’t tried myself but there is a thread in Google Groups.

  8. matti says:

    moi,

    törmäsin tässä yhden projektin tiimoilta tähän artikkeliin ja heräsi sellainen kyssäri että mikä arvo on tuossa google_maps.php luokassa on tuo

    $offset = 268435456;

    - Sehän on siis vertailupiste eikö vaan? mistä se tulee 7 miten se on määritelty?

  9. fathi says:

    thanks alot but i tried to convert from lon/lat to x/y using your php code but converted the ode to C++, and it did not output the same result for example input long, lat -> 58.3756113333, 26.7547026667 your output is -> 308334961, 160637460 but my output is -> 355480854, 226977793

    can you help me in this

    Thanks,

  10. Mika Tuupola says:

    Matti: I answer in english so other readers understand the answer too.

    It is half of the earth circumference in pixels at zoom level 21.

    Try to visualize it by thinking of full map. Full map size is 536870912×536870912 pixels. Center of the map in pixel coordinates is 268435456,268435456 which in latitude and longitude would be 0,0.

  11. Mika Tuupola says:

    Fathi: Impossible to say without seeing any code (and even if I did, I do not speak C++). One thing which comes to mind though. I know atleast Perl has problems rightshifting >> negative values. Maybe this would be problem in your case too.

  12. fathi says:

    Mika, Thanks for reply there is no shifting when call the LongToX function

    the code is here

    PI = 3.14159265358979323846
    OFFSET = 268435456
    RADIUS = OFFSET / PI
    x = OFFSET + RADIUS *  aLongitude * PI / 180
    

    can you told me about the operation that shift do?

    Thanks

  13. fathi says:

    sorry function LongToX

    pi = 3.14159265358979323846   
    offset = 268435456     
    radius = offset / pi      
    x = offset + radius * Long * pi / 180
    
  14. Mika Tuupola says:

    Are you sure your PHP code is correct. If I print out the results I get almost the same as your C++ code. The difference can be explained with the difference os PI decimals.

    print Google_Maps::LontoX(58.3756113333) . "\n";
    print Google_Maps::LattoY(26.7547026667) . "\n";
    
    355491477
    227001520
    

    About right shift. Wikipedia has an good article on bitwise operations.

  15. fathi says:

    Mika, is this the result of your code

  16. fathi says:

    Mika, i get the php code from the php file in you article

  17. Mika Tuupola says:

    Fathi: What code exactly? Google_Maps class from SVN? That prints out the following:

    print Google_Maps::LontoX(58.3756113333) . "\n";
    print Google_Maps::LattoY(26.7547026667) . "\n";
    
    355491477
    227001520
    

    Can you show me your code which prints out something else. Have you modified the original class somehow?

  18. fathi says:

    Mika, can you send me the position of a marker at long/lat 58.368488, 26.768908

    at a map with center 58.3756113333, 26.7547026667 size 512×300 and zoom level 13

    to compare to my results

    Thanks

  19. Mika Tuupola says:

    Fathi: Give me example code I can cut and paste.

Leave a Reply



(will not be published)



(you can use Textile for formatting)