Places - a location information system using Drupal 7 and Google Maps API v3

"Places" is a Drupal7 site that shows all the locations in it either as a list or on a map.

The site can be viewed at www.netnik.com/places.

Each location has a page of its own with a number of fields - in this case the title, the address, the phone number, the Web site, the date, and a photo. There are also fields for latitude and longitude - these two are not displayed on the page, but are used to place markers on the map.

This site's purpose was to be a tour itinerary to make finding and contacting the venues easier. It is optimized for smartphones since it was used primarily on the road. Other fields could be added for other use cases - for instance if the locations listed were homes for sale, fields for price, bedrooms, baths, etc. could be added.

The first page offers the user the choice of viewing the locations as a list or on a map. Building the list is quite simple since it uses Drupal's default behavior, i.e. promoting an article to the front page. In other words, Drupal will typically create a headline and excerpt to display on the front page for each article created (the usual behavior for any blog). In this case I chose to have only the headline promoted and used CSS to style each headline as a button.

Creating the map is more complicated and uses a combination of PHP and javascript.

PHP is used to query the Drupal database to get the list of locations, and then javascript loops over each location and interacts with the Google Maps API.

Before getting into the PHP code, I need to mention Drupal's content types. Drupal 7 does not have any default content types. If you click on "Create Content" on a newly installed site, Drupal will instruct you to first create a content type. For Places, I made two content types: place and custom code. The place content type is the template for the pages that hold the information for each location and it has the fields mentioned above.

The custom code content type has three fields: title, code1, and code2. These last two fields are used to contain code that is executed when the page loads.

Another thing I need to mention about Drupal is text formats. When you create a field in a content type, you can specify what kind of input is allowed in that field. Drupal provides a few by default, filtered HTML and full HTML among them. These provide security for your site by preventing a visitor from entering and executing some javascript code, for instance. When you create a text format you are allowed to apply certain filters and features for the field such as automatically inserting <br /> tags where there are line breaks, etc.

For Places, I created a content type I called "As Is," which, as the name implies, allows me to enter text that remains "as is" (i.e unfiltered and nothing added.) This allows me to enter executable javascript on the page. (This is not a security risk since the As Is text format is set in permissions to be only available to the admin (i.e. me)).

Related to this is a core module called "PHP Filter" which I turned on for Places. This makes "PHP Input" available as a text format that can be used on a field. Again, for security reasons this is limited to the admin role.

So in my content type "custom code," on the page that shows the Google map with all the places shown, I have the two fields: "field_codephp" which I set to the input format of PHP code; and "field_code" which I set to the input format of as is. In the first I put the PHP code that queries the database for information about each location, and in the second I put the javascript that interacts with the Google Maps API to create the map.

The following PHP queries the MySQL database for all nodes with the content type of "place" and puts them in the variable (array) $result. It then loops over each item and formats the data in the JSON (JavaScript Object Notation) format that the Google Maps API requires.


<?php

$query = "SELECT 
node.nid AS nid,
node.title AS title
FROM {node} node 
WHERE (( (type IN  ('place')) ))";

$result = db_query($query);

print'{"markers": [';

foreach ($result as $record) {
   $record = (array) $record;
   $nid = $record['nid'];

   $thekeys = array("field_data_field_latitude",
   "field_data_field_longitude", "field_data_field_address",
   "field_data_field_information");

   $thevalues = array("field_latitude_value",
   "field_longitude_value", "field_address_value",
   "field_information_value");

   $thelables = array("latitude",
   "longitude","address" ,"information");

   $thecount = count($thekeys);

      for ($i=0; $i<$thecount; $i++)  {
        $fieldname = $thekeys[$i];
        $fieldval = $thevalues[$i];
        $fieldlabel = $thelables[$i];
   
        $query = "SELECT 
        $fieldval AS $fieldlabel
        FROM {$fieldname}
        WHERE (( (entity_id IN  ($nid)) ))";

       $more = db_query($query);

          foreach ($more as $unit) {
          $unit = (array) $unit;
          $u1 = key($unit);
          $u2 = $unit[$u1];
          $record[$u1] = $u2;
          }
	}

   $myjson =  json_encode($record);
   print_r($myjson);
   print ",";
   }
  print ']}';
?>

Then the following code calls the Google Maps API which returns the map with all the markers.


<div id="loadarea"><div id="mapdiv"></div></div>

<script src="http://maps.google.com/maps/api/js?sensor=false"
 type="text/javascript"></script>

<script type="text/javascript">

jQuery('div.field-name-field-codephp').hide();

function initialize() {
jQuery('#loadarea').hide();
jQuery('#footer').show();
// this first part gets the mean of the latitudes
// and longitudes to set as the map center
 var centerdata = jQuery('div.field-name-field-codephp').text();
 centerdata = centerdata.replace(/},]/,"}]");
 centerdata = jQuery.parseJSON(centerdata);
 var lats = new Array();
 var lons = new Array();

jQuery.each(centerdata.markers, function(i,markers) {
    var mylati = centerdata.markers[i].latitude;
    var mylong = centerdata.markers[i].longitude;
    lats.push(parseFloat(mylati));
    lons.push(parseFloat(mylong));
});

var sum = 0;
  for (i=0; i<lats.length; i++)
    {
    sum = sum + lats[i];
    }
var meanlat = sum/(lats.length);
meanlat = meanlat.toFixed(5);

var sum = 0;
  for (i=0; i<lons.length; i++)
    {
    sum = sum +lons[i];
    }
var meanlon = sum/(lons.length);
meanlon = meanlon.toFixed(5);


// Set up the map:
var myLatlng = new google.maps.LatLng(meanlat,meanlon);
  var myOptions = {
     zoom: 24,
     center: myLatlng,
     mapTypeControl: true,
     mapTypeControlOptions: {style: google.maps.MapTypeControlStyle.DROPDOWN_MENU},
     navigationControl: true,
     navigationControlOptions: {style: google.maps.NavigationControlStyle.SMALL},
     mapTypeId: google.maps.MapTypeId.ROADMAP 
     };

  var map = new google.maps.Map(document.getElementById("mapdiv"), myOptions);

  var bounds = new google.maps.LatLngBounds();
 // var data = new Object();
 var data = jQuery('div.field-name-field-codephp').text();
 data = data.replace(/},]/,"}]");
 data = jQuery.parseJSON(data);

jQuery.each(data.markers, function(i,markers) {
              var mylati = data.markers[i].latitude;
              var mylong = data.markers[i].longitude;
              var mytitle = data.markers[i].title;
              var myaddress = data.markers[i].address;
              var myhtml = data.markers[i].information;
if (myhtml) {
var myhtml = (myhtml.substr(0,30)) + "...";;
              var myid = data.markers[i].nid;
}

var mycontent = "<div class='infowin'><h3>" +
 mytitle + "</h3>" + "<h4>" + myaddress + 
"</h4>" + myhtml + "<p><a href=" + myid + 
">More info</a></p></div>";

var myLatlng = new google.maps.LatLng(parseFloat(mylati),parseFloat(mylong));

var infowindow = new google.maps.InfoWindow({
        content: mycontent
    });

var marker = new google.maps.Marker({
        position: myLatlng,
        map: map,
        title: mytitle
    });

    google.maps.event.addListener(marker, 'click', function() {
      infowindow.open(map,marker);
    });
	
	bounds.extend(myLatlng);
   });

map.fitBounds(bounds);

jQuery('#footer').hide();
jQuery('#loadarea').css('min-height','320px');
jQuery('#loadarea').slideDown();
}
initialize();

</script>