summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Baines <cb15g11@soton.ac.uk>2014-08-04 19:04:48 +0100
committerChristopher Baines <cb15g11@soton.ac.uk>2014-08-06 21:45:15 +0100
commitdeb8f4ac1eb06221783ead9ff71bbddf6823c651 (patch)
treeda558bc16a046d0ba3580e44906d5cf9fd5495da
parent38f458453ba0c99554565f5ff568820a044dd203 (diff)
downloadleaflet-soton-deb8f4ac1eb06221783ead9ff71bbddf6823c651.tar
leaflet-soton-deb8f4ac1eb06221783ead9ff71bbddf6823c651.tar.gz
Add points of service
Currently, this is limited to food outlets. Not all food outlets display, as some are missing from OSM/Univeristy data. Other modifications include, better support custom display for the interactive information. This also includes an example, that uses jquery, typeahead and leaflet-sidebar.
-rw-r--r--.gitmodules9
-rwxr-xr-xcreate-data.js123
-rw-r--r--examples/pointofservice.html228
m---------resources/jquery0
m---------resources/leaflet-sidebar0
m---------resources/typeahead.js0
-rw-r--r--src/leaflet-soton.js175
7 files changed, 485 insertions, 50 deletions
diff --git a/.gitmodules b/.gitmodules
index 4c35d3b..9bd7205 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -28,3 +28,12 @@
[submodule "resources/leaflet-textpath"]
path = resources/leaflet-textpath
url = https://github.com/makinacorpus/Leaflet.TextPath.git
+[submodule "resources/typeahead.js"]
+ path = resources/typeahead.js
+ url = https://github.com/twitter/typeahead.js.git
+[submodule "resources/leaflet-sidebar"]
+ path = resources/leaflet-sidebar
+ url = https://github.com/Turbo87/leaflet-sidebar.git
+[submodule "resources/jquery"]
+ path = resources/jquery
+ url = https://github.com/jquery/jquery.git
diff --git a/create-data.js b/create-data.js
index d6eda79..fc956ec 100755
--- a/create-data.js
+++ b/create-data.js
@@ -50,6 +50,37 @@ pgql.connect('tcp://' + config.user + ':' +
createTables,
// Get the data from these tables
createCollections,
+ function(collections, callback) {
+
+ async.each(collections.pointsOfService.features, function(feature, callback) {
+
+ if ("uri" in feature.properties) {
+ async.parallel([
+ function(callback) {
+ getOfferings(feature.properties.uri, function(err, offerings) {
+ feature.properties.offerings = offerings;
+
+ callback();
+ });
+ },
+ function(callback) {
+ getDescription(feature.properties.uri, function(err, description) {
+ feature.properties.description = description;
+
+ callback();
+ });
+ }],
+ callback
+ );
+ } else {
+ console.warn("missing uri for point of service");
+
+ callback();
+ }
+ }, function(err) {
+ callback(null, collections);
+ });
+ },
// Now the basic collections have been created, handle the more complicated
// ones:
// - busStops
@@ -192,7 +223,8 @@ function createCollections(callback) {
parking: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,\
name,access,capacity,"capacity:disabled",fee from uni_parking',
bicycleParking: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,capacity,bicycle_parking,covered from uni_bicycle_parking',
- sites: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,name,loc_ref,uri from uni_site'
+ sites: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,name,loc_ref,uri from uni_site',
+ pointsOfService: "select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,ST_AsText(ST_Transform(ST_Centroid(way), 4326)) as center,name,shop,amenity,uri from planet_osm_polygon where (amenity in ('cafe', 'bar', 'restaurant') or shop in ('kiosk', 'convenience')) and ST_Contains((select ST_Union(way) from uni_site), way);"
};
var names = Object.keys(collectionQueries);
@@ -243,6 +275,7 @@ function createCollection(name, query, callback) {
var center = feature.properties.center;
center = center.slice(6, -1);
center = center.split(" ").reverse();
+ center = center.map(parseFloat);
feature.properties.center = center;
}
@@ -523,6 +556,90 @@ function getLibraryData(library_data, callback) {
}));
}
+function getOfferings(uri, callback) {
+
+ var query = "PREFIX ns0: <http://purl.org/goodrelations/v1#>\
+ PREFIX oo: <http://purl.org/openorg/>\
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+ SELECT DISTINCT ?includes ?label ?section ?sectionLabel WHERE {\
+ ?offering a ns0:Offering ;\
+ ns0:includes ?includes ;\
+ ns0:availableAtOrFrom ?pos ;\
+ oo:priceListSection ?section .\
+ ?includes rdfs:label ?label .\
+ ?section rdfs:label ?sectionLabel\
+ FILTER (\
+ ?pos = <URI>\
+ )\
+ }";
+
+ query = query.replace("URI", uri);
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ var offerings = {};
+
+ data.results.bindings.forEach(function(result) {
+ var section = result.section.value;
+
+ var item = {
+ label: result.label.value,
+ uri: result.includes.value
+ };
+
+ if (section in offerings) {
+ offerings[section].items.push(item);
+ } else {
+ offerings[section] = {
+ label: result.sectionLabel.value,
+ items: [ item ]
+ };
+ }
+ });
+
+ callback(null, offerings);
+ });
+}
+
+function getDescription(uri, callback) {
+
+ var query = "PREFIX dcterms: <http://purl.org/dc/terms/>\
+ SELECT ?description WHERE {\
+ ?uri dcterms:description ?description;\
+ FILTER (\
+ ?uri = <URI>\
+ )\
+ }";
+
+ query = query.replace("URI", uri);
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ var b = data.results.bindings;
+
+ var desc;
+ if (b.length == 0) {
+ desc = "";
+ } else {
+ desc = b[0].description.value
+ }
+
+ callback(null, desc);
+ });
+}
+
function createBuildingParts(buildings, callback) {
console.info("creating buildingParts collection");
@@ -676,6 +793,10 @@ function createBuildingParts(buildings, callback) {
for (var i=1; i<parts.length; i++) {
var partLevels = parts[i].properties.level;
+ if (typeof(partLevels) === "undefined") {
+ continue;
+ }
+
if (typeof(partLevels) === "number") {
partLevels = [ partLevels ];
}
diff --git a/examples/pointofservice.html b/examples/pointofservice.html
new file mode 100644
index 0000000..5a431fd
--- /dev/null
+++ b/examples/pointofservice.html
@@ -0,0 +1,228 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Points of Service</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+
+ <link rel="stylesheet" href="../src/leaflet-soton.css" />
+
+ <link rel="stylesheet" href="../resources/leaflet-sidebar/src/L.Control.Sidebar.css" />
+ <link rel="stylesheet" href="../resources/leaflet/dist/leaflet.css" />
+
+ <style>
+ body {
+ padding: 0;
+ margin: 0;
+ }
+
+ html, body, #map {
+ height: 100%;
+ }
+ .typeahead, .tt-query, .tt-hint {
+ border: 2px solid #ccc;
+ border-radius: 8px;
+ font-size: 18px;
+ height: 30px;
+ line-height: 30px;
+ outline: medium none;
+ padding: 8px 12px;
+ width: 90%;
+ }
+ .typeahead {
+ background-color: #fff;
+ }
+ .twitter-typeahead {
+ width: 100%
+ }
+ .typeahead:focus {
+ border: 2px solid #0097cf;
+ }
+ .tt-query {
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset;
+ }
+ .tt-hint {
+ color: #999;
+ }
+ .tt-dropdown-menu {
+ background-color: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 8px;
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ margin-top: 12px;
+ padding: 8px 0;
+ width: 385px;
+ max-height: 150px;
+ overflow-y: auto;
+ }
+ .tt-suggestion {
+ font-size: 18px;
+ line-height: 24px;
+ padding: 3px 20px;
+ }
+ .tt-suggestion.tt-cursor {
+ background-color: #0097cf;
+ color: #fff;
+ }
+ .tt-suggestion p {
+ margin: 0;
+ }
+ </style>
+</head>
+<body>
+ <div id="map"></div>
+
+ <div id="sidebar">
+ <h1>Points of Service</h1>
+
+ <input id="search" class="typeahead" type="text" placeholder="What would you like to eat or drink?">
+
+ <div id="availablefrom">
+ </div>
+
+ <div id="info">
+
+ </div>
+ </div>
+
+ <script src="../resources/leaflet/dist/leaflet.js"></script>
+ <script src="../resources/leaflet-markercluster/dist/leaflet.markercluster.js"></script>
+ <script src="../resources/leaflet-indoor/leaflet-indoor.js"></script>
+ <script src="../resources/leaflet-sidebar/src/L.Control.Sidebar.js"></script>
+ <script src="../resources/jquery/dist/jquery.min.js"></script>
+ <script src="../resources/typeahead.js/dist/typeahead.bundle.min.js"></script>
+
+ <script src="../src/leaflet-soton.js"></script>
+
+ <script type="text/javascript">
+ LS.imagePath = '../resources/images/';
+ LS.dataPath = '../data.json';
+
+ var map = LS.map('map', {
+ indoor: false,
+ });
+
+ var sidebar = L.control.sidebar('sidebar', {
+ position: 'left'
+ });
+
+ map.addControl(sidebar);
+
+ setTimeout(function () {
+ sidebar.show();
+ }, 500);
+
+ var info = document.getElementById("info");
+
+ map.showInfo = function(content, latlng, options) {
+ info.innerHTML = "";
+ info.appendChild(content);
+ sidebar.show();
+ };
+
+ LS.getData(function(data) { var layer = LS.getPointOfServiceLayer();
+ layer.addTo(map);
+
+ var substringMatcher = function(strs) {
+ return function findMatches(q, cb) {
+ var matches, substrRegex;
+ // an array that will be populated with substring matches
+ matches = [];
+
+ // regex used to determine if a string contains the substring `q`
+ substrRegex = new RegExp(q, 'i');
+
+ // iterate through the pool of strings and for any string that
+ // contains the substring `q`, add it to the `matches` array
+ $.each(strs, function(i, str) {
+ if (substrRegex.test(str)) {
+ // the typeahead jQuery plugin expects suggestions to a
+ // JavaScript object, refer to typeahead docs for more info
+ matches.push({ value: str });
+ }
+ });
+
+ cb(matches);
+ };
+ };
+
+ var itemMap = {};
+
+ data.pointsOfService.features.forEach(function(feature) {
+ if ("offerings" in feature.properties) {
+ var offerings = feature.properties.offerings;
+
+ var sections = Object.keys(offerings);
+
+ sections.forEach(function(sectionURI) {
+ var section = offerings[sectionURI];
+
+ section.items.forEach(function(item) {
+ var obj = {
+ uri: item.uri,
+ feature: feature
+ };
+
+ if (item.label in itemMap) {
+ itemMap[item.label].push(obj);
+ } else {
+ itemMap[item.label] = [ obj ];
+ }
+ });
+ });
+ }
+ });
+
+ var items = Object.keys(itemMap);
+
+ var $search = $('#search');
+
+ $search.typeahead(
+ {
+ hint: true,
+ highlight: true,
+ minLength: 1
+ },
+ {
+ name: 'states',
+ displayKey: 'value',
+ source: substringMatcher(items)
+ }
+ );
+
+ var availableFrom = document.getElementById("availablefrom");
+
+ $search.keyup(function() {
+ var val = $search.val();
+
+ if (val in itemMap) {
+ var uris = itemMap[val];
+
+ availableFrom.innerHTML = "";
+
+ var ul = document.createElement("ul");
+
+ uris.forEach(function(result) {
+ var feature = result.feature;
+ console.log(feature.properties);
+ var li = document.createElement("li");
+
+ var a = document.createElement("a");
+ a.textContent = feature.properties.name; //+ " (" + result.uri + ")";
+ a.href = "#";
+ a.onclick = function() {
+ console.log(feature.properties.uri);
+ map.show(feature.properties.uri);
+ return false;
+ };
+
+ li.appendChild(a);
+ ul.appendChild(li);
+ });
+
+ availableFrom.appendChild(ul);
+ }
+ });
+ });
+ </script>
+</body>
+</html>
diff --git a/resources/jquery b/resources/jquery
new file mode 160000
+Subproject 4dec426aa2a6cbabb1b064319ba7c272d594a68
diff --git a/resources/leaflet-sidebar b/resources/leaflet-sidebar
new file mode 160000
+Subproject 39c89d3b5d60ff0eaacdd8c91192ebbe42b4476
diff --git a/resources/typeahead.js b/resources/typeahead.js
new file mode 160000
+Subproject f835e162ec4551479114fd245c907ae032de692
diff --git a/src/leaflet-soton.js b/src/leaflet-soton.js
index 1d995f7..9e4b72f 100644
--- a/src/leaflet-soton.js
+++ b/src/leaflet-soton.js
@@ -108,7 +108,51 @@
var content = vendingPopupTemplate(feature.properties);
- showPopup(map, content, e.latlng, popupOptions);
+ // TODO: Unsure if map is accessible?
+ map.showInfo(content, e.latlng, popupOptions);
+ });
+ }
+ });
+
+ return layer;
+ },
+ getPointOfServiceLayer: function() {
+ var features = this.data.pointsOfService.features;
+
+ var pointFeatures = features.map(function(feature) {
+ var pointFeature = {
+ type: "Feature",
+ geometry: {
+ type: "Point"
+ },
+ properties: feature.properties
+ };
+
+ console.log(feature.properties);
+ var c = feature.properties.center;
+ c = [c[1], c[0]];
+ pointFeature.geometry.coordinates = c;
+
+ return pointFeature;
+ });
+
+ var layer = new L.GeoJSON(pointFeatures, {
+ pointToLayer: function(feature, latlng) {
+ var icon;
+
+ icon = icons.vendingHotDrinks;
+
+ return L.marker(latlng, {icon: icon});
+ },
+ onEachFeature: function(feature, layer) {
+ layer.on('click', function(e) {
+ var popupOptions = {
+ offset: icons.vendingHotDrinks.options.popupAnchor
+ };
+
+ var content = pointOfServiceTemplate(feature.properties);
+
+ map.showInfo(content, e.latlng, popupOptions);
});
}
});
@@ -375,7 +419,7 @@ SELECT * WHERE {\
map,
function() { close(); });
- close = showPopup(map, content, e.latlng);
+ close = map.showInfo(content, e.latlng);
});
};
} else {
@@ -384,7 +428,7 @@ SELECT * WHERE {\
layer.on('click', function(e) {
var content = popupTemplates[layerName](feature.properties);
- showPopup(map, content, e.latlng);
+ map.showInfo(content, e.latlng);
});
};
}
@@ -618,7 +662,7 @@ SELECT * WHERE {\
}
}
- showPopup(map, content, e.latlng, popupOptions);
+ map.showInfo(content, e.latlng, popupOptions);
});
},
pointToLayer: function (feature, latlng) {
@@ -742,7 +786,7 @@ SELECT * WHERE {\
map.panTo(center);
- close = showPopup(map, content, center);
+ close = map.showInfo(content, center);
return;
}
@@ -798,6 +842,49 @@ SELECT * WHERE {\
} else {
throw "unable to handle " + feature.geometry.type;
}
+ },
+ showInfo: function(content, latlng, options) {
+ options = options || {};
+
+ options.maxWidth = map.getContainer().offsetWidth;
+
+ var close;
+
+ if (false && smallScreen()) {
+ // Just in case there is a popup open, as the screen has just shrunk
+ map.closePopup();
+
+ var containerWrapper = document.getElementById('dynamicContentWrapper');
+ containerWrapper.style.display = 'block';
+
+ var container = document.getElementById('dynamicContent');
+
+ var contentDiv = document.createElement('div');
+
+ var closeButton = L.DomUtil.create('button', 'close', container);
+ closeButton.setAttribute('aria-hidden', 'true');
+ closeButton.setAttribute('type', 'button');
+ closeButton.textContent = 'x';
+
+ close = closeButton.onclick = function() {
+ container.innerHTML = '';
+ containerWrapper.style.display = 'none';
+ };
+
+ container.appendChild(content);
+ } else {
+ var popup = L.popup(options).setLatLng(latlng);
+
+ popup.setContent(content);
+
+ popup.openOn(map);
+
+ close = function() {
+ map.closePopup(popup);
+ };
+ }
+
+ return close;
}
});
@@ -819,50 +906,6 @@ SELECT * WHERE {\
return L.marker(latlng, {icon: icon});
}
- function showPopup(map, content, latlng, popupOptions) {
- popupOptions = popupOptions || {};
-
- popupOptions.maxWidth = map.getContainer().offsetWidth;
-
- var close;
-
- if (false && smallScreen()) {
- // Just in case there is a popup open, as the screen has just shrunk
- map.closePopup();
-
- var containerWrapper = document.getElementById('dynamicContentWrapper');
- containerWrapper.style.display = 'block';
-
- var container = document.getElementById('dynamicContent');
-
- var contentDiv = document.createElement('div');
-
- var closeButton = L.DomUtil.create('button', 'close', container);
- closeButton.setAttribute('aria-hidden', 'true');
- closeButton.setAttribute('type', 'button');
- closeButton.textContent = 'x';
-
- close = closeButton.onclick = function() {
- container.innerHTML = '';
- containerWrapper.style.display = 'none';
- };
-
- container.appendChild(content);
- } else {
- var popup = L.popup(popupOptions).setLatLng(latlng);
-
- popup.setContent(content);
-
- popup.openOn(map);
-
- close = function() {
- map.closePopup(popup);
- };
- }
-
- return close;
- }
-
// Template functions for creating the popups
var popupTemplates = {
@@ -1304,6 +1347,40 @@ SELECT * WHERE {\
});
}
+ function pointOfServiceTemplate(properties) {
+ return getTemplateWrapper(properties, function(content) {
+
+ var description = document.createElement("div");
+ if ("description" in properties) {
+ description.innerHTML = properties.description;
+ } else {
+ description.textContent = "No Description Available";
+ }
+ content.appendChild(description);
+
+ if ("offerings" in properties) {
+ Object.keys(properties.offerings).forEach(function(sectionURI) {
+ var section = properties.offerings[sectionURI];
+
+ var header = document.createElement("h4");
+ header.textContent = section.label;
+
+ content.appendChild(header);
+
+ section.items.forEach(function(item) {
+ var a = document.createElement("a");
+
+ a.textContent = item.label;
+ a.href = item.uri;
+
+ content.appendChild(a);
+ content.appendChild(document.createElement("br"));
+ });
+ });
+ }
+ });
+ }
+
function parkingTemplate(properties) {
if (!('name' in properties))
properties.name = 'Car Park';