Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add a MapManager to build and execute the SPARQL query

  • Loading branch information...
commit 17e211768c84476340477e5e29237bb1ed550790 1 parent e4f32bc
Ric Roberts authored January 19, 2012
307  javascripts/swirrl/map-manager.js
... ...
@@ -0,0 +1,307 @@
  1
+(function() {
  2
+
  3
+  // the constructor.
  4
+  var MapManager = function(googleMap, initialScoreDomain) {
  5
+    self = this;
  6
+    map = googleMap;
  7
+    scoreDomain = initialScoreDomain;
  8
+
  9
+    $(this).bind('lsoaDataRetrieved', function() {
  10
+      lsoaDataRetrieved = true;
  11
+
  12
+      // TODO: this is where we're refresh the polygons on the map, later.
  13
+
  14
+      // for now, just log out the lsoa data, and say we've done
  15
+      window.swirrl.log(lsoaData);
  16
+
  17
+      $(self).trigger('finished');
  18
+
  19
+    });
  20
+
  21
+    // if there was an error getting any data, finish the request,
  22
+    // but don't remember the tiles ... so we can try again next time.
  23
+    $(this).bind('dataError', function() {
  24
+      // we only want to do this once - but getting boundaries or lsoa data (sparql) could raise this
  25
+      if (!errored) {
  26
+        errored = true;
  27
+        window.swirrl.log('data error!');
  28
+        prevTiles = []; // blank out the prevTiles
  29
+        $(this).trigger('finished'); // tell people we're done
  30
+      }
  31
+    });
  32
+
  33
+  }
  34
+
  35
+  // public API
  36
+  MapManager.prototype = {
  37
+    refresh: function() {
  38
+      $(this).trigger('started');
  39
+      errored = false;
  40
+
  41
+      // clear these variables for this refresh
  42
+      if (map.getZoom() > minZoom) {
  43
+        $(this).trigger('zoomOK');
  44
+
  45
+        var tiles = getTiles();
  46
+        deleteOldTilesData(tiles);
  47
+        newTiles = getNewTiles(tiles);
  48
+
  49
+        prevTiles = tiles; // remember this set of tiles for comparison next time.
  50
+
  51
+        // TODO: also get the boundary data.
  52
+
  53
+        lsoaDataRetrieved = false;
  54
+        getLsoaData(newTiles);
  55
+
  56
+      } else {
  57
+        $(this).trigger('zoomTooWide');
  58
+        $(this).trigger('finished');
  59
+      }
  60
+    }
  61
+  }
  62
+
  63
+  // private variables
  64
+  var map
  65
+    , errored = false
  66
+    , prevTiles = []
  67
+    , scoreDomain
  68
+    , lsoaData = {} // map of tiles -> data
  69
+    , lsoaDataRetrieved = false
  70
+    , minZoom = 12
  71
+    , self;
  72
+
  73
+  // private functions
  74
+  var tileInArray = function(tile, tileArray) {
  75
+    var inArray = false;
  76
+    for(var i=0; i < tileArray.length; i++) {
  77
+      t = tileArray[i];
  78
+      if (t[0][0] == tile[0][0] && t[0][1] == tile[0][1] &&
  79
+        t[1][0] == tile[1][0] && t[1][1] == tile[1][1]) {
  80
+        inArray = true;
  81
+        break;
  82
+      }
  83
+    }
  84
+    return inArray;
  85
+  };
  86
+
  87
+  var deleteOldTilesData = function(tiles) {
  88
+    for(var i = 0; i < prevTiles.length; i++) {
  89
+      var prevTile = prevTiles[i];
  90
+      if (!tileInArray(prevTile, tiles)) {
  91
+        window.swirrl.log('tile data not needed', prevTile);
  92
+
  93
+        // TODO: also delete boundary data, when we have some.
  94
+
  95
+        delete lsoaData[prevTile];
  96
+      }
  97
+    }
  98
+  };
  99
+
  100
+  var getNewTiles = function(tiles) {
  101
+    var newTiles = [];
  102
+    for(var i = 0; i < tiles.length; i++) {
  103
+      var tile = tiles[i];
  104
+      if (!tileInArray(tile, prevTiles)) {
  105
+        newTiles.push(tile);
  106
+      }
  107
+    }
  108
+    window.swirrl.log('no of new tiles', newTiles.length);
  109
+    window.swirrl.log('new tiles', newTiles);
  110
+    return newTiles;
  111
+  };
  112
+
  113
+  var setLsoaData = function(tile, lsoaNotation, data) {
  114
+     if (!lsoaData[tile]) {
  115
+      lsoaData[tile] = {};
  116
+    }
  117
+    lsoaData[tile][lsoaNotation] = data;
  118
+  };
  119
+
  120
+  // work out the 0.1lat/long tiles that are convered by the current bounds
  121
+  var getTiles = function() {
  122
+
  123
+    var divideLatByTen = function(latx10) {
  124
+      var lat = latx10.substring(0,2);
  125
+      if (latx10.length > 2) {
  126
+        lat += "." + latx10.substring(2);
  127
+      }
  128
+      return lat;
  129
+    };
  130
+
  131
+    // for longs we have to account for the minus sign.
  132
+    var divideLongByTen = function(longx10){
  133
+      var lng = "";
  134
+      if (longx10[0] == "-") {
  135
+        // negative values
  136
+        if (longx10.length == 2){
  137
+          lng = "-0." + longx10[1];
  138
+        } else {
  139
+          lng = longx10.substring(0,2);
  140
+          if (longx10.length > 2) {
  141
+            lng += "." + longx10.substring(2);
  142
+          }
  143
+        }
  144
+      } else {
  145
+        // positive values
  146
+        if (longx10.length == 1){
  147
+          lng = "0." + longx10[0];
  148
+        } else {
  149
+          lng = longx10.substring(0,1);
  150
+          if (longx10.length > 1) {
  151
+            lng += "." + longx10.substring(1);
  152
+          }
  153
+        }
  154
+      }
  155
+
  156
+      // edge case: convert -0.0 to 0.0
  157
+      if (lng == "-0.0") {
  158
+        lng = "0.0";
  159
+      }
  160
+
  161
+      return lng;
  162
+    };
  163
+
  164
+    var getTileBounds = function(lowerLatx10, lowerLongx10) {
  165
+      // make sure they're all strings.
  166
+
  167
+      var upperLatx10 = (lowerLatx10 + 1).toString();
  168
+      var upperLongx10 = (lowerLongx10 + 1).toString();
  169
+      lowerLatx10 = lowerLatx10.toString();
  170
+      lowerLongx10 = lowerLongx10.toString();
  171
+
  172
+      var lowerLat = divideLatByTen(lowerLatx10);
  173
+      var upperLat = divideLatByTen(upperLatx10);
  174
+      var lowerLong = divideLongByTen(lowerLongx10);
  175
+      var upperLong = divideLongByTen(upperLongx10);
  176
+
  177
+      var tileBounds = [ [lowerLat, lowerLong], [upperLat, upperLong] ];
  178
+      window.swirrl.log(tileBounds);
  179
+      return tileBounds;
  180
+    };
  181
+
  182
+    // work out the 0.1lat/long tiles that are convered by the current bounds
  183
+    var northEast = map.getBounds().getNorthEast();
  184
+    var southWest = map.getBounds().getSouthWest();
  185
+
  186
+    // these are the outer bounds, rounded to 0.1 extremities.
  187
+    // we work with everything scaled up to 10 times the size to avoid floating point probs.
  188
+    var lowerLatx10 = Math.floor(southWest.lat() * 10);
  189
+    var upperLatx10 = Math.floor((northEast.lat()+0.1) * 10);
  190
+    var lowerLongx10 = Math.floor(southWest.lng() * 10);
  191
+    var upperLongx10 = Math.floor((northEast.lng()+0.1) * 10);
  192
+
  193
+    var tiles = [];
  194
+    var lat = lowerLatx10;
  195
+
  196
+    while (lat <= (upperLatx10-1)) {
  197
+      var lng = lowerLongx10;
  198
+      while (lng <= (upperLongx10 -1)) {
  199
+        tiles.push(getTileBounds(lat,lng));
  200
+        lng += 1;
  201
+      }
  202
+      lat += 1;
  203
+    }
  204
+
  205
+    return tiles;
  206
+  };
  207
+
  208
+  // get the LSOA data from the database.
  209
+  var getLsoaData = function(tiles) {
  210
+
  211
+    // define this inside this closure - it's not useful outside the scope of this func
  212
+    var buildSparql = function(tile) {
  213
+
  214
+      var lowerLat = tile[0][0];
  215
+      var lowerLong = tile[0][1];
  216
+      var upperLat = tile[1][0];
  217
+      var upperLong = tile[1][1];
  218
+
  219
+      var sparql = "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> " +
  220
+      "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> " +
  221
+      "SELECT ?lsoa ?notation ?label ?lat ?long ?score" +
  222
+      " WHERE { " +
  223
+      "  GRAPH <http://opendatacommunities.org/id/graph/geography/lsoa> { " +
  224
+      "    ?lsoa a <http://opendatacommunities.org/def/geography#LSOA> . " +
  225
+      "    ?lsoa geo:lat ?lat . " +
  226
+      "    ?lsoa geo:long ?long . " +
  227
+      "    ?lsoa <http://www.w3.org/2004/02/skos/core#notation> ?notation . " +
  228
+      "    ?lsoa rdfs:label ?label . " +
  229
+      "  } " +
  230
+      "  GRAPH <http://opendatacommunities.org/id/graph/IMD/2010/" + scoreDomain + "> {  " +
  231
+      "    ?obs <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?lsoa . " +
  232
+      "    ?obs <http://opendatacommunities.org/def/IMD#" + scoreDomain + "> ?score . " +
  233
+      "  } " +
  234
+      "  FILTER ( ?lat >= " + lowerLat + " && " +
  235
+      "    ?lat < " + upperLat + " && " +
  236
+      "    ?long >= " + lowerLong + " && " +
  237
+      "    ?long < " + upperLong + " ) . " +
  238
+      "}";
  239
+      return sparql;
  240
+
  241
+    };
  242
+
  243
+    var noOfTiles = tiles.length;
  244
+
  245
+    // nothing to do.
  246
+    if(noOfTiles == 0) {
  247
+      $(self).trigger('lsoaDataRetrieved');
  248
+    }
  249
+
  250
+    var tilesRetrieved = 0;
  251
+
  252
+    var page = 1;
  253
+    var pageSize = 1000;
  254
+
  255
+    var callAjaxSparqlPaging = function(sparql, tile) {
  256
+      var queryUrl = "http://opendatacommunities.org/sparql.json?_page=" + page.toString() + "&_per_page=" + pageSize.toString() + "&query=" + encodeURIComponent(sparql);
  257
+      window.swirrl.log('about to call', queryUrl);
  258
+      $.ajax(
  259
+        queryUrl,
  260
+        {
  261
+          success: function(data, textStatus, jqXHR) {
  262
+            var pageOfData = data.results.bindings;
  263
+            $.each(pageOfData, function(idx, el){
  264
+              var data = {
  265
+                uri: el.lsoa['value'],
  266
+                label: el.label['value'],
  267
+                lat: parseFloat(el.lat['value']),
  268
+                lng: parseFloat(el['long']['value']),
  269
+                score: parseFloat(el.score['value'])
  270
+              }
  271
+              var lsoaNotation = el.notation['value'];
  272
+              setLsoaData(tile, lsoaNotation, data);
  273
+            });
  274
+
  275
+            if (pageOfData.length == pageSize) {
  276
+              // this page was full. There might be more.
  277
+              page += 1;
  278
+              callAjaxSparqlPaging(sparql);
  279
+            } else {
  280
+              // no more pages.
  281
+              tilesRetrieved+=1;
  282
+              if (tilesRetrieved == tiles.length) {
  283
+                // we've got all the lsoaData.
  284
+                $(self).trigger('lsoaDataRetrieved');
  285
+              }
  286
+            }
  287
+          },
  288
+          error: function(jqXHR, textStatus, errorThrown) {
  289
+            window.swirrl.log("SPARQL Fail: " + errorThrown + " " + textStatus);
  290
+            $(self).trigger('dataError');
  291
+          },
  292
+          dataType: 'json',
  293
+          timeout: 10000 // timeout after a short time.
  294
+        }
  295
+      );
  296
+    };
  297
+
  298
+    // for each tile, get all the pages of data.
  299
+    $.each(tiles, function(i, tile){
  300
+      var sparql = buildSparql(tile);
  301
+      callAjaxSparqlPaging(sparql, tile);
  302
+    });
  303
+  };
  304
+
  305
+  // add the MapManager to the swirrl namespace.
  306
+  window.swirrl.MapManager = MapManager;
  307
+})();
84  map.html
@@ -7,11 +7,17 @@
7 7
   <script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false&region=GB"></script>
