$theTitle=wp_title(" - ", false); if($theTitle != "") { ?> } else { ?> } ?>
by Andrew Johnstone
In: General
31 Dec 2009Last year I wrote an application to highlight media outlets and their reach (coverage of media outlets), selecting regions within the UK and highlighting aspects of a map. This had many issues where by hitting performance problems of rendering within browsers and also limitations of converting KML to tiles via google. A list of these limitations are:
Some of these limits have since been increased by google and are documented.
Maximum fetched file size (raw KML, raw GeoRSS, or compressed KMZ) 3MB Maximum uncompressed KML file size 10MB Maximum number of Network Links 10 Maximum number of total document-wide features 1,000
In order to alleviate these issues I ended up with the following
So depending on the depth (zoom) of the map and the area selected as well the volume of data, it would either use tiles or googles KML directly (Increased functionality).
In order to have greater control over the spatial data within our database we split this into areas, regions, and sub_regions, which held lookups to postcodes, towns and spatial data itself (There are a lot of discrepancies over outlines of maps).
Left hand menu:
<ul style="display: block;"> <li id="East"><a href="#" onclick="loadTilesFromGeoXML('|1|'); return false;">East</a> <ul style="display: none;"> <li><a href="#" onclick="loadTilesFromGeoXML('|1|6'); return false;">Bedfordshire</a></li> <li><a href="#" onclick="loadTilesFromGeoXML('|1|18'); return false;">Cambridgeshire</a></li> ... </ul> </li> </ul>
Javascript to locate tiles
function loadTilesFromGeoXML(entity_id) { // Matches database record ids that are mapped to spatial data within MySQL mapTownsId = entity_id.toString().split('|')[0]; mapRegionsId = entity_id.toString().split('|')[1]; mapSubRegionsId = entity_id.toString().split('|')[2]; locationUrl ='map_towns_id='+mapTownsId+'&map_regions_id='+mapRegionsId+'&map_sub_regions_id='+mapSubRegionsId; var cc = map.fromLatLngToDivPixel(map.getCenter()); map.setZoom(1); // Request URL to cached titles links geoXMLUrl = '/ajax/mapping/get/overlays/region?'+locationUrl; geoXMLUrl+='&format=JSON&method=getLinks&x='+cc.x+'&y='+cc.y+'&zoom='+map.getZoom(); // tileUrlTemplate: 'http://domain.com/maps/proxy/regions/?url=http%3A%2F%2Fdomain.com/ajax/mapping/get/cache/?filename=.1.6.0&x={X}&y={Y}&zoom={Z}', $.getJSON(geoXMLUrl, function(data) { $.each(data, function(i,link) { kmlLinks+=encodeURIComponent(link)+','; }); // Builds the location for tiles to be mapped tileUrlTemplate = '/maps/proxy/regions/?url='+kmlLinks+'&x={X}&y={Y}&zoom={Z}'; var tileLayerOverlay = new GTileLayerOverlay( new GTileLayer(null, null, null, { tileUrlTemplate: tileUrlTemplate, isPng:true, opacity:1.0 }) ); if (debug) GLog.writeUrl('/maps/proxy/regions/?url='+kmlLinks+'&x={X}&y={Y}&zoom={Z}'); map.addOverlay(tileLayerOverlay); }); }
Response whilst retrieving links (if cached)
The code behind this simply caches the KML files, if it does not exist, otherwise attempts to create it and also outputs a json request with the files matching the sequence and globs for any files with a similar pattern, all files are suffixed with their page number.
["/ajax/mapping/get/cache/?filename=.1..0&x=250&y=225&zoom=5","/ajax/mapping/get/cache/?filename=.1..1&x=250&y=225&zoom=5"]
Proxying googles tiles and merging the layer ids
$kmlUrls = urlencode($_GET['url']); $cachePath = dirname(__FILE__).'/cache.maps/tiles/'; $cachedFiles = array_filter(explode(',',rawurldecode($kmlUrls))); $hash = sha1(rawurldecode($kmlUrls).".w{$_GET['w']}.h{$_GET['h']}.x{$_GET['x']}.y{$_GET['y']}.{$_GET['zoom']}"); $cachePath.="{$_GET['x']}.{$_GET['y']}/{$_GET['zoom']}/"; if (!is_dir($cachePath)) { @mkdir($cachePath, 0777, true); } // Returns image if cached already and aggregated. if (file_exists($path = $cachePath.$hash)) { header('Content-Type: image/png'); $fp = fopen($path, 'rb'); fpassthru($fp); } // Extract layer id's from KML files that are to be merged. $layerIds = array(); foreach( $cachedFiles AS $kmlFile) { $kmlFile="http://{$_SERVER['HTTP_HOST']}{$kmlFile}"; $url = "http://maps.google.com/maps/gx?q={$kmlFile}&callback=_xdc_._1fsue7g2w"; @$c = file_get_contents($url); if (!$c) throw new Exception("Failed to request {$url} - {$c}"); preg_match_all('/layer_id:"kml:(.*)"/i', $c, $matches); if (count($matches)>0 && isset($matches[1][0])) { $layerIds[] = "kml:{$matches[1][0]}"; } } // Cache locally. if (count($layerIds)>0) { header('Content-Type: image/png'); // Aggregate layers into a single image $link = "http://mlt0.google.com/mapslt?lyrs=" . implode(',',$layerIds); $link.="&x={$_GET['x']}&y={$_GET['y']}&z={$_GET['zoom']}&w={$_GET['w']}&h={$_GET['h']}&source=maps_api"; echo $c = file_get_contents($link); @file_put_contents($path, $c); } else { // Output 1x1 png header('Content-Type: image/png'); echo base64_decode('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII='); } }
Paging GeoXML loading
function loadGeoXMLPaged(geoXMLUrl) { var cc = map.fromLatLngToDivPixel(map.getCenter()); geoXMLUrl+='&format=JSON&method=getLinks&x='+cc.x+'&y='+cc.y+'&zoom='+map.getZoom(); if (debug) GLog.writeUrl(geoXMLUrl); $.getJSON(geoXMLUrl, function(data) { geoXmlPager = data; loadGeoXmlPage(); }); } var timeoutPID = null; function loadGeoXmlPage(){ if (data = geoXmlPager.pop()){ if (debug) GLog.writeUrl(BASE_URL+data); geoXmlStack.push(new GGeoXml(BASE_URL+data)); map.addOverlay(geoXmlStack[geoXmlStack.length - 1]); GEvent.addListener(geoXmlStack[geoXmlStack.length - 1],"load",function() { timeoutPID = setTimeout("loadGeoXmlPage()", 500); }); }else{ clearTimeout(timeoutPID); map.setZoom(map.getBoundsZoomLevel(bounds)); map.setCenter(bounds.getCenter()); try { geoXmlStack[geoXmlStack.length - 1].gotoDefaultViewport(map); } catch(e) {} } }
All the code above has been modified slightly to make it applicable to others, however don’t accept raw input as its simply an example.
I have been a developer for roughly 10 years and have worked with an extensive range of technologies. Whilst working for relatively small companies, I have worked with all aspects of the development life cycle, which has given me a broad and in-depth experience.