Zoekresultaten van adressen in postcodegebied inclusief een Google Map

Opmerking:

Deze blogpost geldt voor de Google API versie 2, die binnenkort vervangen wordt door versie 3.

 

Wat ik wil maken: Een overzicht van meerdere adressen binnen een opgegeven postcodegebied inclusief een Google Maps kaartje.
Bijvoorbeeld voor modevakschool.net: zoek adressen voor naailes in de buurt van postcodegebied 10 (dit is Amsterdam en omstreken) en geef de resultaten weer in een lijst en in een Google kaartje. Als je puur op postcode 10 zoekt mis je adressen die misschien vlak in de buurt zijn, maar in net een ander postcodegebied liggen.
In principe gaat het maken van het Google kaartje hetzelfde als voor een enkel adres, alleen laad  je nu de verschillende adressen in via een zogenaamd KML-bestand. Onderstaande code bouwt verder op de code voor een enkel adres. Een werkend voorbeeld:  zoekactie op naailes in Tilburg.

Stappenplan

1. Bepalen geo-coördinaten van een postcodegebied

  • De bezoeker geeft de eerste twee cijfers van de postcode in Nederland op (bijvoorbeeld 50 = postcodegebied)
  • In de MySQL database staan per postcodegebied de geo-coordinaten. Hiervoor heb ik de geo-coordinaten van de grootste stad in dit gebied genomen. Voor bijvoorbeeld postcodegebied 50 is dat Tilburg.
  • De coordinaten zijn te verkrijgen via de URL

    http://maps.google.com/maps/geo?q=Tilburg,%20Nederland&output=xml.

    Dit heeft voor Tilburg als resultaat latitude = 5.0906247, longitude= 51.5552409,0

2. Google Maps kaartje

  • De zo verkregen latitude en longitude van het postcodegebied bepalen het midden van het Google kaartje.
  • Alle beschikbare adressen staan in een KML-bestand dat door JavaScript wordt uitgelezen om het kaartje te maken. Bij de zoekresultaten komt het Google kaartje met alle beschikbare adressen, maar gecentreerd in gewenste het postcodegebied. De bezoeker kan nu zelf uit- en inzoomen om meer of minder adressen te bekijken.
  • Elk adres op de kaart heeft een marker, bij aanklikken komt er een tekstballonnetje tevoorschijn met naam en eventueel uitgebreide adresgegevens en bijvoorbeeld een link naar de website.

3. Uitvoer lijst met zoekresultaten

  • Voor de zoekactie wordt als centrum de geo-coördinaten van de grootste stad binnen een postcodegebied gebruikt. Hier omheen wordt een gebied (box) berekend.
  • Uit de database wordt een selectie gemaakt van adressen die in deze box vallen
  • De adressen worden in de uitvoer getoond.

Uitvoering

1. Geo-coördinaten definiëren

Database: In de MySQL database zijn twee tabellen aangemaakt: prefix_geo_plaats met de coördinaten per postcodegebied en prefix_geo met de coördinaten van alle beschikbare adressen.

CREATE TABLE IF NOT EXISTS `wp_geo_plaats` (
`geo_plaats_id` int(11) NOT NULL auto_increment,
`geo_plaats_korte_postcode` int(11) NOT NULL,
`geo_plaats_naam` varchar(255) NOT NULL,
`geo_plaats_land` varchar(255) NOT NULL default 'NL',
`geo_plaats_latitude` varchar(255) NOT NULL,
`geo_plaats_longitude` varchar(255) NOT NULL,
PRIMARY KEY  (`geo_plaats_id`)
) ENGINE=MyISAM ;

Voor de copy/pasters onder u hierbij: een al ingevulde prefix_geo_plaats voor Nederland.

CREATE TABLE IF NOT EXISTS `wp_geo` (
`geo_id` int(11) NOT NULL auto_increment,
`post_id` int(11) NOT NULL,
`geo_latitude` varchar(255) NOT NULL,
`geo_longitude` varchar(255) NOT NULL,PRIMARY KEY  (`geo_id`)
) ENGINE=MyISAM;

