diff options
-rw-r--r-- | .gitmodules | 9 | ||||
-rwxr-xr-x | create-data.js | 123 | ||||
-rw-r--r-- | examples/pointofservice.html | 228 | ||||
m--------- | resources/jquery | 0 | ||||
m--------- | resources/leaflet-sidebar | 0 | ||||
m--------- | resources/typeahead.js | 0 | ||||
-rw-r--r-- | src/leaflet-soton.js | 175 |
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'; |