OpenLayers Dynamic KML

From OpenStreetMap Wiki
Jump to: navigation, search

Description

Google Earth with OSM fuel stations

I copied this page from the GoogleEarth one, and some of it still needs to be changed, now it is too obvious I stole it. :) Wouldn't it be cool to show OpenStreetMap data/poi's into GoogleEarth ? And even cooler would be to download only data that's visible in the current view. And super cool would be to retrieve just what you want by adjusting an url parameter. As extra coolness instead of GoogleEarth use OpenLayers :) Well, good news.. it all can be done, and it's even not that hard to make (with a little help from the master, crschmidt).

HowTo

Requirements:

  • A server with
    • Some kind of database with openstreetmap data (i'm using the postgres mapnik database of the dutch tileserver)
    • PHP with a script to deliver the dynamic KML data


Server Side

First create a table with poi's into your database. If you have already a mapnik setup, then you can use the planet_osm_point table like in the code below. Change the login data and database related stuff for your system. Maybe you also have to correct the transform (4326) for your projection ! Put the data.php onto your server.

data.php

<?php
	// Creates the KML/XML Document.
	$dom = new DOMDocument('1.0', 'UTF-8');
 
	// Creates the root KML element and appends it to the root document.
	$node = $dom->createElementNS('http://earth.google.com/kml/2.1', 'kml');
	$parNode = $dom->appendChild($node);
 
	// Creates a KML Document element and append it to the KML element.
	$dnode = $dom->createElement('Document');
	$docNode = $parNode->appendChild($dnode);
 
 
	// database stuff
	$pgsql['user'] = 'mapnik'; // username
	$pgsql['db'] = 'osm';      // database
 
	// currently we only support amenity in the url
	$what = $_GET['amenity'];
	if ($what == '') {
		$what = 'restaurant';
	}
 
	$connect_string = ' user=' . $pgsql['user'] . ' dbname=' . $pgsql['db'];
	$pgcon = pg_connect($connect_string); 
	if ($pgcon) { // connected!	
 
		$bbox = $_GET['BBOX']; // get the bbox param from google earth
		list($bbox_south, $bbox_west, $bbox_east, $bbox_north) = split(",", $bbox); // west, south, east, north
                // Get the data from the Database Table (planet_osm_point)
                $sql = "SELECT osm_id, name, x(way) as lon, y(way) as lat FROM planet_osm_point WHERE (amenity='" . $what . "') AND (box(point(" . $bbox_south . "," . $bbox_west . "),point(" . $bbox_east . "," . $bbox_north . ")) ~ (way)) LIMIT 1000";
                //or with transform:
		//$sql = "SELECT osm_id, name, x(transform(way,4326)) as lon, y(transform(way, 4326)) as lat FROM planet_osm_point WHERE (amenity='" . $what . "') AND (box(point(" . $bbox_south . "," . $bbox_west . "),point(" . $bbox_east . "," . $bbox_north . ")) ~ transform(way,4326)) LIMIT 100";
 
		// perform query
		$query = pg_query($pgcon, $sql);
		if ($query) {
			if (pg_num_rows($query) > 0) { // found something
 
				// Iterates through the results, creating one Placemark for each row.
				while ($row = pg_fetch_array($query))
				{
					 // Creates a Placemark and append it to the Document.
					  $node = $dom->createElement('Placemark');
					  $placeNode = $docNode->appendChild($node);
 
					  // Creates an id attribute and assign it the value of id column.
					  $placeNode->setAttribute('id', 'placemark' . $row['osm_id']);
 
					  // Create name, and description elements and assigns them the values of the name and address columns from the results.
					  $nameNode = $dom->createElement('name',htmlentities($row['name']));
					  $placeNode->appendChild($nameNode);
 
					  // Creates a Point element.
					  $pointNode = $dom->createElement('Point');
					  $placeNode->appendChild($pointNode);
 
					  // Creates a coordinates element and gives it the value of the lng and lat columns from the results.
					  $coorStr = $row['lon'] . ','  . $row['lat'];
					  $coorNode = $dom->createElement('coordinates', $coorStr);
					  $pointNode->appendChild($coorNode);
				}
 
			} else { // nothing found
			}
		}
		pg_close($pgcon);
	} else {
		// no valid database connection
	}
 
	$kmlOutput = $dom->saveXML();
	header('Content-type: application/vnd.google-earth.kml+xml');
	echo $kmlOutput;
?>

I used today's OpenLayers svn (Nov 21. 2008) You will need this patch: http://trac.openlayers.org/attachment/ticket/1841/8233.patch

Client Side / OpenLayers

index.php

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>OSM - Dynamic POI update</title>
    <style type="text/css">
#map {
        width: 100%;
        height: 100%;
        border: 0px;
        padding: 0px;
        position: absolute;
     }
body {
        border: 0px;
        margin: 0px;
        padding: 0px;
        height: 100%;
     }
    </style>
    <script src="openlayers/lib/OpenLayers.js"></script>
    <script src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
    <script type="text/javascript">
 