In wp_geo staan alle geo-coördinaten van de adressen waarop de bezoeker kan zoeken. Voor modevakschool.net zijn dit er meer dan 800 adressen. wp-geo wordt daar gevuld en bijgehouden via de custom fields in WordPress. Het post_id verwijst naar de pagina met alle gegevens over het adres (in dit geval de modevakschool).
In WordPress worden, bij het bewaren/updaten van een post met een adres, de gegevens van wp_geo bijgewerkt.

2. Tonen Google Maps kaartje

Hiervoor heb je nodig: een KML-bestand met de info van de beschikbare adressen, een JavaScript om het kaartje te maken en een div in de html om het kaartje in te zetten.
KML-bestand met alle adressen: In het KML-bestand staan onder meer de title, delatitude en longitude, en de inhoud van het tekst-ballonnetje voor op de Google kaart. Ik heb ervoor gekozen om alle adressen per tag in het KML-bestand te zetten. Als de bezoeker in een breder gebied wil kijken, kan ze gewoon het kaartje uitzoemen.

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>Modevakscholen in Nederland</name>
  <description>op de website modevakschool.net</description>
    <Placemark>
      <name><![CDATA[Modevakschool Avanti - Tilburg]]></name>
      <description><![CDATA[
                  Modevakschool Avanti - Tilburg <br />
                  Diepenveenstraat 33 <br />
                  5043 JL Tilburg  <br />
                  Nederland
                   ]]></description>
      <Style><IconStyle><Icon><href>http://www.modevakschool.net/img/icons/mapicon_paars.png</href></Icon></IconStyle></Style>
      <Point>
      <extrude>1</extrude>
        <altitudeMode>relativeToGround</altitudeMode>
        <coordinates>51.574717,5.016211,0</coordinates>
      </Point>
    </Placemark>
       … herhaal placemark voor elk adres ….
   </Document>
 </kml>

Dit bestand kun je automatisch laten updaten elke keer als er een post in WordPress wordt toegevoegd of gewijzigd. Je kunt bijvoorbeeld ook per tag of categorie een KML-bestand maken, zodat de bezoeker een voor-selectie kan maken (bijvoorbeeld alleen de adressen voor naailes, of fournituren).

De JavaScript: Toegevoegd aan de head-sectie van de HTML.De longitude, latitude en naam van het KML-bestand zijn variabelen die via PHP worden ingevuld in het JavaScript.  De JavaScript wordt daarom in functions.php geschreven en via  een add_action bij de head-sectie gezet. Zoom bepaald in hoeverre het kaartje is uitgezoomd (de grootte van het getoonde gebied).

<script type="text/javascript">
function initialize() {
  var myLatlng = new google.maps.LatLng(51.5552409,5.0906247);
    var myOptions = {
      zoom: 11,
      center: myLatlng,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    }
    var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
    var ctaLayer = new google.maps.KmlLayer('http://www.modevakschool.net/label/label-tag-naailes.kml', {preserveViewport:true});
    ctaLayer.setMap(map);
}
function loadScript() {
   var script = document.createElement("script");
   script.type = "text/javascript";
   script.src = "http://maps.google.com/maps/api/js?sensor=false&callback=initialize";
   document.body.appendChild(script);
 }
 window.onload = loadScript;

HTML template-bestand: Voeg voor het weergeven van het kaartje de div met id div_map toe aan de HTML.

<div id="div_map" style="width: 500px; height: 400px"></div>

3. Uitvoer lijst met zoekresultaten

