Creative Google maps I: Taking control of the location

Front-end development
Back-end development

We recently finished a very interesting website which uses Google Maps extensively. In fact, the whole thing is visually wrapped in a map, with all the content represented by markers on the map. Here we will share some of the things we learned while developing the mapping part of this site. To help readers avoid fatigue, we will split it up into a couple of articles.

Written by Hannes Lilljequist

In this project we needed to create an interactive map interface which shows the site content as clickable markers on the map rather than the usual lists of posts. In addition to that, the page should never reload, but rather load and save the content through javascript calls to a back end service. This seemed like it could potentially involve a large amount of coding, and we had little time available to finish the site. But by using a set of standard Drupal tools – and a few not-so-standard ones – we managed to develop the site quite rapidly. We ended up writing a fair amount of custom code, but the base functionality is all provided by Drupal modules.

Google maps in Drupal

There are several mapping solutions available for Drupal, such as Gmap, Location, Geo and Mapstraction, only to name a few. The old tried and tested solution is the combination of Location and Gmap, which both use the Google Maps API, so we started out investigating those since we needed a quick and stable solution.

The Location module provides a way to assign a location to a node, which is the foundation of what we needed to do. It has the ability to let users pick a location on a Google map, but in this project we used Location's ability to collect a location name and zip code, and assume a location within Sweden.

Inspecting and validating a location

The Google Maps API, which Location uses to look up latitudes and longitudes, has its limitations however. We found that the lookups didn't always return a reliable result, especially with locations outside of densely populated areas. To handle this, we added our own node validation which checks the returned coordinates and makes sure it is actually a location within Sweden. This is called reverse geocoding, and can be done through a different web service: Geonames.

The following php snippet shows an example of how you can find place names nearby a specified location.

<?php
$url
= "http://ws.geonames.org/findNearbyPlaceNameJSON?style=short&radius=30&maxRows=30&lat=$lat&lng=$lng";
$res = @file_get_contents($url);
$res = json_decode($res);
?>

This is just one example of how Geonames can be used. Take a look at their webservices overview page to find out more.

Controlling the distribution of markers

Another interesting feature of the site is that markers that are placed in the same area are randomly repositioned at a distance from each other. (The client specifically didn't want the users to choose an exact location for privacy reasons.) This is achieved by a somewhat complicated piece of code that uses a couple of functions provided by Location; one for translating between distances in kilometers and changes in latitude and longitude, and one for finding markers near a certain location. Below is a somewhat simplified version of our function.

<?php
/**
* Take a position and check other nodes' positions to make sure it's not
* on the exact same coordinates as one of the nodes.
*
* @param $lat
*   Latitude, floating point value.
* @param $long
*   Longitude, floating point value.
* @param $distance
*   Minimum km distance between dots, as a floating point value.
* @return
*   Array containing the new latitude and longitude.
*/
function mymodule_position_recalculate($lat, $long, $distance = 1) {

 
$return = false;

 
// Get the km scale of the latitude and longitude axis on this location.
 
$lat_scale = location_distance_between(array('lat' => $lat - 0.5, 'lon' => $long), array('lat' => $lat + 0.5, 'lon' => $long), 'km');
 
$long_scale = location_distance_between(array('lat' => $lat, 'lon' => $long - 0.5), array('lat' => $lat, 'lon' => $long + 0.5), 'km');
 
 
// Increase radius (in km) until a free spot is found.
 
for ($radius = mt_rand(50 * $distance, 90 * $distance) / 100; $radius < 10 * $distance; $radius += mt_rand(85 * $distance, 110 * $distance) / 100) {
   
// Look for this maximum number of locations on this radius.
   
$max = 2 * 3.1415 * $radius;
    for (
$n = 0; $n < $max; $n++) {
     
// Random movement on X axis.
     
$delta_x = mt_rand(1, $radius * 100) / 100;
     
// Calculate Y movement based on X.
     
$delta_y = sqrt(pow($radius, 2) - pow($delta_x, 2));
     
// Movements can be negative as well as positive.
     
$delta_x = (rand(0, 1)) ? $delta_x : -$delta_x;
     
$delta_y = (rand(0, 1)) ? $delta_y : -$delta_y;
     
// Calculate new coordinates.
     
$new_lat = $lat + ($delta_x / $lat_scale['scalar']);
     
$new_long = $long + ($delta_y / $long_scale['scalar']);
     
// Get position range for the surrounding area.
     
$latrange = earth_latitude_range($new_long, $new_lat, $distance * 1000);
     
$lonrange = earth_longitude_range($new_long, $new_lat, $distance * 1000);
     
// Look for existing markers within the specified distance.
     
$query = "SELECT lid FROM {location} WHERE ";
     
$query .= "latitude > %f && latitude < %f ";
     
$query .= "AND longitude > %f && longitude < %f ";
     
$query .= "AND " . earth_distance_sql($new_long, $new_lat, '') .' < %f';
     
$res = db_query($query, $latrange[0], $latrange[1], $lonrange[0], $lonrange[1], $distance * 1000);
      if (!
db_result($res)) {
       
// This is a free spot on the map.
        // (Do any validation you might need here.)
       
$return = array($new_lat, $new_long);
        break
2;
      }
    }
  }

  return
$return;
}
?>

The result is a nice pattern of evenly but somewhat randomly positioned markers around popular locations, such as larger cities (see the image at the top!).

Conclusion

In this article we have shown some of the techniques we've used to gather and save the right location data for our nodes. In the next post, we'll talk about how to load a very large number of markers without making things too slow.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Each email address will be obfuscated in a human readable fashion or (if JavaScript is enabled) replaced with a spamproof clickable link.

More information about formatting options