OpenLayers Dynamic KML
Description
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 6
index.php
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/css/ol.css" type="text/css">
<style>
.map {
height: 400px;
width: 100%;
}
</style>
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.14.1/build/ol.js"></script>
<title>OpenLayers example</title>
</head>
<body>
<h2>My Map</h2>
<div id="map" class="map"></div>
<script type="text/javascript">
var vectorSource = new ol.source.Vector({
projection: 'EPSG:4326',
url: function(extent, resolution, proj) {
var extend2 = ol.proj.transformExtent(extent, proj, 'EPSG:4326');
return 'data.php?BBOX='.concat(extend2.join(','));
},
format: new ol.format.KML(),
strategy: ol.loadingstrategy.bbox,
});
var pois = new ol.layer.Vector({
source: vectorSource,
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
pois
],
view: new ol.View({
center: ol.proj.fromLonLat([-50, 30]),
zoom: 4
})
});
function debounce(func, timeout = 1000){
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeout);
};
}
function refreshMap2() {
console.log('refreshMap');
vectorSource.refresh();
}
const refreshMap = debounce(refreshMap2, 1000);
map.getView().on('change:resolution', refreshMap);
map.getView().on('change:center', refreshMap);
map.getView().on('change:rotation ', refreshMap);
</script>
</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