Op de voorpagina van modevakschool.net staat het form voor de selectie van de postcode en het soort uitvoer.

      <form id="stap123_form" action="/stap123" method="get">
      <div id="stap1">
        <h3>Stap1<br /><span>Wat zoek je?</span></h3>
        <input type="radio" name="trefwoord" value="naailes-opleiding" checked />Naailes / mode-opleiding<br />
        <input type="radio" name="trefwoord" value="stoffen-fournituren" />Stoffen / fournituren<br />
        <input type="radio" name="trefwoord" value="naaipatronen" />Naaipatronen<br />
        <input type="radio" name="trefwoord" value="naaimachine" />Naaimachine<br />
      </div>
      <div id="stap2">
        <h3>Stap2<br /><span>In welke regio?</span></h3>
        <p>Vul de eerste<br /><strong>2 cijfers</strong><br />van de postcode in:</p>
      <input type="text" maxlength="2" name="category_name" >
      </div>
      <div id="stap3">
        <h3>Stap3<br /><span>Zoek en vind!</span></h3>
         <input type="submit" name="submit" value="Zoek!" />
      </div>
      </form>

Het postcodegebied (category_name) en het soort resultaat (trefwoord) worden verstuurd als $_GET variabele. Voor de uitvoer heb ik een template-bestand gemaakt (stap123.php) waarin alle code staat of wordt aangeroepen voor de lijst adressen en het kaartje.

De PHP-code

Voor de admin: Voeg toe aan functions.php

// Code by Rian Rietveld @RRWD web development
// http://www.rrwd.nl
// July-August 2010

add_action( 'save_post', 'mvs_save_post' );

function mvs_save_post($post_id) {
  global $wpdb;
  global $table_prefix;
  $coordinatenArray= array();
  unset($my_custom_field);
  unset($geo_id);

  // get addresss form custom field
  $my_custom_field =  get_post_meta($post_id, 'address', true);

  // if found: add or update
  if ($my_custom_field !="") {

  // convert address into geo coordinates
  $coordinatenArray = mvs_get_coordinates_form_address($my_custom_field);
  $geo_latitude = $coordinatenArray[0];
  $geo_longitude = $coordinatenArray[1];

  // find record for this post in database
  $geo_id = $wpdb->get_var($wpdb->prepare("SELECT geo_id FROM ".$table_prefix."geo WHERE post_id='$post_id'"));

  if($geo_id) {
    // update geo-coordinates in database
    $result = $wpdb->query( "UPDATE ".$table_prefix."geo SET geo_latitude= '$geo_latitude', geo_longitude= '$geo_longitude' WHERE geo_id='$geo_id'");
  } else {
    // insert geo-coordinates in database
    $result = $wpdb->query( "INSERT INTO ".$table_prefix."geo VALUES (NULL, '$post_id', '$geo_latitude', '$geo_longitude')") or die(mysql_error());
  }

  }  else {    // if not found delete if found
    // find geo-coordinates for this post in database
    $geo_id = $wpdb->get_var($wpdb->prepare("SELECT geo_id FROM ".$table_prefix."geo WHERE post_id='$post_id'"));
    // delete geo-coordinates in database if found
    if($geo_id) $result = $wpdb->query("DELETE FROM ".$table_prefix."geo WHERE geo_id='$geo_id'") or die(mysql_error());
  }
  mvs_kml_output($post_id);
}