OpenLayers.Layer.OSM.NLMapnik = OpenLayers.Class(OpenLayers.Layer.OSM, {
    /**
     * Constructor: OpenLayers.Layer.OSM.NLMapnik
     *
     * Parameters:
     * name - {String}
     * options - {Object} Hashtable of extra options to tag onto the layer
     */
    initialize: function(name, options) {
        var url = [
            "http://c.tile.openstreetmap.nl/tilecache.py/1.0.0/mapnik/",
            "http://a.tile.openstreetmap.nl/tilecache.py/1.0.0/mapnik/"
        ];
        options = OpenLayers.Util.extend({ numZoomLevels: 19 }, options);
        var newArguments = [name, url, options];
        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
    },
 
    CLASS_NAME: "OpenLayers.Layer.OSM.NLMapnik"
});
 
 
        var map;
	var POI;
	get_string = location.search;
 
	var features = "2:3;1:1;1:2;2:4;5:17;5:18;3:8;3:9;3:10";
 
        function init(){
            map = new OpenLayers.Map('map',
                    { maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34),
                      numZoomLevels: 19,
                      maxResolution: 156543.0399,
                      units: 'm',
                      projection: new OpenLayers.Projection("EPSG:900913"),
                      displayProjection: new OpenLayers.Projection("EPSG:4326")
                    });
 
            var layerNLMapnik = new OpenLayers.Layer.OSM.NLMapnik("NL Mapnik (updated weekly)");
 
            var layerMapnik = new OpenLayers.Layer.OSM.Mapnik("Mapnik (updated weekly)");
 
	    var colors = ["black", "blue", "green", "red"];
 
	    var style = new OpenLayers.Style({
                pointRadius: "${radius}",
                fillColor: "red",
                fillOpacity: 0.8,
                strokeColor: "#ff5555",
                strokeWidth: 2,
                strokeOpacity: 0.8
            }, {
                context: {
                    radius: function(feature) {
			return Math.min(feature.attributes.count, 7) + 3;
                    },
                }
            });
 
	    var pois = new OpenLayers.Layer.Vector("POI", {
		projection: new OpenLayers.Projection("EPSG:4326"),
		strategies: [
			new OpenLayers.Strategy.BBOX(),
			new OpenLayers.Strategy.Cluster()
		],
		protocol: new OpenLayers.Protocol.HTTP({
                        url: "data.php",  //Note that it is probably worth adding a Math.random() on the end of the URL to stop caching.
			format: new OpenLayers.Format.KML({
                                extractStyles: true, 
                                extractAttributes: true
                        }),
		}),
		styleMap: new OpenLayers.StyleMap({
                        "default": style,
                        "select": {
                            fillColor: "#8aeeef",
                            strokeColor: "#32a8a9"
                        }
                })
	    });
 
            map.addLayer(layerMapnik);
	    map.addLayer(layerTah);
	    map.addLayer(layerNLMapnik);
 
	    map.addLayer(pois);
 
	selectControl = new OpenLayers.Control.SelectFeature(map.layers[3],
                {onSelect: onFeatureSelect, onUnselect: onFeatureUnselect});
 
	<?php
	$lon = 5.73334;
	$lat = 52.25;
	$zoom = 9;
	if (isset($_GET['lon'])) { $lon = $_GET['lon'];}
	if (isset($_GET['lat'])) { $lat = $_GET['lat'];}
	if (isset($_GET['zoom'])) { $zoom = $_GET['zoom'];}
 
        printf("var centre = new OpenLayers.LonLat(%s, %s);\n", $lon, $lat);
        printf("var zoom = %s;\n", $zoom);
?>
 
 
 
            map.addControl(new OpenLayers.Control.LayerSwitcher());
	    map.addControl(new OpenLayers.Control.Permalink());
	    map.addControl(selectControl);
	    selectControl.activate();
 
	    map.setCenter(centre.transform(map.displayProjection,map.projection), zoom);
        }
        function onPopupClose(evt) {
            selectControl.unselect(selectedFeature);
        }
 
        function onFeatureSelect(feature) {
            selectedFeature = feature;
	    text = '';
	    for (var i in feature.cluster){
		var feat = feature.cluster[i];
		text += '<h3>'+feat.attributes.name + "</a></h3><div>" + feat.attributes.name + "</div><br />";
	    }
            popup = new OpenLayers.Popup("chicken", 
                                     feature.geometry.getBounds().getCenterLonLat(),
                                     null,
                                     text,
                                     true, onPopupClose);
            feature.popup = popup;
	    popup.setOpacity(0.7);
            map.addPopup(popup);
        }
 
        function onFeatureUnselect(feature) {
            map.removePopup(feature.popup);
            feature.popup.destroy();
            feature.popup = null;
        }
 
        // -->
    </script>
  </head>
  <body onload="init()">
    <div id="map"></div>
  </body>
</html>

Future stuff

  • Remove fixed amenity implementation, so you can query every possible field
  • add polygon support, so you can show ways / borders / buildings / area's

links