Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add a MapManager to build and execute the SPARQL query

  • Loading branch information...
commit 17e211768c84476340477e5e29237bb1ed550790 1 parent e4f32bc
Ric Roberts RicSwirrl authored
Showing with 366 additions and 25 deletions.
  1. +307 −0 javascripts/swirrl/map-manager.js
  2. +59 −25 map.html
307 javascripts/swirrl/map-manager.js
View
@@ -0,0 +1,307 @@
+(function() {
+
+ // the constructor.
+ var MapManager = function(googleMap, initialScoreDomain) {
+ self = this;
+ map = googleMap;
+ scoreDomain = initialScoreDomain;
+
+ $(this).bind('lsoaDataRetrieved', function() {
+ lsoaDataRetrieved = true;
+
+ // TODO: this is where we're refresh the polygons on the map, later.
+
+ // for now, just log out the lsoa data, and say we've done
+ window.swirrl.log(lsoaData);
+
+ $(self).trigger('finished');
+
+ });
+
+ // if there was an error getting any data, finish the request,
+ // but don't remember the tiles ... so we can try again next time.
+ $(this).bind('dataError', function() {
+ // we only want to do this once - but getting boundaries or lsoa data (sparql) could raise this
+ if (!errored) {
+ errored = true;
+ window.swirrl.log('data error!');
+ prevTiles = []; // blank out the prevTiles
+ $(this).trigger('finished'); // tell people we're done
+ }
+ });
+
+ }
+
+ // public API
+ MapManager.prototype = {
+ refresh: function() {
+ $(this).trigger('started');
+ errored = false;
+
+ // clear these variables for this refresh
+ if (map.getZoom() > minZoom) {
+ $(this).trigger('zoomOK');
+
+ var tiles = getTiles();
+ deleteOldTilesData(tiles);
+ newTiles = getNewTiles(tiles);
+
+ prevTiles = tiles; // remember this set of tiles for comparison next time.
+
+ // TODO: also get the boundary data.
+
+ lsoaDataRetrieved = false;
+ getLsoaData(newTiles);
+
+ } else {
+ $(this).trigger('zoomTooWide');
+ $(this).trigger('finished');
+ }
+ }
+ }
+
+ // private variables
+ var map
+ , errored = false
+ , prevTiles = []
+ , scoreDomain
+ , lsoaData = {} // map of tiles -> data
+ , lsoaDataRetrieved = false
+ , minZoom = 12
+ , self;
+
+ // private functions
+ var tileInArray = function(tile, tileArray) {
+ var inArray = false;
+ for(var i=0; i < tileArray.length; i++) {
+ t = tileArray[i];
+ if (t[0][0] == tile[0][0] && t[0][1] == tile[0][1] &&
+ t[1][0] == tile[1][0] && t[1][1] == tile[1][1]) {
+ inArray = true;
+ break;
+ }
+ }
+ return inArray;
+ };
+
+ var deleteOldTilesData = function(tiles) {
+ for(var i = 0; i < prevTiles.length; i++) {
+ var prevTile = prevTiles[i];
+ if (!tileInArray(prevTile, tiles)) {
+ window.swirrl.log('tile data not needed', prevTile);
+
+ // TODO: also delete boundary data, when we have some.
+
+ delete lsoaData[prevTile];
+ }
+ }
+ };
+
+ var getNewTiles = function(tiles) {
+ var newTiles = [];
+ for(var i = 0; i < tiles.length; i++) {
+ var tile = tiles[i];
+ if (!tileInArray(tile, prevTiles)) {
+ newTiles.push(tile);
+ }
+ }
+ window.swirrl.log('no of new tiles', newTiles.length);
+ window.swirrl.log('new tiles', newTiles);
+ return newTiles;
+ };
+
+ var setLsoaData = function(tile, lsoaNotation, data) {
+ if (!lsoaData[tile]) {
+ lsoaData[tile] = {};
+ }
+ lsoaData[tile][lsoaNotation] = data;
+ };
+
+ // work out the 0.1lat/long tiles that are convered by the current bounds
+ var getTiles = function() {
+
+ var divideLatByTen = function(latx10) {
+ var lat = latx10.substring(0,2);
+ if (latx10.length > 2) {
+ lat += "." + latx10.substring(2);
+ }
+ return lat;
+ };
+
+ // for longs we have to account for the minus sign.
+ var divideLongByTen = function(longx10){
+ var lng = "";
+ if (longx10[0] == "-") {
+ // negative values
+ if (longx10.length == 2){
+ lng = "-0." + longx10[1];
+ } else {
+ lng = longx10.substring(0,2);
+ if (longx10.length > 2) {
+ lng += "." + longx10.substring(2);
+ }
+ }
+ } else {
+ // positive values
+ if (longx10.length == 1){
+ lng = "0." + longx10[0];
+ } else {
+ lng = longx10.substring(0,1);
+ if (longx10.length > 1) {
+ lng += "." + longx10.substring(1);
+ }
+ }
+ }
+
+ // edge case: convert -0.0 to 0.0
+ if (lng == "-0.0") {
+ lng = "0.0";
+ }
+
+ return lng;
+ };
+
+ var getTileBounds = function(lowerLatx10, lowerLongx10) {
+ // make sure they're all strings.
+
+ var upperLatx10 = (lowerLatx10 + 1).toString();
+ var upperLongx10 = (lowerLongx10 + 1).toString();
+ lowerLatx10 = lowerLatx10.toString();
+ lowerLongx10 = lowerLongx10.toString();
+
+ var lowerLat = divideLatByTen(lowerLatx10);
+ var upperLat = divideLatByTen(upperLatx10);
+ var lowerLong = divideLongByTen(lowerLongx10);
+ var upperLong = divideLongByTen(upperLongx10);
+
+ var tileBounds = [ [lowerLat, lowerLong], [upperLat, upperLong] ];
+ window.swirrl.log(tileBounds);
+ return tileBounds;
+ };
+
+ // work out the 0.1lat/long tiles that are convered by the current bounds
+ var northEast = map.getBounds().getNorthEast();
+ var southWest = map.getBounds().getSouthWest();
+
+ // these are the outer bounds, rounded to 0.1 extremities.
+ // we work with everything scaled up to 10 times the size to avoid floating point probs.
+ var lowerLatx10 = Math.floor(southWest.lat() * 10);
+ var upperLatx10 = Math.floor((northEast.lat()+0.1) * 10);
+ var lowerLongx10 = Math.floor(southWest.lng() * 10);
+ var upperLongx10 = Math.floor((northEast.lng()+0.1) * 10);
+
+ var tiles = [];
+ var lat = lowerLatx10;
+
+ while (lat <= (upperLatx10-1)) {
+ var lng = lowerLongx10;
+ while (lng <= (upperLongx10 -1)) {
+ tiles.push(getTileBounds(lat,lng));
+ lng += 1;
+ }
+ lat += 1;
+ }
+
+ return tiles;
+ };
+
+ // get the LSOA data from the database.
+ var getLsoaData = function(tiles) {
+
+ // define this inside this closure - it's not useful outside the scope of this func
+ var buildSparql = function(tile) {
+
+ var lowerLat = tile[0][0];
+ var lowerLong = tile[0][1];
+ var upperLat = tile[1][0];
+ var upperLong = tile[1][1];
+
+ var sparql = "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> " +
+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> " +
+ "SELECT ?lsoa ?notation ?label ?lat ?long ?score" +
+ " WHERE { " +
+ " GRAPH <http://opendatacommunities.org/id/graph/geography/lsoa> { " +
+ " ?lsoa a <http://opendatacommunities.org/def/geography#LSOA> . " +
+ " ?lsoa geo:lat ?lat . " +
+ " ?lsoa geo:long ?long . " +
+ " ?lsoa <http://www.w3.org/2004/02/skos/core#notation> ?notation . " +
+ " ?lsoa rdfs:label ?label . " +
+ " } " +
+ " GRAPH <http://opendatacommunities.org/id/graph/IMD/2010/" + scoreDomain + "> { " +
+ " ?obs <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?lsoa . " +
+ " ?obs <http://opendatacommunities.org/def/IMD#" + scoreDomain + "> ?score . " +
+ " } " +
+ " FILTER ( ?lat >= " + lowerLat + " && " +
+ " ?lat < " + upperLat + " && " +
+ " ?long >= " + lowerLong + " && " +
+ " ?long < " + upperLong + " ) . " +
+ "}";
+ return sparql;
+
+ };
+
+ var noOfTiles = tiles.length;
+
+ // nothing to do.
+ if(noOfTiles == 0) {
+ $(self).trigger('lsoaDataRetrieved');
+ }
+
+ var tilesRetrieved = 0;
+
+ var page = 1;
+ var pageSize = 1000;
+
+ var callAjaxSparqlPaging = function(sparql, tile) {
+ var queryUrl = "http://opendatacommunities.org/sparql.json?_page=" + page.toString() + "&_per_page=" + pageSize.toString() + "&query=" + encodeURIComponent(sparql);
+ window.swirrl.log('about to call', queryUrl);
+ $.ajax(
+ queryUrl,
+ {
+ success: function(data, textStatus, jqXHR) {
+ var pageOfData = data.results.bindings;
+ $.each(pageOfData, function(idx, el){
+ var data = {
+ uri: el.lsoa['value'],
+ label: el.label['value'],
+ lat: parseFloat(el.lat['value']),
+ lng: parseFloat(el['long']['value']),
+ score: parseFloat(el.score['value'])
+ }
+ var lsoaNotation = el.notation['value'];
+ setLsoaData(tile, lsoaNotation, data);
+ });
+
+ if (pageOfData.length == pageSize) {
+ // this page was full. There might be more.
+ page += 1;
+ callAjaxSparqlPaging(sparql);
+ } else {
+ // no more pages.
+ tilesRetrieved+=1;
+ if (tilesRetrieved == tiles.length) {
+ // we've got all the lsoaData.
+ $(self).trigger('lsoaDataRetrieved');
+ }
+ }
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ window.swirrl.log("SPARQL Fail: " + errorThrown + " " + textStatus);
+ $(self).trigger('dataError');
+ },
+ dataType: 'json',
+ timeout: 10000 // timeout after a short time.
+ }
+ );
+ };
+
+ // for each tile, get all the pages of data.
+ $.each(tiles, function(i, tile){
+ var sparql = buildSparql(tile);
+ callAjaxSparqlPaging(sparql, tile);
+ });
+ };
+
+ // add the MapManager to the swirrl namespace.
+ window.swirrl.MapManager = MapManager;
+})();
84 map.html
View
@@ -7,11 +7,17 @@
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false&region=GB"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script src="javascripts/raphael.js"></script>
+
+ <script type="text/javascript" src="javascripts/swirrl.js"></script>
+ <script type="text/javascript" src="javascripts/swirrl/map-manager.js"></script>
</head>
<body>
<div id="container">
<div id="info"></div>
<div id="map_canvas"></div>
+ <div id="map_canvas"></div>
+ <div id="zoom_warning" style="display:none"><div class="words">Zoom-level too wide. Please zoom in to see the LSOAs</div></div>
+ <div id="busy_notice" style="display:none"><img src="images/working.gif"/><div class="words">Working...</div></div>
<select id="domain">
<option value="IMD-score">Combined Score</option>
<option value="IMD-housing-score">Housing</option>
@@ -30,35 +36,63 @@
<script type="text/javascript">
(function(){
- var map = new google.maps.Map(document.getElementById("map_canvas"), {
- zoom: 14,
- center: new google.maps.LatLng(53.48, -2.245), // Manchester
- mapTypeId: google.maps.MapTypeId.ROADMAP,
- streetViewControl: false
+ var mapManager
+ , idleListener = null
+ , startTime
+ , map = new google.maps.Map(document.getElementById("map_canvas"), {
+ zoom: 14,
+ center: new google.maps.LatLng(53.48, -2.245), // Manchester
+ mapTypeId: google.maps.MapTypeId.ROADMAP,
+ streetViewControl: false
+ });
+
+ var mapManager = new swirrl.MapManager(map, "IMD-score"); // initialize with the overall score.
+
+ // Handle events coming out of the Map Manager.
+ $(mapManager).bind('started', function() {
+ startTime = new Date();
+ if(idleListener) {
+ google.maps.event.removeListener(idleListener);
+ idleListener = null;
+ }
+ $("#busy_notice").show();
});
- })();
- // this is the SPARQL we want to run.
+ $(mapManager).bind('finished', function() {
+ window.swirrl.log('busy duration: ' + (new Date() - startTime) + ' ms');
+ $("#busy_notice").hide();
+ bindMapIdle();
+ });
- // PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>
- // PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
+ // show warning if zoom too wide
+ $(mapManager).bind('zoomTooWide', function() {
+ if (!$("#zoom_warning").is(":visible")) {
+ $("#zoom_warning").show();
+ }
+ });
+
+ // hide warning if zoom oK
+ $(mapManager).bind('zoomOK', function() {
+ if ($("#zoom_warning").is(":visible")) {
+ $("#zoom_warning").hide();
+ }
+ });
- // SELECT ?lsoa ?notation ?label ?lat ?long ?score
- // WHERE {
- // GRAPH <http://opendatacommunities.org/id/graph/geography/lsoa> {
- // ?lsoa a <http://opendatacommunities.org/def/geography#LSOA> .
- // ?lsoa geo:lat ?lat .
- // ?lsoa geo:long ?long .
- // ?lsoa <http://www.w3.org/2004/02/skos/core#notation> ?notation .
- // ?lsoa rdfs:label ?label .
- // }
- // GRAPH <http://opendatacommunities.org/id/graph/IMD/2010/IMD-score> {
- // ?obs <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?lsoa .
- // ?obs <http://opendatacommunities.org/def/IMD#IMD-score> ?score .
- // }
- // FILTER ( ?lat >= 53.4 && ?lat < 53.5 &&
- // ?long >= -2.3 && ?long < -2.2 ) .
- // }
+ // function to wire up the idleListener
+ var bindMapIdle = function() {
+ // if we don't already have an idle-listener, bind one up.
+ // (fires when the map bounds haven't changed for a bit)
+ if (!idleListener) {
+ idleListener = google.maps.event.addListener(map, 'idle', function(e) {
+ window.swirrl.log('refreshing map');
+ mapManager.refresh();
+ });
+ }
+ }
+
+ // Finally, start listening to Map-Idle events.
+ bindMapIdle();
+ })();
</script>
Please sign in to comment.
Something went wrong with that request. Please try again.