function mvs_get_coordinates_form_address($adres, $timeout=10){
  // Roep Google Maps aan met het adres en de soort output
  $adres = urlencode($adres);
  $url = "http://maps.google.com/maps/geo?q=".$adres."&output=xml";

  // defineer een header
  $parts = parse_url($url);
  $host = $parts['host'];
  $path = $parts['path'];
  $query = $parts['query'];
  $header = "GET $path"."?"."$query HTTP/1.0\r\n";
  $header .= "Host: $host\r\n";
  $header .= "User-Agent: {$_SERVER['SERVER_NAME']}{$_SERVER['REQUEST_URI']} - RRWD\r\n\r\n";

  // open socket naar Google en lees output in
  if (gethostbyname($host) != $host) {
    $socket = @fsockopen($host, 80, $errno, $errstr, $timeout);
    if ($socket) {
      fwrite($socket, $header);
      unset($http_response);
      while (!feof($socket)) {
        $http_response .= fread($socket, 256);
      }
      fclose($socket);
      if (strpos($http_response, "200 OK")) {
        // mik de header weer weg
        $pos1 = stripos($http_response, "<?xml");
        $http_response = substr("$http_response",$pos1);
        // lees de xml in en pik de coordinates eruit
        $xml = new SimpleXMLElement($http_response);
        $coordinatenArray = array();
        $coordinaten =  $xml->Response->Placemark->Point->coordinates;
        $coordinatenArray = explode(",", $coordinaten);
        return $coordinatenArray;
      }
    }
  }
}

// write kml-files for tags and cats of these post
function mvs_kml_output($post_id) {
  global $wpdb; global $table_prefix;
  $icon = "http://www.modevakschool.net/label/mapicon_blauw.png";
  $posttags = get_the_tags($post_id);
  if ($posttags) {
    foreach($posttags as $tag) {
      $tag_id = $tag->name;
      mvs_write_kml($tag_id, $icon, "tag");
    }
  }
  $postcats = get_the_category($post_id);
  if ($postcats) {
    foreach($postcats as $category) {
      $cat_id = $category->cat_ID;
       mvs_write_kml($cat_id, $icon, "category");
    }
  }
}

function mvs_write_kml($sort_id, $icon, $sort) {
  global $wpdb;
  global $table_prefix;
  unset($content_data);
  // Date in the past and no cache
  $content .= "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \r\n";
  $content .= "<kml xmlns=\"http://www.opengis.net/kml/2.2\"> \r\n";
  $content .= "        <name>Adressenlijst voor Google Maps</name> \r\n";
  $content .= "        <description>Zoals opgenomen op de website modevakschool.net</description> \r\n";
  $content .= "  <Document> \r\n";
  rewind_posts();
  if ($sort == "category") $arguments = "numberposts=-1&category=$sort_id";
  if ($sort == "tag") $arguments = "numberposts=-1&tag=$sort_id";
  $lastposts = get_posts($arguments);
  foreach($lastposts as $post) :
    unset ($geo_latitude);
    unset ($geo_longitude);
    $id = $post->ID;
    $geo_latitude  = $wpdb->get_var($wpdb->prepare("SELECT geo_latitude  FROM ".$table_prefix."geo WHERE post_id='$id'"));
    $geo_longitude = $wpdb->get_var($wpdb->prepare("SELECT geo_longitude FROM ".$table_prefix."geo WHERE post_id='$id'"));
    $content_data .= "      <Placemark> \r\n";
    $content_data .= "        <name>".htmlspecialchars($post->post_title)."</name> \r\n";
    $content_data .= "       <Style> \r\n";
    $content_data .= "        <IconStyle> \r\n";
    $content_data .= "          <Icon> \r\n";
    $content_data .= "            <href>$icon</href> \r\n";
    $content_data .= "          </Icon> \r\n";
    $content_data .= "        </IconStyle> \r\n";
    $content_data .= "      </Style> \r\n";
    $content_data .= "        <description> \r\n";
    $content_data .= "          <![CDATA[ \r\n";
    $content_data .= "            <a href=\"".get_permalink($id)."\">".$post->post_title."</a> \r\n";
    $content_data .= "          ]]> \r\n";
    $content_data .= "        </description> \r\n";
    $content_data .= "        <Point> \r\n";
    $content_data .= "          <coordinates>$geo_latitude, $geo_longitude</coordinates> \r\n";
    $content_data .= "        </Point> \r\n";
    $content_data .= "      </Placemark> \r\n";
  endforeach;
  $content .= $content_data;
  $content .= "    </Document> \r\n";
  $content .= "  </kml> \r\n";
  if ($content_data && $geo_latitude) {
    $filename = $_SERVER["DOCUMENT_ROOT"]."/label/label-".$sort."-".$sort_id.".kml";
    $fh = fopen($filename, 'w');
    fwrite($fh,"$content \r\n");
    fclose($fh);
  }
}