8 8
   <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
9 9
   <script src="javascripts/raphael.js"></script>
  10
+
  11
+  <script type="text/javascript" src="javascripts/swirrl.js"></script>
  12
+  <script type="text/javascript" src="javascripts/swirrl/map-manager.js"></script>
10 13
 </head>
11 14
 <body>
12 15
   <div id="container">
13 16
     <div id="info"></div>
14 17
     <div id="map_canvas"></div>
  18
+    <div id="map_canvas"></div>
  19
+    <div id="zoom_warning" style="display:none"><div class="words">Zoom-level too wide. Please zoom in to see the LSOAs</div></div>
  20
+    <div id="busy_notice" style="display:none"><img src="images/working.gif"/><div class="words">Working...</div></div>
15 21
     <select id="domain">
16 22
       <option value="IMD-score">Combined Score</option>
17 23
       <option value="IMD-housing-score">Housing</option>
@@ -30,35 +36,63 @@
30 36
 
31 37
   <script type="text/javascript">
32 38
     (function(){
33  
-      var map = new google.maps.Map(document.getElementById("map_canvas"), {
34  
-        zoom: 14,
35  
-        center: new google.maps.LatLng(53.48, -2.245), // Manchester
36  
-        mapTypeId: google.maps.MapTypeId.ROADMAP,
37  
-        streetViewControl: false
  39
+      var mapManager
  40
+        , idleListener = null
  41
+        , startTime
  42
+        , map = new google.maps.Map(document.getElementById("map_canvas"), {
  43
+            zoom: 14,
  44
+            center: new google.maps.LatLng(53.48, -2.245), // Manchester
  45
+            mapTypeId: google.maps.MapTypeId.ROADMAP,
  46
+            streetViewControl: false
  47
+          });
  48
+
  49
+      var mapManager = new swirrl.MapManager(map, "IMD-score"); // initialize with the overall score.
  50
+
  51
+      // Handle events coming out of the Map Manager.
  52
+      $(mapManager).bind('started', function() {
  53
+        startTime = new Date();
  54
+        if(idleListener) {
  55
+          google.maps.event.removeListener(idleListener);
  56
+          idleListener = null;
  57
+        }
  58
+        $("#busy_notice").show();
38 59
       });
39  
-    })();
40 60
 
41  
-    // this is the SPARQL we want to run.
  61
+      $(mapManager).bind('finished', function() {
  62
+        window.swirrl.log('busy duration: ' + (new Date() - startTime) + ' ms');
  63
+        $("#busy_notice").hide();
  64
+        bindMapIdle();
  65
+      });
42 66
 
43  
-    // PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>
44  
-    // PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
  67
+      // show warning if zoom too wide
  68
+      $(mapManager).bind('zoomTooWide', function() {
  69
+        if (!$("#zoom_warning").is(":visible")) {
  70
+          $("#zoom_warning").show();
  71
+        }
  72
+      });
  73
+
  74
+      // hide warning if zoom oK
  75
+      $(mapManager).bind('zoomOK', function() {
  76
+        if ($("#zoom_warning").is(":visible")) {
  77
+          $("#zoom_warning").hide();
  78
+        }
  79
+      });
45 80
 
46  
-    // SELECT ?lsoa ?notation ?label ?lat ?long ?score
47  
-    // WHERE {
48  
-    //   GRAPH <http://opendatacommunities.org/id/graph/geography/lsoa> {
49  
-    //     ?lsoa a <http://opendatacommunities.org/def/geography#LSOA> .
50  
-    //     ?lsoa geo:lat ?lat .
51  
-    //     ?lsoa geo:long ?long .
52  
-    //     ?lsoa <http://www.w3.org/2004/02/skos/core#notation> ?notation .
53  
-    //     ?lsoa rdfs:label ?label .
54  
-    //   }
55  
-    //   GRAPH <http://opendatacommunities.org/id/graph/IMD/2010/IMD-score> {
56  
-    //     ?obs <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?lsoa .
57  
-    //     ?obs <http://opendatacommunities.org/def/IMD#IMD-score> ?score .
58  
-    //   }
59  
-    //   FILTER ( ?lat >= 53.4 && ?lat < 53.5 &&
60  
-    //     ?long >= -2.3 && ?long < -2.2 ) .
61  
-    // }
  81
+      // function to wire up the idleListener
  82
+      var bindMapIdle = function() {
  83
+        // if we don't already have an idle-listener, bind one up.
  84
+        // (fires when the map bounds haven't changed for a bit)
  85
+        if (!idleListener) {
  86
+          idleListener = google.maps.event.addListener(map, 'idle', function(e) {
  87
+            window.swirrl.log('refreshing map');
  88
+            mapManager.refresh();
  89
+          });
  90
+        }
  91
+      }
  92
+
  93
+      // Finally, start listening to Map-Idle events.
  94
+      bindMapIdle();
  95
+    })();
62 96
 
63 97
   </script>
64 98
 

0 notes on commit 17e2117

Please sign in to comment.
Something went wrong with that request. Please try again.