Voor de uitvoer: Voeg toe aan functions.php

// functies voor Google Maps
// Code by Rian Rietveld @RRWD web development
// http://www.rrwd.nl
// July-August 2010

add_action('wp_head', 'rrwd_get_google_map_js');

function rrwd_get_google_map_js() {
  global $wpd;
  // adresgegevens bij zoekresultaten stap 123
  if (IsSet($_GET['trefwoord']) || IsSet($_GET['category_name'])) rrwd_get_multi_google_map_js();
}

function rrwd_get_multi_google_map_js() {
  global $wpdb, $table_prefix;
  unset($postcode);
  $lat= "5.2912660"; // Default centrum nederland
  $long = "52.1326330";
  $zoom = 7; // Heel Nederland
  $trefwoord = $_GET['trefwoord'];
  $cat_name = $_GET['category_name'];
  if ((!is_numeric($cat_name))) unset($cat_name);
  if ($cat_name !='') {
    if (strlen($cat_name)  == 1 )$cat_name = $cat_name ."0";
    // zoek coordinaten erbij voor centrum kaartje
    $geo_latitude   = $wpdb->get_var($wpdb->prepare("SELECT geo_plaats_latitude  FROM ".$table_prefix."geo_plaats WHERE geo_plaats_korte_postcode='$cat_name'"));
    $geo_longitude  = $wpdb->get_var($wpdb->prepare("SELECT geo_plaats_longitude FROM ".$table_prefix."geo_plaats WHERE geo_plaats_korte_postcode='$cat_name'"));
    $zoom = 11;
    if ($geo_latitude)  $lat=$geo_latitude;
    if ($geo_longitude)  $long=$geo_longitude;
    if ($lat && $long) list($lat1,$lat2,$lon1,$lon2) = getPlatteBox($lat,$long,30);
  }

  $kml = "http://www.modevakschool.net/label/label-tag-naailes.kml";
  if ($trefwoord == 'stoffen-fournituren') $kml = "http://www.modevakschool.net/label/label-tag-stoffen.kml";
  if ($trefwoord == 'naaipatronen')        $kml = "http://www.modevakschool.net/label/label-tag-naaipatronen.kml";
  if ($trefwoord == 'naaimachine')         $kml = "http://www.modevakschool.net/label/label-tag-naaimachine.kml";
  $preserveViewport = ",{preserveViewport:true}";
?>

<script type="text/javascript">
  function initialize() {   var myLatlng = new google.maps.LatLng(<?php echo "$long"?>,<?php echo "$lat"?>);
   var myOptions = {
     zoom: <?php echo $zoom ?>,
     center: myLatlng,
     mapTypeId: google.maps.MapTypeId.ROADMAP
   }
   var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
   var ctaLayer = new google.maps.KmlLayer('<?php echo "$kml"?>', {preserveViewport:true});   ctaLayer.setMap(map);
 }

  function loadScript() {
   var script = document.createElement("script");
   script.type = "text/javascript";
   script.src = "http://maps.google.com/maps/api/js?sensor=false&callback=initialize";
   document.body.appendChild(script); }

 window.onload = loadScript; </script> <?php
}

 function getPlatteBox($lat_degrees,$lon_degrees,$distance_in_km) {
   // met dank aan Marc de Jonge
   $radius = 6730.0;
   $rlat=$radius*sin(deg2rad(90-$lat_degrees));
   $dphi_lon=asin($distance_in_km/$rlat);
   $dphi_lat=asin($distance_in_km/$radius);
   $lat1=$lat_degrees-rad2deg($dphi_lat);
   $lat2=$lat_degrees+rad2deg($dphi_lat);
   $lon1=$lon_degrees-rad2deg($dphi_lon);
   $lon2=$lon_degrees+rad2deg($dphi_lon);
   return array($lat1,$lat2,$lon1,$lon2);
 }

In de template-file, bovenin in de PHP

// get data for the loop, voor elk trefwoord waar de bezoeker op kan zoeken.
if ($trefwoord == 'naailes-opleiding') {
 $args=array(
   'orderby'=> 'title',
   'order'=> 'ASC',
   'tag' => 'naailes,mode-opleiding',
   'posts_per_page' => -1,
  );
}
// set box results to display
 $lat= "5.2912660";
 $long = "52.1326330";
 $distance = 500;
 $cat_name = $_GET['category_name'];
 if ((!is_numeric($cat_name))) unset($cat_name);
 if ($cat_name !='') { if (strlen($cat_name)  == 1 ) $cat_name = $cat_name ."0";
   // zoek coordinaten erbij voor centrum kaartje
   $lat   = $wpdb->get_var($wpdb->prepare("SELECT geo_plaats_latitude  FROM ".$table_prefix."geo_plaats WHERE geo_plaats_korte_postcode='$cat_name'"));
   $long  = $wpdb->get_var($wpdb->prepare("SELECT geo_plaats_longitude FROM ".$table_prefix."geo_plaats WHERE geo_plaats_korte_postcode='$cat_name'"));
   $distance = 20; 
 }
 list($lat1,$lat2,$lon1,$lon2) = getPlatteBox($lat,$long,$distance);

Pas de Loop aan

<?php // check on regio and sort
$post_id = get_the_ID();
  // get geo-coordinates form this post
 unset($geo_id);
 $geo_id = $wpdb->get_var($wpdb->prepare("SELECT geo_latitude FROM ".$table_prefix."geo WHERE post_id = '$post_id' AND (geo_latitude BETWEEN '$lat1' AND '$lat2') AND (geo_longitude BETWEEN '$lon1' AND '$lon2')"));
  // is it in the box? then display
 if ($geo_id) {
   ?> <div> .. uitvoer in de loop.. zoals je het zelf wilt </div>
 <?php  }  ?>

Plugin volgt, zodra ik de items in de discussie heb opgelost.

Discussie

Deze wijze werkt snel, maar alleen voor Nederland. Het voordeel is dat een bezoeker maar twee cijfers hoeft in te vullen en meteen een overzicht krijgt. Deze wijze van zoeken was al aanwezig op de websites modevakschool.net en doggo.nl, waarbij puur naar de eerste twee cijfers van de postcode werd gekeken. Nu wordt er in een echt geografisch gebied gekeken, wat logischere resultaten geeft.

Een en ander is nu wat versnipperd opgezet en kan beslist veel economischer. Voor het kaartje en voor de resultaten moet het programma nu twee keer door de data heen, de trefwoorden staan nu hard in de code, dat kan beslist variabel en zo al er nog meer zijn. Feel free to comment :-)

De link in het tekst-ballonnetje opent naar een nieuw venster. Dit is lastig als het een link naar dezelfde site betreft. Dit valt waarschijnlijk op te lossen via JQuery. (How to override target=blank in KML popups in embedded Google map?) Moet er nog beter naar kijken hoe dit werkt.

Als ook op buitenlandse adressen moet worden gezocht is meer input nodig van de bezoeker dan alleen een twee cijferige postcode. De bezoeker zal dat ook voor het land moeten kiezen bij het invullen van de zoekactie.

Ik laat het nu even bezinken en ga na de vakantie eens puzzelen hoe een en ander in een mooie vorm te gieten en er een plugin voor te schrijven. In combinatie met de optie om bij elke pagina met een adres een kaartje te laten zien. Wordt vervolgt dus…

Bronnen

About these ads

Published by

Rian Rietveld

Web developer @ RRWD web development | WordPress and Accessibility

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s