summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--.gitmodules18
-rw-r--r--.jshintrc3
-rw-r--r--config.json.default7
-rwxr-xr-xcreate-data.js1419
-rw-r--r--examples/basic.html34
-rw-r--r--examples/buildingsearch.html177
-rw-r--r--examples/full.html45
-rw-r--r--examples/index.html372
-rw-r--r--examples/indoor.html37
-rw-r--r--examples/search.html222
-rw-r--r--examples/workstations.html39
-rw-r--r--examples/zepler.html44
-rw-r--r--resources/images/candy.pngbin0 -> 1497 bytes
-rw-r--r--resources/images/coffee.pngbin0 -> 1131 bytes
-rw-r--r--resources/images/loader.gifbin0 -> 1849 bytes
-rw-r--r--resources/images/locate.pngbin0 -> 829 bytes
-rw-r--r--resources/images/locate.svg383
-rw-r--r--resources/images/locate_alt.pngbin0 -> 1127 bytes
-rw-r--r--resources/images/locate_alt.svg493
-rw-r--r--resources/images/locate_touch.pngbin0 -> 1095 bytes
-rw-r--r--resources/images/locate_touch_alt.pngbin0 -> 1438 bytes
-rwxr-xr-xresources/images/logos/stags-head.pngbin0 -> 10371 bytes
-rwxr-xr-xresources/images/logos/stags-head2.pngbin0 -> 3813 bytes
-rwxr-xr-xresources/images/logos/susu-cafe.pngbin0 -> 10145 bytes
-rwxr-xr-xresources/images/logos/susu-cafe2.pngbin0 -> 3733 bytes
-rwxr-xr-xresources/images/logos/susu-shop.pngbin0 -> 8662 bytes
-rwxr-xr-xresources/images/logos/susu-shop2.pngbin0 -> 3181 bytes
-rwxr-xr-xresources/images/logos/susu.pngbin0 -> 92944 bytes
-rwxr-xr-xresources/images/logos/susu2.pngbin0 -> 7387 bytes
-rwxr-xr-xresources/images/logos/the-bridge.pngbin0 -> 4921 bytes
-rwxr-xr-xresources/images/logos/the-bridge2.pngbin0 -> 2467 bytes
-rw-r--r--resources/images/printer.pngbin0 -> 740 bytes
-rw-r--r--resources/images/search-icon-mobile.pngbin0 -> 3959 bytes
-rw-r--r--resources/images/search-icon.pngbin0 -> 3834 bytes
-rw-r--r--resources/images/spinner.gifbin0 -> 1397 bytes
-rw-r--r--resources/images/toilets-f.pngbin0 -> 915 bytes
-rw-r--r--resources/images/toilets-m.pngbin0 -> 789 bytes
-rw-r--r--resources/images/toilets.pngbin0 -> 752 bytes
-rw-r--r--resources/images/toilets_disability.pngbin0 -> 1131 bytes
-rw-r--r--resources/images/workstation-big.pngbin0 -> 633 bytes
-rw-r--r--resources/images/workstation-closed.pngbin0 -> 601 bytes
-rw-r--r--resources/images/workstation-group.pngbin0 -> 514 bytes
-rw-r--r--resources/images/workstation.pngbin0 -> 557 bytes
m---------resources/leaflet0
m---------resources/leaflet-hash0
m---------resources/leaflet-locatecontrol0
m---------resources/leaflet-markercluster0
m---------resources/mfd-location0
m---------resources/syntaxhighlighter0
-rw-r--r--src/leaflet-soton.css275
-rw-r--r--src/leaflet-soton.js1851
52 files changed, 5425 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca72c4d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+data.json
+data-source.json
+config.json
+*.swp
+*~
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..d6ad98a
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,18 @@
+[submodule "resources/leaflet"]
+ path = resources/leaflet
+ url = https://github.com/Leaflet/Leaflet.git
+[submodule "resources/syntaxhighlighter"]
+ path = resources/syntaxhighlighter
+ url = https://github.com/alexgorbatchev/syntaxhighlighter.git
+[submodule "resources/leaflet-markercluster"]
+ path = resources/leaflet-markercluster
+ url = https://github.com/Leaflet/Leaflet.markercluster.git
+[submodule "resources/mfd-location"]
+ path = resources/mfd-location
+ url = git@sourcekettle.ecs.soton.ac.uk:projects/mfd-location.git
+[submodule "resources/leaflet-locatecontrol"]
+ path = resources/leaflet-locatecontrol
+ url = https://github.com/domoritz/leaflet-locatecontrol.git
+[submodule "resources/leaflet-hash"]
+ path = resources/leaflet-hash
+ url = https://github.com/mlevans/leaflet-hash.git
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..d4d6e48
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "multistr": true
+}
diff --git a/config.json.default b/config.json.default
new file mode 100644
index 0000000..8b0ee91
--- /dev/null
+++ b/config.json.default
@@ -0,0 +1,7 @@
+{
+ "user": "",
+ "password": "password",
+ "server": "localhost",
+ "port": "5432",
+ "database": "hampshire"
+}
diff --git a/create-data.js b/create-data.js
new file mode 100755
index 0000000..e236a99
--- /dev/null
+++ b/create-data.js
@@ -0,0 +1,1419 @@
+#!/usr/bin/env node
+
+/*
+ * This does two things, combines University Open Data, and the data from the
+ * osm2pgsql tables in the database to add more information back in to the
+ * database for the tile renderer. It also produces the static json files used
+ * by the web clients
+ */
+
+var S = require('string');
+S.extendPrototype();
+
+var fs = require('fs');
+var http = require("http");
+var async = require("async");
+var yaml = require('js-yaml');
+
+var config = require("./config.json");
+
+// Get document, or throw exception on error
+try {
+ var printers = yaml.safeLoad(fs.readFileSync('./resources/mfd-location/data.yaml', 'utf8'));
+} catch (e) {
+ console.error(e);
+ return;
+}
+
+var validationByURI = {};
+
+// prefix for the database tables
+var tablePrefix = "uni_";
+
+var pgql = require('pg');
+var pg = null;
+pgql.connect('tcp://' + config.user + ':' +
+ config.password + '@' +
+ config.server + ':' +
+ config.port + '/' +
+ config.database, function(err, client) {
+ if (err) {
+ console.error(err);
+ return;
+ }
+
+ pg = client;
+
+ async.series([
+ loadBusData, // TODO: At the moment this puts bus data in the database, that then gets pulled back out...
+ function(callback) {
+ createTables(function(err) {
+ createCollections(function(err, collections) {
+
+ async.parallel([
+ function(callback) {
+ var workstations = {};
+
+ var buildings = {};
+
+ collections.buildings.features.forEach(function(building) {
+ if ("uri" in building.properties) {
+ buildings[building.properties.uri] = building;
+ }
+ });
+
+ createRooms(buildings, workstations, function(err, buildingParts) {
+
+ collections.buildingParts = buildingParts;
+
+ async.parallel([
+ function(callback) {
+ getBuildingFeatures(buildings, function(err, buildingFeatures) {
+ collections.buildingFeatures = buildingFeatures;
+ callback(err);
+ });
+ },
+ function(callback) {
+ getUniWorkstations(workstations, function(err, workstations) {
+ collections.workstations = workstations;
+ callback(err);
+ });
+ }
+ ], callback);
+ });
+ },
+ function(callback) {
+ getBuildingImages(collections.buildings, callback);
+ }
+ ], function(err) {
+ if (err) console.error(err);
+
+ callback(err, collections);
+ });
+ });
+ });
+ }
+ ], function(err, results) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
+
+ var collections = results[1];
+
+ console.log("ending database connection");
+ pgql.end();
+ writeDataFiles(collections, function() {
+
+ Object.keys(validationByURI).sort().forEach(function(uri) {
+ if ("location" in validationByURI[uri].errors) {
+ //console.log(uri + " " + validationByURI[uri].errors.location);
+ console.log(uri + " location unknown");
+ }
+ });
+
+ console.log("complete");
+
+ process.exit(0);
+ });
+ });
+});
+
+function createCollections(callback) {
+ var collectionQueries = {
+ buildings: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as \
+ polygon,name,loc_ref,uri,leisure,height \
+ from uni_building where uri is not null',
+ 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',
+ busStops: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,name,uri,routes from uni_bus_stop',
+ busRoutes: 'select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,name,note,colour,ref from uni_bus_route'
+ };
+
+ var names = Object.keys(collectionQueries);
+
+ async.map(names, function(name, callback) {
+ createCollection(name, collectionQueries[name], callback);
+ }, function(err, newCollections) {
+ var collectionsObject = {};
+
+ for (var i in names) {
+ name = names[i];
+
+ collectionsObject[name] = {
+ type: "FeatureCollection",
+ features: newCollections[i]
+ };
+ }
+
+ callback(err, collectionsObject);
+ });
+}
+
+function createCollection(name, query, callback) {
+ var collection = [];
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error("Query: " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ async.map(results.rows, function(row, callback) {
+ var feature = {type: "Feature"};
+ feature.geometry = JSON.parse(row.polygon);
+ delete row.polygon;
+
+ feature.properties = row;
+
+ for (var key in feature.properties) {
+ if (feature.properties[key] === null) {
+ delete feature.properties[key];
+ }
+ }
+
+ if ("center" in feature.properties) {
+ var center = feature.properties.center;
+ center = center.slice(6, -1);
+ center = center.split(" ").reverse();
+ feature.properties.center = center;
+ }
+
+ /*if (name === "buildings") {
+ buildings[feature.properties.uri] = feature;
+ }*/
+
+ callback(err, feature);
+ }, callback);
+ });
+}
+
+function createTables(callback) {
+ var tableSelects = {
+ site: "select way,name,loc_ref,uri,amenity,landuse \
+ from planet_osm_polygon \
+ where operator='University of Southampton'",
+ building: 'select way,coalesce("addr:housename", name, \'\') as name,coalesce(height::int, "building:levels"::int * 10, 10) as height,loc_ref,leisure,uri, case when coalesce("addr:housename", name, \'\')=\'\' or "addr:housename"="addr:housenumber" then true else false end as minor from planet_osm_polygon where ST_Contains((select ST_Union(way) from uni_site), way) and building is not null order by z_order,way_area desc',
+ parking: 'select way,name,access,capacity,"capacity:disabled",fee from planet_osm_polygon where amenity=\'parking\' and ST_Contains((select ST_Union(way) from uni_site), way)',
+ bicycle_parking: "select way,capacity,bicycle_parking,covered from planet_osm_polygon where amenity='bicycle_parking' and ST_Contains((select ST_Union(way) from uni_site), way) union select way,capacity,bicycle_parking,covered from planet_osm_point where amenity='bicycle_parking' and ST_Contains((select ST_Union(way) from uni_site), way)"
+ };
+
+ // Create all the tables, these contain Universtiy relevant data that is
+ // both further queried, and used by sum-carto
+ async.eachSeries(Object.keys(tableSelects), function(table, callback) {
+ createTable(table, tableSelects[table], callback);
+ }, callback);
+}
+
+function createTable(name, query, callback) {
+ var tableName = tablePrefix + name;
+
+ console.log("creating table " + tableName);
+
+ pg.query("drop table if exists " + tableName, function(err, results) {
+ var fullQuery = "create table " + tableName + " as " + query;
+ pg.query(fullQuery, function(err, results) {
+ if (err) {
+ console.error("error creating table " + tableName);
+ console.error("query: " + fullQuery);
+ } else {
+ console.log("finished creating table " + tableName);
+ }
+ callback(err);
+ });
+ });
+}
+
+// buildings
+
+function getBuildingFeatures(buildings, callback) {
+ async.parallel([
+ function(callback) {
+ getPrinters(buildings, callback);
+ },
+ function(callback) {
+ getVendingMachines(buildings, callback);
+ }
+ ], function(err, results) {
+ var features = []
+ features = features.concat.apply(features, results);
+
+ var buildingFeatures = { type: "FeatureCollection", features: features };
+
+ callback(err, buildingFeatures);
+ });
+}
+
+function getBuildingImages(buildings, callback) {
+ console.log("getting building images");
+ async.each(buildings.features, function(building, callback) {
+ getImagesFor(building.properties.uri, function(err, images) {
+ building.properties.images = images;
+ callback(err);
+ });
+ }, callback);
+}
+
+// buildingParts
+
+function createRooms(buildings, workstations, callback) {
+ console.log("creating buildingParts collection");
+
+ async.parallel([getBuildingParts, getBuildingRelations],
+ function(err, results) {
+ var buildingParts = results[0];
+ var buildingRelations = results[1];
+
+ var buildingPartsByURI = {};
+
+ async.parallel([
+ function(callback) {
+ async.each(buildingParts, function(part, callback) {
+ if (part.properties.buildingpart === "room") {
+
+ if ("ref" in part.properties && !("uri" in part.properties)) {
+ console.warn("room missing URI " + JSON.stringify(part.properties));
+ }
+
+ if ("uri" in part.properties) {
+ buildingPartsByURI[part.properties.uri] = part;
+ }
+
+ async.parallel([
+ function(callback) {
+ findRoomFeatures(part, callback);
+ },
+ function(callback) {
+ findRoomContents(part, workstations, callback);
+ },
+ function(callback) {
+ findRoomImages(part, callback);
+ }], callback);
+ } else {
+ callback();
+ }
+ }, callback);
+ },
+ function(callback) {
+ var levelRelations = []
+
+ // Process level relations
+ async.each(buildingRelations, function(buildingRelation, callback) {
+ getLevelRelations(buildingRelation, function(err, newLevelRelations) {
+ levelRelations.push.apply(levelRelations, newLevelRelations);
+ callback();
+ });
+ }, function(err) {
+
+ osmIDToLevel = {};
+
+ async.each(levelRelations, function(level, callback) {
+ getBuildingPartMemberRefs(level, function(err, refs) {
+ for (var i=0; i<refs.length; i++) {
+ osmIDToLevel[refs[i]] = parseInt(level.tags.level, 10);
+ }
+ callback();
+ });
+ }, function(err) {
+ for (var i=0; i<buildingParts.length; i++) {
+ var part = buildingParts[i];
+
+ if (part.id in osmIDToLevel) {
+ part.properties.level = osmIDToLevel[part.id];
+ } else {
+ console.log("unknown level");
+ console.log(JSON.stringify(part, null, 4));
+ }
+ }
+ callback();
+ });
+ });
+ }], function(err) {
+
+ var query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\
+ PREFIX ns1: <http://vocab.deri.ie/rooms#>\
+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+ PREFIX spacerel: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>\
+ PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+ SELECT * WHERE {\
+ { ?room a ns1:Room ;\
+ rdf:type ?type ;\
+ rdfs:label ?label ;\
+ spacerel:within ?building .\
+ } UNION {\
+ ?room a soton:SyllabusLocation ;\
+ rdf:type ?type ;\
+ rdfs:label ?label ;\
+ spacerel:within ?building .\
+ }\
+ } GROUP BY ?room";
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.log("Query " + query);
+ console.error(err);
+ }
+
+ async.each(data.results.bindings, function(result, callback) {
+ var uri = result.room.value;
+ var type = result.type.value;
+ var label = result.label.value;
+ var building = result.building.value;
+
+ var feature;
+
+ if (uri in buildingPartsByURI) {
+ feature = buildingPartsByURI[uri];
+ } else {
+ feature = {
+ type: "Feature",
+ properties: {
+ uri: uri
+ }
+ };
+
+ var info = decomposeRoomURI(uri);
+
+ if (typeof(info) !== "undefined") {
+ feature.properties.ref = info.room;
+ feature.properties.level = info.level;
+ }
+
+ buildingParts.push(feature);
+ }
+
+ if (type === "http://id.southampton.ac.uk/ns/CentrallyBookableSyllabusLocation") {
+ feature.properties.teaching = true;
+ feature.properties.bookable = true;
+ } else if (type === "http://id.southampton.ac.uk/ns/SyllabusLocation") {
+ feature.properties.teaching = true;
+ feature.properties.bookable = false;
+ } else {
+ feature.properties.teaching = false;
+ feature.properties.bookable = false;
+ }
+
+ if (feature.properties.teaching && !("geometry" in feature)) {
+ addRoomMessage(uri, "errors", "location", "unknown (teaching)");
+ }
+
+ if (!("name" in feature.properties)) {
+ feature.properties.name = label;
+ }
+
+ if (building in buildings) {
+ var buildingProperties = buildings[building].properties;
+
+ if (!('rooms' in buildingProperties)) {
+ buildingProperties.rooms = {};
+ }
+
+ if ("level" in feature.properties) {
+ var level = feature.properties.level;
+
+ if (!(level in buildingProperties.rooms)) {
+ buildingProperties.rooms[level] = [];
+ }
+
+ buildingProperties.rooms[level].push(uri);
+ } else {
+ console.warn("no level for " + JSON.stringify(feature, null, 4));
+ }
+ } else {
+ addBuildingMessage(building, "errors", "location", "unknown (createRooms)");
+ }
+
+ callback();
+ }, function(err) {
+ callback(null, {
+ type: "FeatureCollection",
+ features: buildingParts
+ });
+ });
+ }); // SPARQL Query
+ }
+ ); // parallel
+ }
+ );
+}
+
+function getBuildingPartMemberRefs(levelRelation, callback) {
+ var partRefs = []
+
+ for (var i=0; i<levelRelation.members.length; i++) {
+ var member = levelRelation.members[i];
+
+ if (member.role === 'buildingpart') {
+ partRefs.push(member.ref);
+ }
+ }
+
+ callback(null, partRefs);
+}
+
+function getBuildingParts(callback) {
+ var query = "select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as polygon,ST_AsText(ST_Transform(ST_Centroid(way), 4326)) as center,osm_id,name,buildingpart,ref,uri,amenity,unisex,male,female from planet_osm_polygon where buildingpart is not null";
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error("Query: " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ async.map(results.rows, function(part, callback) {
+ var feature = {type: "Feature", id: part.osm_id};
+ feature.geometry = JSON.parse(part.polygon);
+ delete part.polygon;
+ delete part.osm_id;
+
+ feature.properties = part;
+
+ for (var key in feature.properties) {
+ if (feature.properties[key] === null) {
+ delete feature.properties[key];
+ }
+ }
+
+ if ("center" in feature.properties) {
+ var center = feature.properties.center;
+ center = center.slice(6, -1);
+ center = center.split(" ").reverse();
+ feature.properties.center = center;
+ }
+
+ callback(null, feature);
+ }, callback);
+ });
+}
+
+function getBuildingRelations(callback) {
+ var query = "select id,parts,members,tags from planet_osm_rels where (tags[1] = 'type' and tags[2] = 'building') or (tags[3] = 'type' and tags[4] = 'building')";
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error("Query: " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ async.map(results.rows, function(relation, callback) {
+ processRelation(relation, callback);
+ }, callback);
+ });
+}
+
+function getLevelRelations(buildingRelation, callback) {
+ var refs = [];
+ for (var i=0; i<buildingRelation.members.length; i++) {
+ var member = buildingRelation.members[i];
+ if (member.role.slice(0, 5) === 'level') {
+ refs.push(member.ref);
+ }
+ }
+
+ getRelations(refs, function(err, relations) {
+ callback(err, relations);
+ });
+}
+
+function findRoomImages(room, callback) {
+ getImagesFor(room.properties.uri, function(err, images) {
+ room.properties.images = images;
+ callback(err);
+ });
+}
+
+function findRoomContents(room, workstations, callback) {
+ var query = "PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>\
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+PREFIX dct: <http://purl.org/dc/terms/>\
+PREFIX spacerel: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>\
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\
+SELECT ?roomFeature ?subject ?label WHERE {\
+?roomFeature spacerel:within <{{uri}}> ;\
+ rdfs:label ?label ;\
+ dct:subject ?subject ;\
+} GROUP BY ?roomFeature".template({ uri: room.properties.uri });
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ //console.log("features: " + JSON.stringify(data, null, 4));
+
+ room.properties.contents = [];
+ async.each(data.results.bindings, function(feature, callback) {
+ room.properties.contents.push({feature: feature.roomFeature.value, subject: feature.subject.value, label: feature.label.value});
+
+ if (feature.subject.value === "http://id.southampton.ac.uk/point-of-interest-category/iSolutions-Workstations") {
+ workstations[feature.roomFeature.value] = {
+ type: "Feature",
+ geometry: {
+ type: "Point",
+ coordinates: [parseFloat(room.properties.center[1], 10), parseFloat(room.properties.center[0], 10)]
+ },
+ properties: {
+ label: feature.label.value,
+ room: room.properties.uri,
+ uri: feature.roomFeature.value
+ }
+ };
+ }
+
+ callback();
+ }, callback);
+ });
+}
+
+function findRoomFeatures(room, callback) {
+ var query = "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+PREFIX oo: <http://purl.org/openorg/>\
+SELECT ?feature ?label WHERE {\
+ ?uri oo:hasFeature ?feature .\
+ ?feature rdfs:label ?label\
+ FILTER (";
+
+ query += "?uri = <" + room.properties.uri + ">";
+ query += ')}';
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ }
+
+ room.properties.features = [];
+ async.each(data.results.bindings, function(feature, callback) {
+ room.properties.features.push({feature: feature.feature.value, label: feature.label.value});
+ callback();
+ }, callback);
+ });
+}
+
+// workstations
+
+function getUniWorkstations(workstations, callback) {
+ var query = 'PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+PREFIX dct: <http://purl.org/dc/terms/>\
+PREFIX spacerel: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>\
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\
+SELECT * WHERE {\
+?workstation a <http://purl.org/goodrelations/v1#LocationOfSalesOrServiceProvisioning> ;\
+ dct:subject <http://id.southampton.ac.uk/point-of-interest-category/iSolutions-Workstations> ;\
+ rdfs:label ?label ;\
+ spacerel:within ?building .\
+ ?building rdf:type soton:UoSBuilding .\
+}';
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ }
+
+ var results = data.results.bindings;
+
+ async.each(results, function(workstation, callback) {
+ var uri = workstation.workstation.value,
+ label = workstation.label.value,
+ building = workstation.building.value;
+
+ if (!(uri in workstations)) {
+
+ getBuildingCenter(building, function(err, center) {
+ if (err) {
+ console.error("workstation err " + err);
+ callback();
+ return;
+ }
+
+ workstations[uri] = {
+ type: "Feature",
+ geometry: center,
+ properties: {
+ label: label,
+ uri: uri
+ }
+ };
+
+ callback();
+ });
+ } else {
+ callback();
+ }
+ }, function(err) {
+ var features = Object.keys(workstations).map(function(workstation) {
+ return workstations[workstation];
+ });
+
+ var workstationsFeatureCollection = { type: "FeatureCollection", features: features };
+
+ callback(null, workstationsFeatureCollection);
+ });
+ });
+}
+
+// buildingFeatures
+
+function getPrinters(buildings, callback) {
+ console.log("begining create printers");
+
+ var query = "PREFIX spacerel: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>\
+PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+PREFIX ns1: <http://vocab.deri.ie/rooms#>\
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\
+SELECT * WHERE {\
+ ?mdf a <http://www.productontology.org/id/Multifunction_printer> ;\
+ rdfs:label ?label ;\
+ <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/within> ?building .\
+ ?building <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> soton:UoSBuilding .\
+ OPTIONAL {\
+ ?mdf <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/within> ?room .\
+ ?room rdf:type ns1:Room\
+ }\
+} group by ?mdf";
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ }
+
+ var printerLabelByURI = {};
+
+ // For validation
+ var openDataPrinterURIs = {}
+
+ async.map(data.results.bindings, function(result, callback) {
+
+ var uri = result.mdf.value;
+
+ openDataPrinterURIs[uri] = true;
+
+ var building = result.building.value;
+ if ("room" in result)
+ var room = result.room.value;
+ var label = result.label.value;
+
+ printerLabelByURI[uri] = label;
+
+ var feature = {
+ type: "Feature",
+ properties: {
+ label: label,
+ uri: uri
+ }
+ };
+
+ if (uri in printers) {
+ feature.geometry = {
+ type: "Point",
+ coordinates: printers[uri].coordinates
+ };
+
+ feature.properties.level = parseInt(printers[uri].level, 10);
+ }
+
+ if (building in buildings) {
+ var buildingProperties = buildings[building].properties;
+
+ if (!('services' in buildingProperties)) {
+ buildingProperties.services = { mfds: [] };
+ } else if (!('mfds' in buildingProperties.services)) {
+ buildingProperties.services.mfds = [];
+ }
+
+ buildingProperties.services.mfds.push(uri);
+
+ buildingProperties.services.mfds.sort(function(aURI, bURI) {
+ var textA = printerLabelByURI[aURI].toUpperCase();
+ var textB = printerLabelByURI[bURI].toUpperCase();
+ return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
+ });
+ } else {
+ addBuildingMessage(building, "errors", "location", "unknown buildingPrinter");
+ }
+
+ callback(null, feature);
+ }, function(err, results) {
+ var printersWithLocations = 0;
+
+ Object.keys(printers).forEach(function(uri) {
+ if (!(uri in openDataPrinterURIs)) {
+ console.err("printer " + uri + " is not known");
+ } else {
+ printersWithLocations++;
+ }
+ });
+
+ console.log("finished processing printers (" + printersWithLocations + "/" + Object.keys(openDataPrinterURIs).length + ")");
+
+ async.filter(results,
+ function(printer, callback) {
+ callback(typeof printer !== 'undefined');
+ },
+ function(cleanResults) {
+ callback(err, cleanResults);
+ }
+ );
+ });
+ });
+}
+
+function getVendingMachines(buildings, callback) {
+ console.log("begin getVendingMachines");
+
+ var query = "PREFIX spacerel: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>\
+PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+SELECT * WHERE {\
+ ?uri a <http://purl.org/goodrelations/v1#LocationOfSalesOrServiceProvisioning> ;\
+ rdfs:label ?label ;\
+ soton:vendingMachineModel ?model ;\
+ soton:vendingMachineType ?type ;\
+ spacerel:within ?building .\
+}";
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ query = "select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as point,osm_id,vending,level,uri from planet_osm_point where ST_Contains((select ST_Union(way) from uni_site), way) and amenity='vending_machine';"
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error("Query: " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ var machinesByURI = {};
+ var machines = [];
+
+ // First, look through OSM finding the location of the vending
+ // machines
+ results.rows.forEach(function(part) {
+ var feature = { type: "Feature" };
+ feature.geometry = JSON.parse(part.point);
+ delete part.point;
+ delete part.osm_id;
+
+ feature.properties = part;
+
+ machinesByURI[part.uri] = feature;
+
+ machines.push(feature);
+ });
+
+ // Then look through the University Open Data, to find the ones OSM
+ // is missing, and any additional information
+ data.results.bindings.forEach(function(result) {
+ var uri = result.uri.value;
+ var machine;
+
+ if (uri in machinesByURI) {
+ machine = machinesByURI[uri];
+
+ machine.properties.label = result.label.value;
+ } else {
+ machine = { type: "Feature", properties: { uri: uri, label: result.label.value } };
+
+ machinesByURI[uri] = machine;
+
+ machines.push(machine);
+ }
+
+ var building = result.building.value;
+ if (!(building in buildings)) {
+ if (building.indexOf("site") === -1) // building could actually be a site, query needs fixing
+ addBuildingMessage(building, "errors", "location", "unknown (vendingMachine)");
+ } else {
+ var buildingProperties = buildings[building].properties;
+
+ if (!('services' in buildingProperties)) {
+ buildingProperties.services = { vendingMachines: [] };
+ } else if (!('vendingMachines' in buildingProperties.services)) {
+ buildingProperties.services.vendingMachines = [];
+ }
+
+ buildingProperties.services.vendingMachines.push(uri);
+ }
+ });
+
+ callback(err, machines);
+ });
+ });
+}
+
+// buses
+
+function loadBusData(callback) {
+ async.waterfall([
+ function(callback) {
+ pg.query('drop table if exists uni_bus_route', function(err, results) {
+ callback(err);
+ });
+ },
+ function(callback) {
+ console.log("creating uni_bus_route");
+ pg.query('create table uni_bus_route ( way geometry, name text, note text, colour text, ref text)', function(err, results) {
+ callback(err);
+ });
+ },
+ function(callback) {
+ var query = "select id,parts,members,tags from planet_osm_rels where tags @> array['type', 'route_master', 'Uni-link']";
+ pg.query(query, callback);
+ },
+ function(results, callback) {
+ async.map(results.rows, function(relation, callback) {
+ processRelation(relation, callback);
+ }, callback);
+ },
+ function(routeMasters, callback) {
+ var stopAreaRoutes = {} // Mapping from id to stop area, also contains the route names for that stop area
+
+ async.each(routeMasters, function(routeMaster, callback) {
+ async.each(routeMaster.members, function(member, callback) {
+ getRelation(member.ref, function(err, route) {
+ if (err) callback(err);
+
+ var ways = [];
+ var stopAreasRoutes = {};
+
+ async.eachSeries(route.members, function(member /* either a stop_area, or a road */, callback) {
+ if (member.type === "relation") { // Then its a stop_area
+ // Add the stop to the list (stopAreas)
+ if (member.ref in stopAreaRoutes) {
+ if (stopAreaRoutes[member.ref].indexOf(route.tags.ref) < 0)
+ stopAreaRoutes[member.ref].push(route.tags.ref);
+ } else {
+ stopAreaRoutes[member.ref] = [route.tags.ref];
+ }
+ callback();
+ } else {
+ var query = "select ST_AsGeoJSON(ST_Transform(way, 4326), 10) as way from planet_osm_line where osm_id = " + member.ref;
+
+ pg.query(query, function(err, results) {
+ if (err) callback(err);
+
+ ways.push(JSON.parse(results.rows[0].way).coordinates);
+
+ callback();
+ });
+ }
+ }, function(err) {
+ // Now to create the route geometry
+
+ createRouteGeometry(ways, function(err, routeCoords) {
+ if (err) {
+ console.error("geometry errors for route " + route.tags.name);
+ err.forEach(function(error) {
+ console.log(" " + error);
+ });
+ }
+
+ var flattenedCoords = [];
+ flattenedCoords = flattenedCoords.concat.apply(flattenedCoords, routeCoords);
+
+ var colour = ('colour' in route.tags) ? route.tags.colour : routeMaster.tags.colour;
+
+ var pgQuery = "insert into uni_bus_route values(ST_GeomFromText('LINESTRING(" + flattenedCoords.join(" ") + "'), ";
+ pgQuery = pgQuery + "'" + [route.tags.name, "note", colour, route.tags.ref].join("', '") + "')";
+
+ callback();
+ /*pg.query(pgQuery, function(err, result) {
+ callback(err);
+ });*/
+ });
+ });
+ });
+ }, callback);
+ }, function(err) {
+ callback(err, stopAreaRoutes);
+ });
+ },
+ function(stopAreaRoutes, callback) {
+ // Now the route processing has finished, the bus stops can be created
+
+ createBusStops(stopAreaRoutes, callback);
+ }
+ ], function(err) {
+ console.log("finished loadBusData");
+ if (err)
+ console.error(err);
+
+ callback(err);
+ });
+}
+
+function createRouteGeometry(ways, callback) {
+ var routeCoords = [];
+
+ function last(way) {
+ return way.slice(-1)[0];
+ }
+
+ function first(way) {
+ return way[0];
+ }
+
+ function equal(coord1, coord2) {
+ return coord1[0] === coord2[0] && coord1[1] === coord2[1];
+ }
+
+ // Determine the orientation of the first way
+ if (equal(last(ways[0]), first(ways[1])) || equal(last(ways[0]), last(ways[1]))) {
+ routeCoords = ways[0];
+ } else {
+ routeCords = ways[0].reverse();
+ }
+
+ var errors = [];
+
+ for (var i=1; i<ways.length; i++) {
+ var way = ways[i];
+
+ // pop the end node, as this will be present on the next way added
+ routeCords.pop();
+
+ if (equal(last(ways[i-1]), first(way))) {
+ routeCords.push.apply(routeCords, way);
+ } else {
+ if (!equal(last(ways[i-1]), last(way))) {
+ errors.push("break detected at " + i);
+ }
+ routeCoords.push.apply(routeCoords, way.reverse());
+ }
+ }
+
+ if (errors.length === 0)
+ errors = null;
+
+ callback(errors, routeCoords);
+}
+
+function createBusStops(stopAreaRoutes, callback) {
+ async.waterfall([
+ function(callback) {
+ pg.query('drop table if exists uni_bus_stop', function(err, results) {
+ callback(err);
+ });
+ },
+ function(callback) {
+ console.log("creating uni_bus_stop");
+ pg.query('create table uni_bus_stop ( way geometry, name text, uri text, routes text array);', function(err, results) {
+ callback(err);
+ });
+ },
+ function(callback) {
+ getRelations(Object.keys(stopAreaRoutes), function(err, areas) {
+ async.each(areas, function(area, callback) {
+ createBusStop(area, stopAreaRoutes[area.id], callback);
+ }, callback);
+ });
+ }
+ ], function(err) {
+ if (err)
+ console.error(err);
+
+ console.log("finished createBusStops");
+
+ callback(err);
+ });
+}
+
+function createBusStop(stopArea, routes, callback) {
+ for (var i=0; i<stopArea.members.length; i++) {
+ var member = stopArea.members[i];
+ if (member.role === "platform") {
+ var ref = member.ref;
+ switch (member.type) {
+ case "node":
+ getNode(ref, function(err, node) {
+ var name = stopArea.tags.name;
+ if (name !== undefined) {
+ name = name.replace("'", "''");
+ } else {
+ name = '';
+ }
+
+ var routeArray = "{" + routes.join(", ") + "}";
+
+ var pgQuery = "insert into uni_bus_stop values(ST_SetSRID(ST_MakePoint("
+ pgQuery = pgQuery + node.geometry.coordinates[0] + ", " + node.geometry.coordinates[1];
+ pgQuery = pgQuery + "),4326), '" + name + "', '" + stopArea.tags.uri + "', '" + routeArray + "');";
+
+ pg.query(pgQuery, function(err, result) {
+ if (err) {
+ console.error("Query: " + pgQuery);
+ console.error(err);
+ }
+ callback(err);
+ });
+ });
+
+ break;
+ case "way":
+
+ callback();
+
+ break;
+ }
+ // Found the platform, so
+ break;
+ }
+ }
+
+ if (ref === undefined) {
+ console.log("no platform for area " + stopArea.tags.name + " (" + stopArea.id + ")");
+ callback();
+ return;
+ }
+}
+
+// Utility Functions
+
+function decomposeRoomURI(uri) {
+ var parts = uri.split("/").slice(-1)[0].split("-");
+
+ if (parts.length !== 2) {
+ console.log("cannot parse " + uri);
+ return undefined;
+ }
+
+ var level = parts[1].slice(0, parts[1].length - 3);
+
+ return { building: parts[0], room: parts[1], level: level };
+}
+
+function processRelation(relation, callback) {
+ var obj = { tags: {}, members: []};
+
+ for (var i=0; i<relation.members.length; i+=2) {
+ var type = relation.members[i].charAt(0);
+ var ref = parseInt(relation.members[i].slice(1), 10);
+ if (type === "r")
+ type = "relation";
+ else if (type === "w")
+ type = "way";
+ else if (type === "n")
+ type = "node";
+ else
+ console.log("Unknown type " + type);
+ obj.members.push({ type: type, ref: ref, role: relation.members[i+1] });
+ }
+
+ for (var i=0; i<relation.tags.length; i+=2) {
+ obj.tags[relation.tags[i]] = relation.tags[i+1];
+ }
+
+ obj.id = parseInt(relation.id, 10);
+
+ callback(null, obj);
+}
+
+function getRelations(ids, callback) {
+ if (ids.length === 0) {
+ console.error("cant get 0 relations");
+ callback("cant get 0 relations");
+ return;
+ }
+
+ var query = "select id,parts,members,tags from planet_osm_rels where id in (";
+
+ query += ids.join() + ")";
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error(err);
+ console.error(query);
+ callback(err);
+ return;
+ }
+
+ async.map(results.rows, function(relation, callback) {
+ processRelation(relation, callback);
+ }, callback);
+ });
+}
+
+function getBuildingCenter(uri, callback) {
+ var query = "select ST_AsGeoJSON(ST_Centroid(ST_Transform(way, 4326)), 10) as center from uni_building where uri='" + uri + "';";
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error(err);
+ console.error(query);
+ callback(err);
+ return;
+ }
+
+ if (results.rows.length === 0) {
+ callback("building not found " + uri);
+ } else {
+ callback(err, JSON.parse(results.rows[0].center));
+ }
+ });
+}
+
+function getRelation(id, callback) {
+ var query = "select id,parts,members,tags from planet_osm_rels where id = " + id;
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error("Query: " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ processRelation(results.rows[0], callback);
+ });
+}
+
+function getNode(id, callback) {
+ var query = "select *, ST_AsGeoJSON(ST_Transform(way, 4326)) as point from planet_osm_point where osm_id = " + id;
+
+ pg.query(query, function(err, results) {
+ if (err) {
+ console.error("Query: " + query);
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ var row = results.rows[0];
+
+ var node = { id: row.osm_id, geometry: JSON.parse(row.point), properties: {} };
+
+ delete row.point;
+ delete row.osm_id;
+ delete row.way;
+
+ for (var tag in row) {
+ var value = row[tag];
+ if (value !== null) {
+ node.properties[tag] = value;
+ }
+ }
+
+ callback(err, node);
+ });
+}
+
+function sparqlQuery(query, callback) {
+ http.get("http://sparql.data.southampton.ac.uk/?query=" + encodeURIComponent(query) + "&output=json", function(res) {
+ var data = '';
+
+ res.on('data', function (chunk){
+ data += chunk;
+ });
+
+ res.on('end',function(){
+ if (res.statusCode !== 200)
+ callback(data);
+
+ var obj = JSON.parse(data);
+
+ callback(null, obj);
+ })
+ }).on('error', function(e) {
+ console.error("SPARQL error: " + e.message);
+ callback(e);
+ });
+}
+
+function getImagesFor(uri, callback) {
+
+ var imageQuery = 'PREFIX foaf: <http://xmlns.com/foaf/0.1/>\
+PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\
+PREFIX nfo: <http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#>\
+PREFIX dcterms: <http://purl.org/dc/terms/>\
+SELECT * WHERE {\
+GRAPH <http://id.southampton.ac.uk/dataset/photos/latest> {\
+?image a foaf:Image ;\
+foaf:depicts <{{uri}}> ;\
+nfo:width ?width ;\
+nfo:height ?height .\
+OPTIONAL { ?image dcterms:creator ?creator ; } .\
+OPTIONAL { ?image dcterms:license ?license ; }\
+}\
+}'
+
+ imageQuery = imageQuery.template({uri: uri});
+
+ sparqlQuery(imageQuery, function(err, data) {
+ if (err) {
+ console.error(err);
+ callback(err);
+ return;
+ }
+
+ var imageGroups = {};
+
+ async.each(data.results.bindings, function(image, callback) {
+ var obj = {};
+ obj.url = image.image.value;
+ obj.width = image.width.value;
+ obj.height = image.height.value;
+
+
+ var imageName = obj.url.split("/").slice(-1)[0];
+
+ if (imageName in imageGroups) {
+ imageGroups[imageName].versions.push(obj);
+ } else {
+ imageGroups[imageName] = { versions: [obj] };
+ if ('licence' in image) {
+ imageGroups[imageName].license = image.license.value;
+ }
+ if ('creator' in image) {
+ imageGroups[imageName].creator = image.creator.value;
+ }
+ }
+
+ callback(null, obj);
+ }, function(err) {
+ var images = [];
+
+ for (var key in imageGroups) {
+ var ig = imageGroups[key];
+
+ ig.versions.sort(function(v1, v2) {
+ return (v2.width * v2.height) - (v1.width * v1.height);
+ });
+
+ images.push(ig);
+ }
+
+ callback(err, images);
+ });
+ });
+}
+
+// Output Functions
+
+function writeDataFiles(data, callback) {
+ async.parallel([
+ function(callback) {
+ var stream = fs.createWriteStream("./data.json");
+ stream.once('open', function(fd) {
+ stream.write(JSON.stringify(data));
+ stream.end();
+ callback();
+ });
+ },
+ function(callback) {
+ var stream = fs.createWriteStream("./data-source.json");
+ stream.once('open', function(fd) {
+ stream.write(JSON.stringify(data, null, 4));
+ stream.end();
+ callback();
+ });
+ }
+ ], callback);
+}
+
+// Validation Functions
+
+function validateBuildingParts(buildingParts, callback) {
+ console.log("begining validating buildingparts");
+
+ async.each(Object.keys(uniRooms), function(room, callback) {
+ var type = uniRooms[room].type;
+ var building = uniRooms[room].building;
+
+ if (room in buildingRooms) {
+
+ } else {
+ var roomNeeded = 'Room <a href="' + room + '">' + room + '</a> is missing';
+
+ addBuildingToDo(building, 'rooms', roomNeeded);
+ }
+
+ callback();
+ }, callback);
+}
+
+function validateBuildings(callback) {
+ var query = "PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+PREFIX skos: <http://www.w3.org/2004/02/skos/core#>\
+SELECT * WHERE {\
+ ?building <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> soton:UoSBuilding ;\
+ skos:notation ?ref\
+}";
+
+ sparqlQuery(query, function(err, data) {
+ if (err) {
+ console.error("Query " + query);
+ console.error(err);
+ }
+
+ async.each(data.results.bindings, function(building, callback) {
+ var uri = building.building.value;
+
+ if (!(uri in buildings)) {
+ addBuildingMessage(uri, "errors", "location", "unknown (validateBuildings)");
+ }
+
+ callback();
+ }, function(err) {
+ console.log("finished validateBuildings");
+ callback(err);
+ });
+ });
+}
+
+function addBuildingMessage(buildingURI, severity, section, message) {
+ var buildingValidation;
+
+ if (buildingURI in validationByURI) {
+ buildingValidation = validationByURI[buildingURI];
+ } else {
+ buildingValidation = {todo: {}, warnings: {}, errors: {}};
+ validationByURI[buildingURI] = buildingValidation;
+ }
+
+ if (!(section in buildingValidation[severity])) {
+ buildingValidation[severity][section] = [];
+ }
+
+ buildingValidation[severity][section].push(message);
+}
+
+function addRoomMessage(roomURI, severity, section, message) {
+ var roomValidation;
+
+ if (roomURI in validationByURI) {
+ roomValidation = validationByURI[roomURI];
+ } else {
+ roomValidation = {todo: {}, warnings: {}, errors: {}};
+ validationByURI[roomURI] = roomValidation;
+ }
+
+ if (!(section in roomValidation[severity])) {
+ roomValidation[severity][section] = [];
+ }
+
+ roomValidation[severity][section].push(message);
+}
diff --git a/examples/basic.html b/examples/basic.html
new file mode 100644
index 0000000..161ecaa
--- /dev/null
+++ b/examples/basic.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Map - University of Southampton</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+
+ <link rel="stylesheet" href="../src/sum.css" />
+ <link rel="stylesheet" href="../resources/leaflet/dist/leaflet.css" />
+
+ <style>
+ body {
+ padding: 0;
+ margin: 0;
+ }
+
+ html, body, #map {
+ height: 100%;
+ }
+ </style>
+</head>
+<body>
+ <div id="map"></div>
+
+ <script src="../resources/leaflet/dist/leaflet-src.js"></script>
+
+ <script src="../src/sum.js"></script>
+
+ <script type="text/javascript">
+ SUM.dataPath = '../data.json';
+
+ var map = SUM.map('map');
+ </script>
+</body>
+</html>
diff --git a/examples/buildingsearch.html b/examples/buildingsearch.html
new file mode 100644
index 0000000..7ee8f40
--- /dev/null
+++ b/examples/buildingsearch.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+
+ <link rel="stylesheet" href="../src/leaflet-soton.css" />
+ <link rel="stylesheet" href="../resources/leaflet/dist/leaflet.css" />
+
+<style type="text/css">
+
+html { height: 100% }
+body { height: 100%; margin: 0px; padding: 0px }
+#top_bar {
+ height: 10%;
+ width:100%;
+ height:5%;
+ border-bottom: 1px solid #000;
+ overflow:hidden;
+ background-color:#ccc
+}
+
+#map_canvas { height: 90% }
+
+.info_photo {
+ float:right;
+ padding: 0em 0em 0.3em 1em;
+}
+.info {
+ font-family: sans-serif;
+ width: 400px;
+ height: 200px;
+ overflow-y: auto;
+}
+.info_label {
+ font-weight: bold;
+ font-size: 130%;
+}
+.info_n {
+ font-size: 200%;
+}
+.info_link {
+ margin-bottom: 1em;
+ font-size: small;
+}
+.info_txt {
+ margin-bottom: 0.5em;
+}
+#resultsDiv {
+ padding-bottom: 2em;
+}
+#resultsDiv a {
+ display: block;
+ padding-top: 0.3em;
+ padding-left: 5%;
+ padding-right: 5%;
+}
+#resultsDiv a .n {
+ padding-right: 0.5em;
+ display: block;
+ float: right;
+ font-size: 90%;
+}
+#resultsDiv a .label {
+ display: block;
+ padding-left: 0.5em;
+ border-bottom: solid 1px #ccc;
+ padding-bottom: 0.3em;
+ font-size: 120%;
+}
+
+#searchBox {
+ text-align: center;
+ width: 90%;
+ font-size: 140%;
+ padding: 0.1em;
+ border: solid 1px #000;
+}
+
+</style>
+
+</head>
+<body>
+<div id="top_bar">
+<div style='font-size:40px;padding:5px 5px 3px 5px;'>
+ Building Search
+</div>
+</div>
+<div id="map" style="width:70%; height:95%; float:right"></div>
+
+<div style='height:95%;overflow-y:auto;border-right:1px solid #000'>
+
+ <div style='padding-bottom:0.5em;border-bottom:solid 1px #000;margin-bottom:0.5em'>
+
+ <div style='margin:0.3em;text-align:center;clear:both;'>
+ <input id='searchBox' />
+ </div>
+ <div style='text-align:center'>
+ <label>
+ <input id='res' type='checkbox' name='res' value='yes' />
+ Include Halls and Residential Buildings
+ </label>
+ </div>
+ <div style='text-align:center;margin-top:0.3em;font-size:80%'>
+ Type "b12." to search for a specific building number.
+ </div>
+ </div>
+
+ <div id='resultsDiv'>
+ </div>
+</div>
+
+<script src="../resources/leaflet/dist/leaflet.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');
+
+ LS.on("dataload", function(data) {
+ var searchBox = document.getElementById("searchBox");
+ var residentialTickBox = document.getElementById("res");
+ var resultsDiv = document.getElementById("resultsDiv");
+
+ var buildings = data.buildings.features.sort(function(a, b) {
+ return a.properties.name.localeCompare(b.properties.name);
+ });
+
+ function filter() {
+ var searchText = searchBox.value.toLowerCase();
+
+ resultsDiv.innerHTML = "";
+
+ var results = [];
+
+ buildings.forEach(function(building) {
+ var name = building.properties.name;
+
+ if (name.length === 0)
+ return;
+
+ if (name.toLowerCase().indexOf(searchText) !== -1) {
+ var a = document.createElement("a");
+ a.href = "#";
+
+ a.onclick = function() {
+ map.showPopupByURI(building.properties.uri);
+ };
+
+ var label = document.createElement("span");
+ label.textContent = name;
+ label.className = "label";
+ a.appendChild(label);
+
+ var n = document.createElement("span");
+ n.textContent = building.properties.loc_ref;
+ n.className = "n";
+ a.appendChild(n);
+
+ resultsDiv.appendChild(a);
+
+ results.push(building.properties.uri);
+ }
+ });
+
+ if (results.length === 1) {
+ map.showPopupByURI(results[0]);
+ }
+ }
+
+ searchBox.onkeyup = filter;
+
+ filter();
+ });
+</script>
+</body>
+</html>
diff --git a/examples/full.html b/examples/full.html
new file mode 100644
index 0000000..02d1c96
--- /dev/null
+++ b/examples/full.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Workstations - University of Southampton</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+
+ <link rel="stylesheet" href="../resources/leaflet-locatecontrol/src/L.Control.Locate.css" />
+ <link rel="stylesheet" href="../resources/leaflet/dist/leaflet.css" />
+
+ <link rel="stylesheet" href="../src/leaflet-soton.css" />
+
+ <style>
+ body {
+ padding: 0;
+ margin: 0;
+ }
+
+ html, body, #map {
+ height: 100%;
+ }
+ </style>
+</head>
+<body>
+ <div id="map"></div>
+
+ <script src="../resources/leaflet/dist/leaflet.js"></script>
+ <script src="../resources/leaflet-markercluster/dist/leaflet.markercluster.js"></script>
+ <script src="../resources/leaflet-locatecontrol/src/L.Control.Locate.js"></script>
+ <script src="../resources/leaflet-hash/leaflet-hash.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', {
+ workstations: true,
+ indoor: true
+ });
+
+ L.control.locate().addTo(map);
+ </script>
+</body>
+</html>
diff --git a/examples/index.html b/examples/index.html
new file mode 100644
index 0000000..997e65d
--- /dev/null
+++ b/examples/index.html
@@ -0,0 +1,372 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Leaflet Soton - Documentation</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
+
+ <style>
+/* Adapted from a stylesheet by Edward O'Connor */
+/* http://edward.oconnor.cx/ */
+
+/* Undo default HTML styling of some elements */
+
+h1, h2, h3, h4, h5, h6 {
+ font-weight: normal;
+ margin: 0;
+ padding: 0;
+ text-align: left;
+}
+
+/* Basic layout */
+
+body {
+ margin: 1.3125em auto 0 auto;
+ padding: 0;
+ width: 80em;
+ max-width: 98%;
+ hyphens: auto;
+}
+
+p {
+ font-size: large;
+ margin: 1.3125em 0;
+ padding: 0;
+}
+
+li {
+ font-size: large;
+ padding: 0;
+}
+
+/* Typography */
+
+/* 6pt = 0.375em
+ * 7pt = 0.4375em
+ * 8pt = 0.5em
+ * 9pt = 0.5625em
+ * 10pt = 0.625em
+ * 11pt = 0.6875em
+ * 12pt = 0.75em
+ * 14pt = 0.875em
+ * 16pt = 1em
+ * 18pt = 1.125em
+ * 21pt = 1.3125em
+ * 24pt = 1.5em
+ * 36pt = 2.25em
+ * 48pt = 3em
+ * 60pt = 3.75em
+ * 72pt = 4.5em
+ */
+
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 100%;
+ line-height: 1.625;
+}
+
+h1 { font-size: 2.25em; line-height: 1.167; margin-bottom: .75em}
+h2 { font-size: 2em; line-height: 3; margin-bottom: .75em}
+h3 { font-size: 1.3125em; line-height: 2; margin-bottom: .75em}
+h4 { font-size: 1.25em; line-height: 1.05; }
+h5 { font-size: 1.125em; line-height: 1.167; }
+h6 { font-size: 1em; line-height: 1.3125; }
+ </style>
+
+ <link rel="stylesheet" href="../src/leaflet-soton.css" />
+
+ <link rel="stylesheet" href="../resources/leaflet/dist/leaflet.css" />
+
+ <script type="text/javascript" src="../resources/syntaxhighlighter/pkg/scripts/shCore.js"></script>
+ <script type="text/javascript" src="../resources/syntaxhighlighter/pkg/scripts/shBrushJScript.js"></script>
+ <script type="text/javascript" src="../resources/syntaxhighlighter/pkg/scripts/shBrushXml.js"></script>
+ <link type="text/css" rel="stylesheet" href="../resources/syntaxhighlighter/pkg/styles/shCoreDefault.css"/>
+ <script type="text/javascript">SyntaxHighlighter.all();</script>
+</head>
+<body>
+ <h1>An Introduction to OpenStreetMap and the University of Southampton</h1>
+
+ <h2>OpenStreetMap</h2>
+ <table>
+ <tr>
+ <td>
+ <p>
+
+ The OpenStreetMap project was started in 2004 with the aim of
+ creating a free map of the world. The core, but perhaps less
+ obvious output from the project is the data. You can see an
+ example of some OSM data, represented in XML on the right.
+
+ </p>
+ <p>
+
+ This abreviated data describes the Zepler building on Highfield
+ Campus. You can see in the tags, that the building name,
+ number, and uri are present.
+
+ </p>
+ </td>
+ <td>
+ <script type="syntaxhighlighter" class="brush: xml"><![CDATA[
+ <?xml version='1.0' encoding='UTF-8'?>
+ <osm version='0.6'>
+ <node id='304648094' lat='50.9371225' lon='-1.397536' />
+ <node id='304648184' lat='50.9375162' lon='-1.3976054' />
+ ...
+ <node id='2628668610' lat='50.9371174' lon='-1.3976749' />
+ <way id='27743656'>
+ <nd ref='1550130370' />
+ ...
+ <nd ref='1550130274' />
+ <nd ref='1550130370' />
+ <tag k='building' v='yes' />
+ <tag k='loc_ref' v='59' />
+ <tag k='name' v='Zepler' />
+ <tag k='uri' v='http://id.southampton.ac.uk/building/59' />
+ </way>
+ </osm>
+ ]]></script>
+ </td>
+ </tr>
+ </table>
+
+ <h2>Slippy Maps</h2>
+ <table>
+ <tr>
+ <td>
+ <img src="http://a.tile.openstreetmap.org/16/32513/21958.png" />
+ </td>
+ <td>
+ <h3>Data to Images (or Tiles)</h3>
+ <p>
+
+ XML is perhaps not the best way to view the data if you want to
+ use if for navigation, so this data can be rendered in to
+ images for use in a map. Shown here is the "Standard" rendering
+ of part of Highfield campus.
+
+ </p>
+ <p>
+
+ This tile has been downloaded from the OpenStreetMap tile
+ servers. A tileserver is normally composed of a database
+ (usually postgresql with postgis), a tile rendering stack
+ (e.g. renderd and mapnik) and a webserver (e.g. apache with
+ mod_tile).
+
+ </p>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <h3>Leaflet</h3>
+ <p>
+
+ There are a few libraries that can handle displaying a "slippy"
+ map, the one in use here is called leaflet. Tiles like the one
+ shown above are combined together in to a grid that can be
+ moved with the mouse.
+
+ </p>
+ <p>
+
+ The javascript used to create the map seen on the right is shown below.
+
+ </p>
+
+ <pre class="brush: js;">
+ var map1 = L.map('zepler-osm');
+
+ map1.setView([50.93733, -1.39779], 19);
+
+ L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
+ maxZoom: 19
+ }).addTo(map1);
+ </pre>
+ </td>
+ <td>
+ <div id="zepler-osm" style="height: 500px; width: 100%;"></div>
+ </td>
+ </tr>
+ </table>
+ <h2>Custom Maps for the University</h2>
+ <table>
+ <tr>
+ <td>
+ <div id="sum-carto" style="height: 500px; width: 600px;"></div>
+ </td>
+ <td>
+ <h3>Custom Rendering (sum-carto)</h3>
+ <p>
+
+ Jumping back to tileservers, the rendering stack decides how to
+ display the data as an image. Mapnik, which is commonly used
+ has an xml stylesheet that it uses, this can be (and in the
+ case of the "Standard" OSM style, is) generated from another
+ CSS like language called carto.
+
+ </p>
+ <p>
+
+ The "Standard" OSM rendering has some disadvantages for the
+ University. Probably the biggest is that it shows some
+ irelevent things, e.g. housenumbers for the surrounding
+ residential area, it also does not show relevent things e.g.
+ building numbers, cycle parking, ...
+
+ </p>
+ <p>
+
+ To the left, you can see the same view as before, but this
+ time, the data has been rendered according to a different
+ stylesheet. Notice that the buildings number is displayed
+ below the name, and that some cycle parking is shown just to
+ the top right of the Zepler building (green P).
+
+ </p>
+ <p>
+
+ Currently there are two tileservers hosting this University
+ specific tileset. Inside ECS there is
+ kanga-cb15g11.ecs.soton.ac.uk, which is updated daily. Outside
+ of ECS, there is altair.cbaines.net, which is rarely updated.
+
+ </p>
+ <pre class="brush: js;">
+ var map2 = L.map('sum-carto');
+
+ map2.setView([50.93733, -1.39779], 19);
+
+ L.tileLayer('http://kanga-cb15g11.ecs.soton.ac.uk/sum/{z}/{x}/{y}.png', {
+ maxZoom: 19
+ }).addTo(map2);
+ </pre>
+ </td>
+ </tr>
+ </table>
+ <h2>Interactivity</h2>
+ <table>
+ <tr>
+ <td>
+ <h2>sum</h2>
+ <p>
+
+ So far we have seen static maps, and slippy maps. But extra
+ interactivty can be used to enhance slippy maps with more data
+ where available. The University is off to a good start with
+ this data, and some of it can be interacted with through the
+ map.
+
+ </p>
+ <p>
+
+ The sum (TODO: Find better name) library allows you to add this
+ interactivity to the map with ease, its mainly clientside, with
+ a small server side component. It handles everything from
+ setting up Leaflet, to advanced interactive components.
+
+ </p>
+ <p>
+
+ Try clicking on the buildings and the bicycle parking.
+
+ </p>
+ <pre class="brush: js;">
+ var map3 = LS.map('sum-basic');
+ </pre>
+ </td>
+ <td>
+ <div id="sum-basic" style="width: 100%; height: 500px;"></div>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <div id="sum-workstations" style="width: 100%; height: 500px;"></div>
+ </td>
+ <td>
+ <h3>Workstations</h3>
+ <p>
+
+ One of the very useful pieces of information published by the
+ University is the workstation use data. On the left you can see
+ this being displayed on the map.
+
+ </p>
+ <p>
+
+ As you zoom in and out, the workstation markers will group
+ together and split apart to keep the data visible. All markers
+ are also interactive and display more data in a popup when
+ clicked on.
+
+ </p>
+ <pre class="brush: js;">
+ var map4 = LS.map('sum-workstations', {
+ workstations: true
+ });
+ </pre>
+ </td>
+ </tr>
+ </table>
+ <h2> Future Experimental Features</h2>
+ <table>
+ <tr>
+ <td>
+ <h3>Indoor Maps</h3>
+ <p>
+
+ Navigating around the University is slightly different from
+ navigating around a city, e.g. Southampton or London. Most of
+ navigation for students and staff of the university involves
+ moving between rooms.
+
+ </p>
+ <p>
+
+ The sum library also has an experimental indoor feature, you
+ can see a view of the library (level 2) on the left.
+
+ </p>
+ </td>
+ <td>
+ <div id="sum-indoor" style="width: 600px; height: 500px;"></div>
+ </td>
+ </tr>
+ </table>
+
+ <script src="../resources/leaflet/dist/leaflet-src.js"></script>
+ <script src="../resources/leaflet-markercluster/dist/leaflet.markercluster.js"></script>
+
+ <script src="../src/leaflet-soton.js"></script>
+
+ <script type="text/javascript">
+ LS.dataPath = '../data.json';
+ LS.imagePath = '../resources/images/';
+
+ var map1 = L.map('zepler-osm').setView([50.93733, -1.39779], 19);
+
+ L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
+ maxZoom: 19
+ }).addTo(map1);
+
+ var map2 = L.map('sum-carto').setView([50.93733, -1.39779], 19);
+
+ L.tileLayer('http://kanga-cb15g11.ecs.soton.ac.uk/sum/{z}/{x}/{y}.png', {
+ maxZoom: 22
+ }).addTo(map2);
+
+ var map3 = LS.map('sum-basic');
+
+ var map4 = LS.map('sum-workstations', {
+ workstations: true
+ });
+
+ var map5 = LS.map('sum-indoor', {
+ workstations: true,
+ indoor: true,
+ center: [50.93471, -1.3957],
+ zoom: 20,
+ level: "2"
+ });
+ </script>
+</body>
+</html>
diff --git a/examples/indoor.html b/examples/indoor.html
new file mode 100644
index 0000000..0e46409
--- /dev/null
+++ b/examples/indoor.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Map - University of Southampton</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/dist/leaflet.css" />
+
+ <style>
+ body {
+ padding: 0;
+ margin: 0;
+ }
+
+ html, body, #map {
+ height: 100%;
+ }
+ </style>
+</head>
+<body>
+ <div id="map"></div>
+
+ <script src="../resources/leaflet/dist/leaflet-src.js"></script>
+
+ <script src="../src/leaflet-soton.js"></script>
+
+ <script type="text/javascript">
+ LS.dataPath = '../data.json';
+ LS.imagePath = '../resources/images/';
+
+ var map = LS.map('map', {
+ indoor: true
+ });
+ </script>
+</body>
+</html>
diff --git a/examples/search.html b/examples/search.html
new file mode 100644
index 0000000..85e8d80
--- /dev/null
+++ b/examples/search.html
@@ -0,0 +1,222 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
+
+ <link rel="stylesheet" href="../src/leaflet-soton.css" />
+ <link rel="stylesheet" href="../resources/leaflet/dist/leaflet.css" />
+
+<style type="text/css">
+
+html { height: 100% }
+body { height: 100%; margin: 0px; padding: 0px }
+#top_bar {
+ height: 10%;
+ width:100%;
+ height:5%;
+ border-bottom: 1px solid #000;
+ overflow:hidden;
+ background-color:#ccc
+}
+
+#map_canvas { height: 90% }
+
+.info_photo {
+ float:right;
+ padding: 0em 0em 0.3em 1em;
+}
+.info {
+ font-family: sans-serif;
+ width: 400px;
+ height: 200px;
+ overflow-y: auto;
+}
+.info_label {
+ font-weight: bold;
+ font-size: 130%;
+}
+.info_n {
+ font-size: 200%;
+}
+.info_link {
+ margin-bottom: 1em;
+ font-size: small;
+}
+.info_txt {
+ margin-bottom: 0.5em;
+}
+#resultsDiv {
+ padding-bottom: 2em;
+}
+#resultsDiv a {
+ display: block;
+ padding-top: 0.3em;
+ padding-left: 5%;
+ padding-right: 5%;
+}
+#resultsDiv a .n {
+ padding-right: 0.5em;
+ display: block;
+ float: right;
+ font-size: 90%;
+}
+#resultsDiv a .label {
+ display: block;
+ padding-left: 0.5em;
+ border-bottom: solid 1px #ccc;
+ padding-bottom: 0.3em;
+ font-size: 120%;
+}
+
+#searchBox {
+ text-align: center;
+ width: 90%;
+ font-size: 140%;
+ padding: 0.1em;
+ border: solid 1px #000;
+}
+
+</style>
+
+</head>
+<body>
+<div id="top_bar">
+<div style='font-size:40px;padding:5px 5px 3px 5px;'>
+ Search
+</div>
+</div>
+<div id="map" style="width:70%; height:95%; float:right"></div>
+
+<div style='height:95%;overflow-y:auto;border-right:1px solid #000'>
+
+ <div style='padding-bottom:0.5em;border-bottom:solid 1px #000;margin-bottom:0.5em'>
+
+ <div style='margin:0.3em;text-align:center;clear:both;'>
+ <input id='searchBox' />
+ </div>
+ <div style='text-align:center'>
+ <label>
+ <input id='res' type='checkbox' name='res' value='yes' />
+ Include Halls and Residential Buildings
+ </label>
+ </div>
+ <div style='text-align:center;margin-top:0.3em;font-size:80%'>
+ Type "b12." to search for a specific building number.
+ </div>
+ </div>
+
+ <div id='resultsDiv'>
+ </div>
+</div>
+
+<script src="../resources/leaflet/dist/leaflet.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: true
+ });
+
+ LS.on("dataload", function(data) {
+
+ var buildingsByRef = {};
+ var buildingRoomsByRef = {};
+
+ data.buildings.features.forEach(function(building) {
+ if ("loc_ref" in building.properties) {
+ buildingsByRef[building.properties.loc_ref] = building;
+ }
+ });
+
+ data.buildingParts.features.forEach(function(part) {
+ if (part.properties.buildingPart === "room" && "ref" in part.properties) {
+ buildingRoomsByRef[part.properties.ref] = part;
+ }
+ });
+
+ function matchSlashSeperated(searchText) {
+
+ var parts = searchText.split("/");
+
+ if (parts.length !== 2)
+ return null;
+
+ var building = parts[0].trim();
+ var room = parts[1].trim();
+
+ if (building in buildingsByRef) {
+ if (room in buildingRoomsByRef[building]) {
+ return buildingsRoomsByRef[building][room];
+ } else {
+ return buildingsByRef[building];
+ }
+ } else {
+ return null;
+ }
+ }
+
+ var searchBox = document.getElementById("searchBox");
+ var residentialTickBox = document.getElementById("res");
+ var resultsDiv = document.getElementById("resultsDiv");
+
+ var buildings = data.buildings.features.sort(function(a, b) {
+ return a.properties.name.localeCompare(b.properties.name);
+ });
+
+ function filter() {
+ var searchText = searchBox.value.toLowerCase();
+
+ resultsDiv.innerHTML = "";
+
+ var matchingBuildings = [];
+ var matchingBuildingRooms = {};
+
+ buildings.forEach(function(building) {
+ var name = building.properties.name;
+ var loc_ref = building.properties.loc_ref;
+
+ if (name.length === 0)
+ return;
+
+ if (name.toLowerCase().indexOf(searchText) !== -1 ||
+ loc_ref.indexOf(searchText) !== -1) {
+
+ matchingBuildings.push(building);
+ }
+ });
+
+ matchingBuildings.forEach(function(building) {
+ var a = document.createElement("a");
+ a.href = "#";
+
+ a.onclick = function() {
+ map.showPopupByURI(building.properties.uri);
+ };
+
+ var label = document.createElement("span");
+ label.textContent = building.properties.name;
+ label.className = "label";
+ a.appendChild(label);
+
+ var n = document.createElement("span");
+ n.textContent = building.properties.loc_ref;
+ n.className = "n";
+ a.appendChild(n);
+
+ resultsDiv.appendChild(a);
+ });
+
+ /*if (results.length === 1) {
+ map.showPopupByURI(results[0]);
+ }*/
+ }
+
+ searchBox.onkeyup = filter;
+
+ filter();
+ });
+</script>
+</body>
+</html>
diff --git a/examples/workstations.html b/examples/workstations.html
new file mode 100644
index 0000000..819c41b
--- /dev/null
+++ b/examples/workstations.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Workstations - University of Southampton</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/dist/leaflet.css" />
+
+ <style>
+ body {
+ padding: 0;
+ margin: 0;
+ }
+
+ html, body, #map {
+ height: 100%;
+ }
+ </style>
+</head>
+<body>
+ <div id="map"></div>
+
+ <script src="../resources/leaflet/dist/leaflet.js"></script>
+ <script src="../resources/leaflet-markercluster/dist/leaflet.markercluster.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', {
+ workstations: true
+ });
+ </script>
+</body>
+</html>
diff --git a/examples/zepler.html b/examples/zepler.html
new file mode 100644
index 0000000..4caf762
--- /dev/null
+++ b/examples/zepler.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Zepler (Building 59)</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/dist/leaflet.css" />
+
+ <style>
+ body {
+ padding: 0;
+ margin: 0;
+ }
+
+ html, body, #map {
+ height: 100%;
+ }
+ </style>
+</head>
+<body>
+ <div id="map"></div>
+
+ <script src="../resources/leaflet/dist/leaflet.js"></script>
+ <script src="../resources/leaflet-markercluster/dist/leaflet.markercluster.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: true,
+ zoom: 20,
+ center: [50.93732, -1.39774],
+ highlight: {
+ "http://id.southampton.ac.uk/building/59": true
+ }
+ });
+ </script>
+</body>
+</html>
diff --git a/resources/images/candy.png b/resources/images/candy.png
new file mode 100644
index 0000000..6862980
--- /dev/null
+++ b/resources/images/candy.png
Binary files differ
diff --git a/resources/images/coffee.png b/resources/images/coffee.png
new file mode 100644
index 0000000..47da029
--- /dev/null
+++ b/resources/images/coffee.png
Binary files differ
diff --git a/resources/images/loader.gif b/resources/images/loader.gif
new file mode 100644
index 0000000..d3ef195
--- /dev/null
+++ b/resources/images/loader.gif
Binary files differ
diff --git a/resources/images/locate.png b/resources/images/locate.png
new file mode 100644
index 0000000..14faeb7
--- /dev/null
+++ b/resources/images/locate.png
Binary files differ
diff --git a/resources/images/locate.svg b/resources/images/locate.svg
new file mode 100644
index 0000000..1b5d318
--- /dev/null
+++ b/resources/images/locate.svg
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="90"
+ height="30"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ inkscape:export-filename="/Users/dominik/Developer/JavaScript/Leaflet.locatecontrol/src/images/locate_new.png"
+ inkscape:export-xdpi="300"
+ inkscape:export-ydpi="300"
+ sodipodi:docname="locate_new.svg">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3799">
+ <stop
+ style="stop-color:#ff8700;stop-opacity:1;"
+ offset="0"
+ id="stop3801" />
+ <stop
+ style="stop-color:#ffe300;stop-opacity:1;"
+ offset="1"
+ id="stop3803" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3817">
+ <stop
+ style="stop-color:#0090ff;stop-opacity:1;"
+ offset="0"
+ id="stop3819" />
+ <stop
+ style="stop-color:#00efff;stop-opacity:1;"
+ offset="1"
+ id="stop3821" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3823"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3046"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3181"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3183"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3199"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3201"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3214"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3216"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3225"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3251"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient4016"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient4030"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient4054"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient4056"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568543"
+ inkscape:cx="34.140928"
+ inkscape:cy="17.703589"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1626"
+ inkscape:window-height="1006"
+ inkscape:window-x="1280"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:snap-global="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="main"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1022.3622)"
+ style="display:inline">
+ <path
+ style="fill:#404040;fill-opacity:1;stroke:#404040;stroke-width:1.20000005;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m -20,1025.3787 -5,10.9835 5.044194,-4.5303 4.955806,4.5303 z"
+ id="path3040"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ id="path3818"
+ d="m 20.298511,1031.9111 -10.5556336,4.5115 6.4490076,0.051 0.577406,6.3608 z"
+ style="fill:#404040;fill-opacity:1;stroke:#404040;stroke-width:1.14146423;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ <path
+ style="fill:#005c94;fill-opacity:1;stroke:#005c94;stroke-width:1.14146422999999997;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ d="m 50.298511,1031.9111 -10.555634,4.5115 6.449008,0.051 0.577406,6.3608 z"
+ id="path3820"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:nodetypes="ccccc"
+ inkscape:connector-curvature="0"
+ id="path3822"
+ d="m 80.298511,1031.9111 -10.555634,4.5115 6.449008,0.051 0.577406,6.3608 z"
+ style="fill:#ff9900;fill-opacity:1;stroke:#ff9900;stroke-width:1.14146422999999997;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="mobile"
+ style="display:none">
+ <path
+ transform="matrix(1.1593579,0,0,1.1593579,10.043852,10.043869)"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ sodipodi:ry="6"
+ sodipodi:rx="6"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path3989"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ sodipodi:type="arc" />
+ <rect
+ y="15.60867"
+ x="10.043851"
+ height="2.7824588"
+ width="4.1736889"
+ id="rect3991"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect3993"
+ width="4.1736889"
+ height="2.7824588"
+ x="19.782459"
+ y="15.60867" />
+ <rect
+ y="10.044036"
+ x="15.60877"
+ height="4.1737127"
+ width="2.7824588"
+ id="rect3995"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect3997"
+ width="2.7824588"
+ height="4.1736636"
+ x="15.60877"
+ y="19.782412" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient4054);fill-opacity:1;stroke:none;display:inline"
+ id="path4004"
+ sodipodi:cx="6"
+ sodipodi:cy="6"
+ sodipodi:rx="2"
+ sodipodi:ry="2"
+ d="M 8,6 A 2,2 0 1 1 4,6 2,2 0 1 1 8,6 z"
+ transform="matrix(3.46875,0,0,3.46875,26.125,-3.875)" />
+ <path
+ transform="matrix(3.5,0,0,3.5,56,-4)"
+ d="M 8,6 A 2,2 0 1 1 4,6 2,2 0 1 1 8,6 z"
+ sodipodi:ry="2"
+ sodipodi:rx="2"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path4018"
+ style="fill:url(#linearGradient4056);fill-opacity:1;stroke:none;display:inline"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ id="path4034"
+ sodipodi:cx="6"
+ sodipodi:cy="6"
+ sodipodi:rx="6"
+ sodipodi:ry="6"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ transform="matrix(1.1593579,0,0,1.1593579,40.043852,10.043869)" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4036"
+ width="4.1736889"
+ height="2.7824588"
+ x="40.04385"
+ y="15.60867" />
+ <rect
+ y="15.60867"
+ x="49.782459"
+ height="2.7824588"
+ width="4.1736889"
+ id="rect4038"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4040"
+ width="2.7824588"
+ height="4.1737127"
+ x="45.608772"
+ y="10.044036" />
+ <rect
+ y="19.782412"
+ x="45.608772"
+ height="4.1736636"
+ width="2.7824588"
+ id="rect4042"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <path
+ transform="matrix(1.1593579,0,0,1.1593579,70.043852,10.043869)"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ sodipodi:ry="6"
+ sodipodi:rx="6"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path4044"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ sodipodi:type="arc" />
+ <rect
+ y="15.60867"
+ x="70.043854"
+ height="2.7824588"
+ width="4.1736889"
+ id="rect4046"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4048"
+ width="4.1736889"
+ height="2.7824588"
+ x="79.782455"
+ y="15.60867" />
+ <rect
+ y="10.044036"
+ x="75.608772"
+ height="4.1737127"
+ width="2.7824588"
+ id="rect4050"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4052"
+ width="2.7824588"
+ height="4.1736636"
+ x="75.608772"
+ y="19.782412" />
+ </g>
+</svg>
diff --git a/resources/images/locate_alt.png b/resources/images/locate_alt.png
new file mode 100644
index 0000000..acebf30
--- /dev/null
+++ b/resources/images/locate_alt.png
Binary files differ
diff --git a/resources/images/locate_alt.svg b/resources/images/locate_alt.svg
new file mode 100644
index 0000000..63ccfb5
--- /dev/null
+++ b/resources/images/locate_alt.svg
@@ -0,0 +1,493 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="90"
+ height="30"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ inkscape:export-filename="/Users/dominik/Developer/JavaScript/Leaflet.locatecontrol/src/images/locate.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:docname="locate.svg">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3799">
+ <stop
+ style="stop-color:#ff8700;stop-opacity:1;"
+ offset="0"
+ id="stop3801" />
+ <stop
+ style="stop-color:#ffe300;stop-opacity:1;"
+ offset="1"
+ id="stop3803" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3817">
+ <stop
+ style="stop-color:#0090ff;stop-opacity:1;"
+ offset="0"
+ id="stop3819" />
+ <stop
+ style="stop-color:#00efff;stop-opacity:1;"
+ offset="1"
+ id="stop3821" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3823"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3046"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3181"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3183"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3199"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3201"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3214"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient3216"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3225"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient3251"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient4016"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient4030"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3817"
+ id="linearGradient4054"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3799"
+ id="linearGradient4056"
+ gradientUnits="userSpaceOnUse"
+ x1="6"
+ y1="8"
+ x2="6"
+ y2="4" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="8"
+ inkscape:cx="18.329426"
+ inkscape:cy="38.429117"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="true"
+ inkscape:window-width="1626"
+ inkscape:window-height="1006"
+ inkscape:window-x="1280"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:snap-global="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid2985"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="main"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-1022.3622)"
+ style="display:inline">
+ <path
+ transform="matrix(2.5,0,0,2.5,30,1022.3622)"
+ d="M 8,6 A 2,2 0 1 1 4,6 2,2 0 1 1 8,6 z"
+ sodipodi:ry="2"
+ sodipodi:rx="2"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path3167"
+ style="fill:url(#linearGradient3251);fill-opacity:1;stroke:none;display:inline"
+ sodipodi:type="arc" />
+ <path
+ transform="matrix(0.86950283,0,0,0.86950283,39.782983,1032.1452)"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ sodipodi:ry="6"
+ sodipodi:rx="6"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path3958"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ <rect
+ y="1036.3187"
+ x="39.782982"
+ height="2.0868068"
+ width="3.1302104"
+ id="rect3960"
+ style="fill:#404040;fill-opacity:1;stroke:none" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none"
+ id="rect3962"
+ width="3.1302104"
+ height="2.0868068"
+ x="47.086807"
+ y="1036.3187" />
+ <rect
+ y="1032.1453"
+ x="43.956596"
+ height="3.1302283"
+ width="2.0868068"
+ id="rect3964"
+ style="fill:#404040;fill-opacity:1;stroke:none" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none"
+ id="rect3966"
+ width="2.0868068"
+ height="3.1301918"
+ x="43.956596"
+ y="1039.449" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="path3946"
+ sodipodi:cx="6"
+ sodipodi:cy="6"
+ sodipodi:rx="6"
+ sodipodi:ry="6"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ transform="matrix(0.86950283,0,0,0.86950283,9.782983,1032.1452)" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none"
+ id="rect3948"
+ width="3.1302104"
+ height="2.0868068"
+ x="9.7829819"
+ y="1036.3187" />
+ <rect
+ y="1036.3187"
+ x="17.086807"
+ height="2.0868068"
+ width="3.1302104"
+ id="rect3950"
+ style="fill:#404040;fill-opacity:1;stroke:none" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none"
+ id="rect3952"
+ width="2.0868068"
+ height="3.1302283"
+ x="13.956596"
+ y="1032.1453" />
+ <rect
+ y="1039.449"
+ x="13.956596"
+ height="3.1301918"
+ width="2.0868068"
+ id="rect3954"
+ style="fill:#404040;fill-opacity:1;stroke:none" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient3046);fill-opacity:1;stroke:none;display:inline"
+ id="path3044"
+ sodipodi:cx="6"
+ sodipodi:cy="6"
+ sodipodi:rx="2"
+ sodipodi:ry="2"
+ d="M 8,6 A 2,2 0 1 1 4,6 2,2 0 1 1 8,6 z"
+ transform="matrix(2.5,0,0,2.5,60,1022.3622)" />
+ <path
+ transform="matrix(0.86950283,0,0,0.86950283,69.782983,1032.1452)"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ sodipodi:ry="6"
+ sodipodi:rx="6"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path3968"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ sodipodi:type="arc" />
+ <rect
+ y="1036.3187"
+ x="69.782982"
+ height="2.0868068"
+ width="3.1302104"
+ id="rect3970"
+ style="fill:#404040;fill-opacity:1;stroke:none" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none"
+ id="rect3972"
+ width="3.1302104"
+ height="2.0868068"
+ x="77.086807"
+ y="1036.3187" />
+ <rect
+ y="1032.1453"
+ x="73.956596"
+ height="3.1302283"
+ width="2.0868068"
+ id="rect3974"
+ style="fill:#404040;fill-opacity:1;stroke:none" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none"
+ id="rect3976"
+ width="2.0868068"
+ height="3.1301918"
+ x="73.956596"
+ y="1039.449" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="mobile"
+ style="display:none">
+ <path
+ transform="matrix(1.1593579,0,0,1.1593579,10.043852,10.043869)"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ sodipodi:ry="6"
+ sodipodi:rx="6"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path3989"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ sodipodi:type="arc" />
+ <rect
+ y="15.60867"
+ x="10.043851"
+ height="2.7824588"
+ width="4.1736889"
+ id="rect3991"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect3993"
+ width="4.1736889"
+ height="2.7824588"
+ x="19.782459"
+ y="15.60867" />
+ <rect
+ y="10.044036"
+ x="15.60877"
+ height="4.1737127"
+ width="2.7824588"
+ id="rect3995"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect3997"
+ width="2.7824588"
+ height="4.1736636"
+ x="15.60877"
+ y="19.782412" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient4054);fill-opacity:1;stroke:none;display:inline"
+ id="path4004"
+ sodipodi:cx="6"
+ sodipodi:cy="6"
+ sodipodi:rx="2"
+ sodipodi:ry="2"
+ d="M 8,6 A 2,2 0 1 1 4,6 2,2 0 1 1 8,6 z"
+ transform="matrix(3.46875,0,0,3.46875,26.125,-3.875)" />
+ <path
+ transform="matrix(3.5,0,0,3.5,56,-4)"
+ d="M 8,6 A 2,2 0 1 1 4,6 2,2 0 1 1 8,6 z"
+ sodipodi:ry="2"
+ sodipodi:rx="2"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path4018"
+ style="fill:url(#linearGradient4056);fill-opacity:1;stroke:none;display:inline"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ id="path4034"
+ sodipodi:cx="6"
+ sodipodi:cy="6"
+ sodipodi:rx="6"
+ sodipodi:ry="6"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ transform="matrix(1.1593579,0,0,1.1593579,40.043852,10.043869)" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4036"
+ width="4.1736889"
+ height="2.7824588"
+ x="40.04385"
+ y="15.60867" />
+ <rect
+ y="15.60867"
+ x="49.782459"
+ height="2.7824588"
+ width="4.1736889"
+ id="rect4038"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4040"
+ width="2.7824588"
+ height="4.1737127"
+ x="45.608772"
+ y="10.044036" />
+ <rect
+ y="19.782412"
+ x="45.608772"
+ height="4.1736636"
+ width="2.7824588"
+ id="rect4042"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <path
+ transform="matrix(1.1593579,0,0,1.1593579,70.043852,10.043869)"
+ d="M 12,6 A 6,6 0 1 1 0,6 6,6 0 1 1 12,6 z"
+ sodipodi:ry="6"
+ sodipodi:rx="6"
+ sodipodi:cy="6"
+ sodipodi:cx="6"
+ id="path4044"
+ style="fill:none;stroke:#404040;stroke-width:1.79999995;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ sodipodi:type="arc" />
+ <rect
+ y="15.60867"
+ x="70.043854"
+ height="2.7824588"
+ width="4.1736889"
+ id="rect4046"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4048"
+ width="4.1736889"
+ height="2.7824588"
+ x="79.782455"
+ y="15.60867" />
+ <rect
+ y="10.044036"
+ x="75.608772"
+ height="4.1737127"
+ width="2.7824588"
+ id="rect4050"
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline" />
+ <rect
+ style="fill:#404040;fill-opacity:1;stroke:none;display:inline"
+ id="rect4052"
+ width="2.7824588"
+ height="4.1736636"
+ x="75.608772"
+ y="19.782412" />
+ </g>
+</svg>
diff --git a/resources/images/locate_touch.png b/resources/images/locate_touch.png
new file mode 100644
index 0000000..9669ff4
--- /dev/null
+++ b/resources/images/locate_touch.png
Binary files differ
diff --git a/resources/images/locate_touch_alt.png b/resources/images/locate_touch_alt.png
new file mode 100644
index 0000000..a3b5196
--- /dev/null
+++ b/resources/images/locate_touch_alt.png
Binary files differ
diff --git a/resources/images/logos/stags-head.png b/resources/images/logos/stags-head.png
new file mode 100755
index 0000000..9b65634
--- /dev/null
+++ b/resources/images/logos/stags-head.png
Binary files differ
diff --git a/resources/images/logos/stags-head2.png b/resources/images/logos/stags-head2.png
new file mode 100755
index 0000000..e61552b
--- /dev/null
+++ b/resources/images/logos/stags-head2.png
Binary files differ
diff --git a/resources/images/logos/susu-cafe.png b/resources/images/logos/susu-cafe.png
new file mode 100755
index 0000000..49a7e86
--- /dev/null
+++ b/resources/images/logos/susu-cafe.png
Binary files differ
diff --git a/resources/images/logos/susu-cafe2.png b/resources/images/logos/susu-cafe2.png
new file mode 100755
index 0000000..1af5a40
--- /dev/null
+++ b/resources/images/logos/susu-cafe2.png
Binary files differ
diff --git a/resources/images/logos/susu-shop.png b/resources/images/logos/susu-shop.png
new file mode 100755
index 0000000..f6820a4
--- /dev/null
+++ b/resources/images/logos/susu-shop.png
Binary files differ
diff --git a/resources/images/logos/susu-shop2.png b/resources/images/logos/susu-shop2.png
new file mode 100755
index 0000000..6bbfb90
--- /dev/null
+++ b/resources/images/logos/susu-shop2.png
Binary files differ
diff --git a/resources/images/logos/susu.png b/resources/images/logos/susu.png
new file mode 100755
index 0000000..44aa056
--- /dev/null
+++ b/resources/images/logos/susu.png
Binary files differ
diff --git a/resources/images/logos/susu2.png b/resources/images/logos/susu2.png
new file mode 100755
index 0000000..d9942f2
--- /dev/null
+++ b/resources/images/logos/susu2.png
Binary files differ
diff --git a/resources/images/logos/the-bridge.png b/resources/images/logos/the-bridge.png
new file mode 100755
index 0000000..e803595
--- /dev/null
+++ b/resources/images/logos/the-bridge.png
Binary files differ
diff --git a/resources/images/logos/the-bridge2.png b/resources/images/logos/the-bridge2.png
new file mode 100755
index 0000000..b5ad5c8
--- /dev/null
+++ b/resources/images/logos/the-bridge2.png
Binary files differ
diff --git a/resources/images/printer.png b/resources/images/printer.png
new file mode 100644
index 0000000..1a5ee39
--- /dev/null
+++ b/resources/images/printer.png
Binary files differ
diff --git a/resources/images/search-icon-mobile.png b/resources/images/search-icon-mobile.png
new file mode 100644
index 0000000..920fa27
--- /dev/null
+++ b/resources/images/search-icon-mobile.png
Binary files differ
diff --git a/resources/images/search-icon.png b/resources/images/search-icon.png
new file mode 100644
index 0000000..b51c165
--- /dev/null
+++ b/resources/images/search-icon.png
Binary files differ
diff --git a/resources/images/spinner.gif b/resources/images/spinner.gif
new file mode 100644
index 0000000..aa70283
--- /dev/null
+++ b/resources/images/spinner.gif
Binary files differ
diff --git a/resources/images/toilets-f.png b/resources/images/toilets-f.png
new file mode 100644
index 0000000..4117ab4
--- /dev/null
+++ b/resources/images/toilets-f.png
Binary files differ
diff --git a/resources/images/toilets-m.png b/resources/images/toilets-m.png
new file mode 100644
index 0000000..93cdbfa
--- /dev/null
+++ b/resources/images/toilets-m.png
Binary files differ
diff --git a/resources/images/toilets.png b/resources/images/toilets.png
new file mode 100644
index 0000000..17ccfd8
--- /dev/null
+++ b/resources/images/toilets.png
Binary files differ
diff --git a/resources/images/toilets_disability.png b/resources/images/toilets_disability.png
new file mode 100644
index 0000000..240539c
--- /dev/null
+++ b/resources/images/toilets_disability.png
Binary files differ
diff --git a/resources/images/workstation-big.png b/resources/images/workstation-big.png
new file mode 100644
index 0000000..090d182
--- /dev/null
+++ b/resources/images/workstation-big.png
Binary files differ
diff --git a/resources/images/workstation-closed.png b/resources/images/workstation-closed.png
new file mode 100644
index 0000000..d8ff7b7
--- /dev/null
+++ b/resources/images/workstation-closed.png
Binary files differ
diff --git a/resources/images/workstation-group.png b/resources/images/workstation-group.png
new file mode 100644
index 0000000..d334886
--- /dev/null
+++ b/resources/images/workstation-group.png
Binary files differ
diff --git a/resources/images/workstation.png b/resources/images/workstation.png
new file mode 100644
index 0000000..a60de27
--- /dev/null
+++ b/resources/images/workstation.png
Binary files differ
diff --git a/resources/leaflet b/resources/leaflet
new file mode 160000
+Subproject bcf370b5be176bd6d23febb003dc8abcd2da5f3
diff --git a/resources/leaflet-hash b/resources/leaflet-hash
new file mode 160000
+Subproject d73fc84d8cac7f1ea7068e4fe1dcf70f9379183
diff --git a/resources/leaflet-locatecontrol b/resources/leaflet-locatecontrol
new file mode 160000
+Subproject 1c3cd90e5f381d1cb46ca7c2efc13c4b2f0e6ca
diff --git a/resources/leaflet-markercluster b/resources/leaflet-markercluster
new file mode 160000
+Subproject 6e9ffc45ec9655f8dc9fefa4739fb4e6e7a4fa0
diff --git a/resources/mfd-location b/resources/mfd-location
new file mode 160000
+Subproject fe7f0b1e4485347eb995f36db80ba62965317fb
diff --git a/resources/syntaxhighlighter b/resources/syntaxhighlighter
new file mode 160000
+Subproject 841f53168059d89720e26bde2664291961d06a0
diff --git a/src/leaflet-soton.css b/src/leaflet-soton.css
new file mode 100644
index 0000000..2bf18c2
--- /dev/null
+++ b/src/leaflet-soton.css
@@ -0,0 +1,275 @@
+.ls-content-table a {
+ text-decoration: none;
+}
+
+.ls-popup-title {
+ font: 25px/16px Arial, Helvetica, sans-serif;
+}
+
+.ls-levelselector {
+ line-height: 18px;
+ color: #555;
+ padding: 6px 8px;
+ font: 14px/16px Arial, Helvetica, sans-serif;
+ background: white;
+ background: rgba(255,255,255,0.8);
+ box-shadow: 0 0 15px rgba(0,0,0,0.2);
+ border-radius: 5px;
+}
+
+.ls-workstation-marker {
+ text-align: center;
+}
+
+.ls-room-marker {
+ text-align: center;
+}
+
+.ls-workstationicon {
+ width: 66px;
+ height: 32px;
+ text-align: center;
+ font-size: 23px;
+}
+
+#ls-dynamicContentWrapper {
+ background: white;
+ min-height: 100%;
+ width: 100%;
+ z-index: 2000;
+ position: absolute;
+ padding-left: 5px;
+ padding-top: 5px;
+ padding-right: 5px;
+ left: 0px;
+ top: 0px;
+ display: none;
+}
+
+#ls-dynamicContent {
+
+}
+
+.ls-btn {
+ display: inline-block;
+ *display: inline;
+ /* IE7 inline-block hack */
+
+ *zoom: 1;
+ padding: 4px 10px 4px;
+ margin-bottom: 0;
+ font-size: 13px;
+ line-height: 18px;
+ color: #333333;
+ text-align: center;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+ vertical-align: middle;
+ background-color: #f5f5f5;
+ background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+ background-image: linear-gradient(top, #ffffff, #e6e6e6);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+ border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+ border: 1px solid #cccccc;
+ border-bottom-color: #b3b3b3;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+ cursor: pointer;
+ *margin-left: .3em;
+}
+.ls-btn:hover,
+.ls-btn:active,
+.ls-btn.active,
+.ls-btn.disabled,
+.ls-btn[disabled] {
+ background-color: #e6e6e6;
+}
+.ls-btn:active,
+.ls-btn.active {
+ background-color: #cccccc \9;
+}
+.ls-btn:first-child {
+ *margin-left: 0;
+}
+.ls-btn:hover {
+ color: #333333;
+ text-decoration: none;
+ background-color: #e6e6e6;
+ background-position: 0 -15px;
+ -webkit-transition: background-position 0.1s linear;
+ -moz-transition: background-position 0.1s linear;
+ -ms-transition: background-position 0.1s linear;
+ -o-transition: background-position 0.1s linear;
+ transition: background-position 0.1s linear;
+}
+.ls-btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+.ls-btn.active,
+.ls-btn:active {
+ background-image: none;
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+ background-color: #e6e6e6;
+ background-color: #d9d9d9 \9;
+ outline: 0;
+}
+
+.ls-btn-group .ls-btn {
+ position: relative;
+ float: left;
+ margin-left: -1px;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+.ls-btn-group .ls-btn:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ border-top-left-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ border-bottom-left-radius: 4px;
+}
+.ls-btn-group .ls-btn:last-child,
+.ls-btn-group .dropdown-toggle {
+ -webkit-border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+ border-bottom-right-radius: 4px;
+}
+.ls-btn-group .ls-btn.large:first-child {
+ margin-left: 0;
+ -webkit-border-top-left-radius: 6px;
+ -moz-border-radius-topleft: 6px;
+ border-top-left-radius: 6px;
+ -webkit-border-bottom-left-radius: 6px;
+ -moz-border-radius-bottomleft: 6px;
+ border-bottom-left-radius: 6px;
+}
+.ls-btn-group .ls-btn.large:last-child,
+.ls-btn-group .large.dropdown-toggle {
+ -webkit-border-top-right-radius: 6px;
+ -moz-border-radius-topright: 6px;
+ border-top-right-radius: 6px;
+ -webkit-border-bottom-right-radius: 6px;
+ -moz-border-radius-bottomright: 6px;
+ border-bottom-right-radius: 6px;
+}
+.ls-btn-group .ls-btn:hover,
+.ls-btn-group .ls-btn:focus,
+.ls-btn-group .ls-btn:active,
+.ls-btn-group .ls-btn.active {
+ z-index: 2;
+}
+
+.ls-nav {
+ margin-left: 0;
+ padding: 0;
+ margin-bottom: 18px;
+ list-style: none;
+}
+.ls-nav > li > a {
+ text-decoration: none;
+ display: block;
+ font-size: medium
+}
+.ls-nav > li > a:hover {
+ background-color: #eeeeee;
+}
+.ls-nav .ls-nav-header {
+ display: block;
+ padding: 3px 15px;
+ font-size: 11px;
+ font-weight: bold;
+ line-height: 18px;
+ color: #999999;
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+ text-transform: uppercase;
+}
+.ls-nav li + .ls-nav-header {
+ margin-top: 9px;
+}
+.ls-nav-list {
+ padding-left: 15px;
+ padding-right: 15px;
+ margin-bottom: 0;
+}
+.ls-nav-tabs:before,
+.ls-nav-pills:before,
+.ls-nav-tabs:after,
+.ls-nav-pills:after {
+ display: table;
+ content: "";
+}
+.ls-nav-tabs:after,
+.ls-nav-pills:after {
+ clear: both;
+}
+.ls-nav-tabs > li,
+.ls-nav-pills > li {
+ float: left;
+}
+.ls-nav-tabs > li > a,
+.ls-nav-pills > li > a {
+ padding-right: 12px;
+ padding-left: 12px;
+ margin-right: 2px;
+ line-height: 14px;
+}
+.ls-nav-tabs {
+ border-bottom: 1px solid #ddd;
+}
+.ls-nav-tabs > li {
+ margin-bottom: -1px;
+}
+.ls-nav-tabs > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ line-height: 18px;
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px 4px 0 0;
+ -moz-border-radius: 4px 4px 0 0;
+ border-radius: 4px 4px 0 0;
+}
+.ls-nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+.ls-nav-tabs > .active > a,
+.ls-nav-tabs > .active > a:hover {
+ color: #555555;
+ background-color: #ffffff;
+ border: 1px solid #ddd;
+ border-bottom-color: transparent;
+ cursor: default;
+}
+.ls-nav-pills > li > a {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+.ls-nav-pills > .active > a,
+.ls-nav-pills > .active > a:hover {
+ color: #ffffff;
+ background-color: #0088cc;
+}
diff --git a/src/leaflet-soton.js b/src/leaflet-soton.js
new file mode 100644
index 0000000..accd7f7
--- /dev/null
+++ b/src/leaflet-soton.js
@@ -0,0 +1,1851 @@
+(function() {
+ "use strict";
+
+ var LS = window.LS = L.extend({}, L.Mixin.Events, {
+
+ dataPath: 'data.json',
+ imagePath: 'images/',
+ data: null,
+ _dataFetchInProgress: false,
+ workstationData: null,
+ _workstationDataFetchInProgress: false,
+
+ getData: function(callback) {
+ if (this.data !== null) {
+ callback(this.data);
+ } else {
+ this.on("dataload", callback);
+
+ if (!this._dataFetchInProgress) {
+ this._dataFetchInProgress = true;
+ getJSON({url: LS.dataPath} , function(data) {
+ LS.data = data;
+ LS._dataFetchInProgress = false;
+
+ LS.fire("dataload", data);
+ });
+ }
+ }
+ },
+ getWorkstationData: function(callback) {
+ if (this.workstationData !== null) { // TODO: Some kind of periodic refresh
+ callback(this.workstationData);
+ } else {
+ this.addOneTimeEventListener("workstationData", callback);
+ if (!this._workstationDataFetchInProgress) {
+ this._workstationDataFetchInProgress = true;
+ this._updateWorkstationData();
+ }
+ }
+ },
+ getFeatureByURI: function(uri) {
+ var features, feature;
+
+ var names = Object.keys(LS.data);
+
+ for (var i=0; i<names.length; i++) {
+ features = LS.data[names[i]].features;
+
+ for (var j=0; j<features.length; j++) {
+ feature = features[j];
+
+ if ("uri" in feature.properties &&
+ feature.properties.uri === uri) {
+ return feature;
+ }
+ }
+ }
+
+ return null;
+ },
+ _updateWorkstationData: function() {
+ var query;
+
+ if (this.data.workstations.features.length > 10) {
+ query = 'PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+SELECT * WHERE {\
+ ?uri soton:workstationSeats ?total_seats .\
+ ?uri soton:workstationFreeSeats ?free_seats .\
+ ?uri soton:workstationStatus ?status .\
+ FILTER (\
+ ?free_seats >= 0\
+ )\
+}';
+ } else {
+ query = 'PREFIX soton: <http://id.southampton.ac.uk/ns/>\
+SELECT * WHERE {\
+ ?uri soton:workstationSeats ?total_seats .\
+ ?uri soton:workstationFreeSeats ?free_seats .\
+ ?uri soton:workstationStatus ?status .\
+ FILTER (';
+
+ query += '(' + workstations.features.map(function(workstation) {
+ return '?uri = <' + workstation.properties.uri + '> ';
+ }).join(' || ');
+
+ query += ') && ?free_seats >= 0';
+
+ query += ')}';
+ }
+
+ getJSON({
+ url: 'http://sparql.data.southampton.ac.uk/?query=' + encodeURIComponent(query)
+ },
+ function(data) {
+ LS.workstationData = {};
+
+ if (data === null) {
+ LS.fire("workstationData", null);
+ return;
+ }
+
+ data.results.bindings.forEach(function(result) {
+ var workstation = result.uri.value;
+ var id = "#" + workstation.split('/').slice(-1)[0];
+ var obj = {};
+
+ obj.total_seats = parseInt(result.total_seats.value, 10);
+ obj.free_seats = parseInt(result.free_seats.value, 10);
+ obj.status = result.status.value;
+
+ LS.workstationData[workstation] = obj;
+ });
+
+ LS._workstationDataFetchInProgress = false;
+
+ LS.fire("workstationData", LS.workstationData);
+ }
+ );
+ }
+ });
+
+ var busRouteColours = {};
+
+ var icons = {
+ created: false,
+ createIcons: function() {
+ this.printer = L.icon({
+ iconUrl: LS.imagePath + 'printer.png',
+
+ iconSize: [32, 37], // size of the icon
+ iconAnchor: [16, 37], // point of the icon which will correspond to marker's location
+ popupAnchor: [0, -35] // point from which the popup should open relative to the iconAnchor
+ });
+
+ this.vendingHotDrinks = L.icon({
+ iconUrl: LS.imagePath + 'coffee.png',
+
+ iconSize: [32, 37],
+ iconAnchor: [16, 37],
+ popupAnchor: [0, -35]
+ });
+
+ this.vendingSweets = L.icon({
+ iconUrl: LS.imagePath + 'candy.png',
+
+ iconSize: [32, 37],
+ iconAnchor: [16, 37],
+ popupAnchor: [0, -35]
+ });
+
+ this.toiletsUnisex = L.icon({
+ iconUrl: LS.imagePath + 'toilets.png',
+
+ iconSize: [32, 32],
+ iconAnchor: [16, 16],
+ popupAnchor: [0, -35]
+ });
+
+ this.toiletsMale = L.icon({
+ iconUrl: LS.imagePath + 'toilets-m.png',
+
+ iconSize: [32, 32],
+ iconAnchor: [16, 16],
+ popupAnchor: [0, -35]
+ });
+
+ this.toiletsFemale = L.icon({
+ iconUrl: LS.imagePath + 'toilets-f.png',
+
+ iconSize: [32, 32],
+ iconAnchor: [16, 16],
+ popupAnchor: [0, -35]
+ });
+
+ this.toiletsDisabled = L.icon({
+ iconUrl: LS.imagePath + 'toilets_disability.png',
+
+ iconSize: [32, 32],
+ iconAnchor: [16, 37],
+ popupAnchor: [0, -35]
+ });
+
+ this.theBridge = L.icon({
+ iconUrl: LS.imagePath + 'logos/the-bridge.png',
+
+ iconSize: [300, 80],
+ iconAnchor: [150, 40],
+ popupAnchor: [0, 0]
+ });
+
+ this.theStags = L.icon({
+ iconUrl: LS.imagePath + 'logos/stags-head.png',
+
+ iconSize: [300, 80],
+ iconAnchor: [150, 40],
+ popupAnchor: [0, 0]
+ });
+
+ this.theSUSUShop = L.icon({
+ iconUrl: LS.imagePath + 'logos/susu-shop.png',
+
+ iconSize: [300, 80],
+ iconAnchor: [150, 40],
+ popupAnchor: [0, 0]
+ });
+
+ this.theSUSUCafe = L.icon({
+ iconUrl: LS.imagePath + 'logos/susu-cafe.png',
+
+ iconSize: [300, 80],
+ iconAnchor: [150, 40],
+ popupAnchor: [0, 0]
+ });
+
+ this.created = true;
+ }
+ };
+
+ var blankStyle = function(feature) {
+ return {
+ weight: 0,
+ opacity: 0,
+ fillOpacity: 0
+ };
+ };
+
+ var showStyle = function(feature) {
+ return {
+ weight: 1,
+ opacity: 1,
+ fillOpacity: 1
+ };
+ };
+
+ var emptyFeatureCollection = { type: "FeatureCollection", features: [] };
+ var transparaentStyle = function(feature) {return {weight: 0, opacity: 0, fillOpacity: 0};};
+
+ var layerNames = ['sites', 'parking', 'bicycleParking', 'buildings', 'busStops' /*'busRoutes',*/];
+
+ var busRouteStyle = function(feature) {
+ return {weight: 5, opacity: 0.5, color: feature.properties.colour};
+ };
+
+ LS.Map = L.Map.extend({
+ options: {
+ center: [50.9354, -1.3964],
+ indoor: false,
+ workstations: false,
+ zoom: 17,
+ tileUrl: 'http://bus.southampton.ac.uk/graphics/map/tiles/{z}/{x}/{y}.png',
+ tileAttribution: 'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors'
+ },
+
+ initialize: function (id, options) {
+ options = L.setOptions(this, options);
+
+ L.Map.prototype.initialize.call(this, id, options);
+ var map = this;
+
+ if (!("layers" in options) || options.layers.length === 0) {
+ var tileLayer = L.tileLayer(options.tileUrl, {
+ maxZoom: 22,
+ attribution: options.tileAttribution
+ });
+
+ tileLayer.addTo(map);
+ }
+
+ if (!("MarkerClusterGroup" in L)) {
+ options.workstations = false;
+ }
+
+ if (!("highlight" in options)) {
+ options.highlight = {};
+ }
+
+ var overlayMaps = {
+ //"Bus Routes": self.layers.busRoutes,
+ };
+
+ if ("Hash" in L) {
+ var hash;
+ if (this.options.indoor) {
+ hash = new LS.Hash(this);
+ } else {
+ hash = new L.Hash(this);
+ }
+ }
+
+ if (!icons.created) {
+ icons.createIcons();
+ }
+
+ var layers = {};
+ var showingIndoorControl = false;
+ var showLevel = null;
+ map._startLevel = options.level || "1";
+
+ layerNames.forEach(function(layerName) {
+ var layerOptions = {
+ style: function(feature) {
+ if (feature.properties.uri in options.highlight &&
+ options.highlight[feature.properties.uri]) {
+
+ return {weight: 5, opacity: 0.5, color: 'blue'};
+ } else if (layerName === "busRoutes") {
+ return busRouteStyle();
+ } else {
+ return blankStyle();
+ }
+ }
+ };
+
+ if (layerName === 'buildings') {
+ layerOptions.onEachFeature = function(feature, layer) {
+ // When the feature is clicked on
+ layer.on('click', function(e) {
+ var close;
+
+ var content = buildingTemplate(feature.properties,
+ options.indoor,
+ map,
+ function() { close(); });
+
+ close = showPopup(map, content, e.latlng);
+ });
+ };
+ } else {
+ layerOptions.onEachFeature = function(feature, layer) {
+ // When the feature is clicked on
+ layer.on('click', function(e) {
+ var content = popupTemplates[layerName](feature.properties);
+
+ showPopup(map, content, e.latlng);
+ });
+ };
+ }
+
+ if (layerName === "bicycleParking") {
+ layerOptions.pointToLayer = function (feature, latlng) {
+ return L.circleMarker(latlng, {
+ radius: 8,
+ opacity: 1,
+ });
+ };
+ }
+
+ if (layerName === "busStops") {
+ layerOptions.pointToLayer = function (feature, latlng) {
+ return L.circleMarker(latlng, {
+ radius: 8,
+ opacity: 1,
+ });
+ };
+ }
+
+ layers[layerName] = L.geoJson(emptyFeatureCollection, layerOptions).addTo(map);
+ });
+
+ this.on('zoomend', function(e) {
+ var zoom = this.getZoom();
+
+ // The buildingParts layer wants to show on zooms > 19, that is 20, 21 and 22
+ // The sites layer wants to show on zoom levels less than 18, that is 17 - 1
+
+ if (zoom <= 15) {
+ if (!(this.hasLayer(layers.sites))) {
+ this.addLayer(layers.sites, true);
+ }
+ } else if (zoom > 15) {
+ if (this.hasLayer(layers.sites)) {
+ this.removeLayer(layers.sites);
+ }
+ }
+ });
+
+
+ LS.getData(function(data) {
+ for (var layerName in layers) {
+ var layer = layers[layerName];
+
+ layer.clearLayers();
+ layer.addData(data[layerName]);
+ }
+
+ LS.getWorkstationData(function(workstationData) {
+
+ // Adding .features means leaflet will
+ // ignore those without a geometry
+ map.indoorLayer = L.indoorLayer(data.buildingParts.features, {
+ level: map._startLevel,
+ style: function(feature) {
+ var fill = 'white';
+ if (feature.properties.buildingpart === 'corridor') {
+ fill = '#169EC6';
+ }
+
+ return {
+ fillColor: fill,
+ weight: 1,
+ color: '#666',
+ fillOpacity: 1
+ };
+ },
+ markerForFeature: function(part) {
+ if (part.properties.buildingpart === "room") {
+
+ var iconCoords = part.properties.center;
+
+ if (part.properties.name === "The Bridge") {
+ return L.marker(iconCoords, {icon: icons.theBridge});
+ } else if (part.properties.name === "SUSU Shop") {
+ return L.marker(iconCoords, {icon: icons.theSUSUShop});
+ } else if (part.properties.name === "The Stag's") {
+ return L.marker(iconCoords, {icon: icons.theStags});
+ } else if (part.properties.name === "SUSU Cafe") {
+ return L.marker(iconCoords, {icon: icons.theSUSUCafe});
+ }
+
+ var partWorkstation = null;
+
+ if ('contents' in part.properties) {
+ part.properties.contents.forEach(function(feature) {
+ if (feature.subject === "http://id.southampton.ac.uk/point-of-interest-category/iSolutions-Workstations") {
+ partWorkstation = feature;
+ }
+ });
+ }
+
+ if (part.properties.amenity === "toilets") {
+ if ("male" in part.properties) {
+ return L.marker(iconCoords, {icon: icons.toiletsMale});
+ } else if ("female" in part.properties) {
+ return L.marker(iconCoords, {icon: icons.toiletsFemale});
+ } else if ("unisex" in part.properties) {
+ return L.marker(iconCoords, {icon: icons.toiletsUnisex});
+ } // TODO: Disabled
+ }
+
+ var content;
+
+ if ("name" in part.properties && "ref" in part.properties) {
+ content = part.properties.name + " (" + part.properties.ref + ")";
+ } else if ("ref" in part.properties) {
+ content = "Room " + part.properties.ref;
+ } else if ("name" in part.properties) {
+ content = part.properties.name;
+ } else {
+ return;
+ }
+
+ if (partWorkstation && partWorkstation.feature in workstationData) {
+ var workstationIcon = '<div class="ls-workstationicon" style="margin-left: auto; margin-right: auto; background-image: url(' + LS.imagePath + 'workstation-group.png' + ')"><div style="padding-left: 26px;">';
+
+ var freeSeats = workstationData[partWorkstation.feature].free_seats;
+
+ workstationIcon += freeSeats + "</div></div>";
+
+ content = workstationIcon + content;
+ }
+
+ var myIcon = L.divIcon({
+ className: 'ls-room-marker',
+ html: content,
+ iconSize: new L.Point(100, 30),
+ iconAnchor: new L.Point(50, 15)
+ });
+
+ var marker = L.marker(iconCoords, {icon: myIcon});
+
+ return marker;
+ }
+ },
+ onEachFeature: function(feature, layer) {
+ layer.on('click', function(e) {
+ var content;
+ var popupOptions = {};
+
+ // When the feature is clicked on
+ if ("buildingpart" in feature.properties) {
+ content = roomPopupTemplate(feature.properties);
+ } else { // Assume that it is a printer
+ // TODO: Use different icons where appropriate
+ popupOptions.offset = icons.vendingHotDrinks.options.popupAnchor;
+
+ if ('vending' in feature.properties) {
+ content = vendingPopupTemplate(feature.properties);
+ } else {
+ content = printerPopupTemplate(feature.properties);
+ }
+ }
+
+ showPopup(map, content, e.latlng, popupOptions);
+ });
+ },
+ pointToLayer: function (feature, latlng) {
+ var icon;
+
+ if ('vending' in feature.properties) {
+ if (feature.properties.vending === 'drinks') {
+ icon = icons.vendingHotDrinks;
+ } else if (feature.properties.vending === 'sweets') {
+ icon = icons.vendingSweets;
+ } else {
+ console.warn("Unrecognired vending " + feature.properties.vending);
+ }
+ } else {
+ icon = icons.printer;
+ }
+
+ return L.marker(latlng, {icon: icon});
+ }
+ });
+
+ map.indoorLayer.addData(data.buildingFeatures);
+
+ map.levelControl = L.Control.level({
+ levels: map.indoorLayer.getLevels(),
+ level: map._startLevel
+ });
+
+ map.levelControl.addEventListener("levelchange", map.indoorLayer.setLevel, map.indoorLayer);
+
+ map.levelControl.on("levelchange", function(e) {
+ map.fireEvent("levelchange", e);
+ });
+
+ var workstationMarkerLayer;
+ if (options.workstations) {
+ workstationMarkerLayer = LS.workstationLayer();
+ }
+
+ if (options.indoor) {
+ var setIndoorContent = function(zoom) {
+ if (zoom <= 19) {
+ if (showingIndoorControl) {
+ map.levelControl.removeFrom(map);
+ showingIndoorControl = false;
+ }
+
+ if (map.hasLayer(map.indoorLayer)) {
+ map.removeLayer(map.indoorLayer);
+ }
+
+ if (options.workstations && !map.hasLayer(workstationMarkerLayer)) {
+ map.addLayer(workstationMarkerLayer);
+ }
+ } else if (zoom > 19) {
+ if (!showingIndoorControl) {
+ map.levelControl.addTo(map);
+ showingIndoorControl = true;
+ }
+
+ if (!map.hasLayer(map.indoorLayer)) {
+ map.addLayer(map.indoorLayer);
+ }
+
+ if (options.workstations && map.hasLayer(workstationMarkerLayer)) {
+ map.removeLayer(workstationMarkerLayer);
+ }
+ }
+ };
+
+ setIndoorContent(map.getZoom());
+
+ map.on('zoomend', function(e) {
+ setIndoorContent(this.getZoom());
+ });
+ } else {
+ if (options.workstations) {
+ map.addLayer(workstationMarkerLayer);
+ }
+ }
+ });
+ });
+
+ return this;
+ },
+ setLevel: function(level) {
+ if ("levelControl" in this) {
+ this.levelControl.setLevel(level);
+ } else {
+ this._startLevel = level;
+ }
+ },
+ getLevel: function() {
+ if ("levelControl" in this) {
+ return this.levelControl.getLevel();
+ } else {
+ return this._startLevel;
+ }
+ },
+ show: function(thing) {
+ this.showByURI(thing);
+ },
+ showPopupByURI: function(uri) {
+ var map = this;
+
+ var buildings = LS.data.buildings.features;
+ for (var i=0; i<buildings.length; i++) {
+ var building = buildings[i];
+
+ if (building.properties.uri === uri) {
+ var close;
+
+ var content = buildingTemplate(building.properties,
+ this.options.indoor,
+ map,
+ function() { close(); });
+
+ var temp = { _rings: building.geometry.coordinates, _map: this };
+
+
+ var center = building.geometry.coordinates[0][0];
+ center = [center[1], center[0]];
+
+ map.panTo(center);
+
+ close = showPopup(map, content, center);
+
+ return;
+ }
+ }
+ },
+ showByURI: function(uri) {
+ var feature = LS.getFeatureByURI(uri);
+
+ if (feature === null) {
+ throw "can't find " + uri;
+ }
+
+ if (!("geometry" in feature)) {
+ throw "no location for " + uri;
+ }
+
+ if (feature.geometry.type === "Polygon") {
+ var coords = L.GeoJSON.coordsToLatLngs(feature.geometry.coordinates[0], 0, L.GeoJSON.coordsToLatLng);
+ var bounds = L.latLngBounds(coords);
+ this.fitBounds(bounds);
+ if ("level" in feature.properties) {
+ this.setLevel(feature.properties.level);
+ }
+ this.closePopup();
+
+ return;
+ } else if (feature.geometry.type === "Point") {
+ this.setView(L.GeoJSON.coordsToLatLng(feature.geometry.coordinates), 22);
+ if ("level" in feature.properties) {
+ this.setLevel(feature.properties.level);
+ }
+ this.closePopup();
+ return;
+ } else {
+ throw "unable to handle " + feature.geometry.type;
+ }
+ }
+ });
+
+ LS.map = function (id, options) {
+ return new LS.Map(id, options);
+ };
+
+ 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 = {
+ sites: siteTemplate,
+ buildings: buildingTemplate,
+ bicycleParking: bicycleParkingTemplate,
+ parking: parkingTemplate,
+ busStops: busStopTemplate
+ };
+
+ function roomPopupTemplate(properties) {
+ properties = L.extend({}, properties);
+
+ if (!("name" in properties)) {
+ properties.name = "Room ";
+ if ("ref" in properties) {
+ properties.name += properties.ref;
+ }
+ }
+
+ return getTemplateWrapper(properties, function(content) {
+
+ var tabs = [
+ {
+ id: 'features',
+ name: 'Features',
+ active: true
+ },
+ {
+ id: 'contents',
+ name: 'Contents',
+ },
+ {
+ id: 'bookings',
+ name: 'Bookings',
+ },
+ {
+ id: 'pictures',
+ name: 'Pictures',
+ }];
+
+ tabs = createTabs(tabs, content);
+
+ if ('contents' in properties) {
+ properties.contents.forEach(function(feature) {
+ createBlankLink(feature.feature, feature.label, tabs.features);
+
+ if (feature.subject === "http://id.southampton.ac.uk/point-of-interest-category/iSolutions-Workstations") {
+ var content = '<a href="' + feature.feature + '">' + feature.label + '</a>';
+ var data = properties.data;
+ if (typeof data !== 'undefined' && 'total_seats' in data) {
+ content += '<br>' + data.status;
+ content += '<br>' + data.free_seats + ' seats free (' + data.total_seats + ' total seats)';
+ }
+ return content;
+ } else {
+ return '<a href="' + feature.feature + '">' + feature.label + '</a>';
+ }
+ });
+ }
+
+ if ('images' in properties) {
+ properties.images.forEach(function(image) {
+
+ });
+ }
+ });
+ }
+
+ function printerPopupTemplate(properties) {
+ properties.name = "Printer";
+
+ return getTemplateWrapper(properties, function(content) {
+
+ });
+ }
+
+ function vendingPopupTemplate(properties) {
+ properties.name = "Vending Machine";
+
+ return getTemplateWrapper(properties, function(content) {
+
+ content.textContent = properties.vending;
+
+ });
+ }
+
+ function siteTemplate(properties) {
+ return getTemplateWrapper(properties, function(content) {
+
+ });
+ }
+
+ function buildingTemplate(properties, indoor, map, close) {
+ return getTemplateWrapper(properties, function(content) {
+
+ var buildingTabs = [
+ {
+ id: 'picture',
+ name: 'Pictures',
+ active: true
+ },
+ {
+ id: 'energyUsage',
+ name: 'Energy Usage',
+ }];
+
+ if (indoor) {
+ buildingTabs.push({
+ id: 'rooms',
+ name: 'Facilities',
+ });
+
+ buildingTabs.push({
+ id: 'services',
+ name: 'Services',
+ });
+ }
+
+ var tabs = createTabs(buildingTabs, content);
+
+ var imageWidth;
+ var imageHeight;
+
+ if (properties.images.length !== 0) {
+
+ var versions = properties.images[0].versions;
+ var url;
+
+ for (var i=0; i<versions.length; i++) {
+ var version = versions[i];
+ url = version.url;
+
+ imageWidth = version.width;
+ imageHeight = version.height;
+
+ var mapContainer = map.getContainer();
+ var widthBound = mapContainer.offsetWidth;
+ var heightBound = mapContainer.offsetHeight;
+
+ if (!smallScreen()) {
+ widthBound *= 0.7;
+ heightBound *= 0.7;
+ }
+
+ if (imageWidth < widthBound &&
+ imageHeight < heightBound) {
+ break; // Use this image, as it is the first smaller
+ // than the screen width
+ }
+ }
+
+ if (url !== null) {
+ // Link to the biggest image (versions is sorted by size on the server)
+ var imageLink = createBlankLink(versions[0].url, false, tabs.picture);
+
+ tabs.picture.style.minWidth = imageWidth + "px";
+ tabs.picture.style.minHeight = imageHeight + "px";
+
+ var image = document.createElement('img');
+ image.setAttribute('src', url);
+ image.setAttribute('width', imageWidth);
+ image.setAttribute('height', imageHeight);
+
+ imageLink.appendChild(image);
+
+ //createBlankLink(properties.images[0].licence, "Licence", tabs.picture);
+ } else {
+ tabs.picture.textContent = "No Image Available";
+ }
+ } else {
+
+ }
+
+ var energyIFrame = document.createElement('iframe');
+ energyIFrame.setAttribute('src', 'http://data.southampton.ac.uk/time-series?action=fetch&series=elec/b' + properties.loc_ref + '/ekw&type=average&format=graph&resolution=3600&startTime=1375009698000.0');
+ energyIFrame.setAttribute('frameBorder', '0');
+ energyIFrame.setAttribute('style', 'width: 100%; height 100%;');
+
+ tabs.energyUsage.style.minWidth = imageWidth + "px";
+ tabs.energyUsage.style.minHeight = imageHeight + "px";
+ tabs.energyUsage.appendChild(energyIFrame);
+
+ createBlankLink('http://data.southampton.ac.uk/time-series?action=fetch&series=elec/b' + properties.loc_ref + '/ekw&type=average&format=graph&resolution=3600&startTime=0', 'Graph for all time', tabs.energyUsage);
+
+ // Rooms
+ if (indoor) {
+
+ tabs.rooms.style.minWidth = imageWidth + "px";
+ tabs.rooms.style.minHeight = imageHeight + "px";
+
+ tabs.rooms.style.maxWidth = imageWidth + "px";
+ tabs.rooms.style.maxHeight = imageHeight + "px";
+
+ tabs.rooms.style.overflow = 'scroll';
+
+ for (var level in properties.rooms) {
+ var rooms = properties.rooms[level];
+
+ // Heading
+
+ var panelTitle = L.DomUtil.create('h4', '', tabs.rooms);
+ panelTitle.textContent = "Level " + level;
+
+ // Content
+
+ var contentPanel = L.DomUtil.create('div', '', tabs.rooms);
+
+ rooms.forEach(function(uri) {
+ var room = LS.getFeatureByURI(uri);
+
+ if (room === null) {
+ console.err("Unable to find room " + uri);
+ return;
+ }
+
+ var teachingAndBookable = "";
+
+ if (room.properties.teaching) {
+ teachingAndBookable += " (T) ";
+ }
+
+ if (room.properties.teaching) {
+ teachingAndBookable += " (B)";
+ }
+
+ var roomProps = document.createTextNode(teachingAndBookable);
+
+ var description = room.properties.ref;
+ if ("name" in room.properties) {
+ description += ": " + room.properties.name;
+ }
+
+ if ("center" in room.properties) {
+ var roomLink = createLink('#', false, tabs.rooms);
+
+ roomLink.onclick = function() {
+ close();
+ map.showByURI(uri);
+ };
+
+ roomLink.textContent = description;
+ } else {
+ var roomText = document.createTextNode(description);
+
+ tabs.rooms.appendChild(roomText);
+ }
+
+ tabs.rooms.appendChild(roomProps);
+
+ var moreInfo = createBlankLink(uri, "(More Information)", tabs.rooms);
+ moreInfo.style.cssFloat = moreInfo.style.styleFloat = "right";
+
+ tabs.rooms.appendChild(document.createElement('br'));
+ });
+ }
+
+ tabs.services.style.minWidth = imageWidth + "px";
+ tabs.services.style.minHeight = imageHeight + "px";
+
+ tabs.services.style.maxWidth = imageWidth + "px";
+ tabs.services.style.maxHeight = imageHeight + "px";
+
+ tabs.services.style.overflow = 'scroll';
+
+ if ("services" in properties) {
+ if ("vendingMachines" in properties.services) {
+ title = L.DomUtil.create('h4', '', tabs.services);
+ title.textContent = "Vending Machines";
+
+ properties.services.vendingMachines.forEach(function(machine) {
+ var feature = LS.getFeatureByURI(machine);
+
+ if (feature === null) {
+ console.error("no feature for " + machine);
+ return;
+ }
+
+ if ("geometry" in feature) {
+ var machineLink = createLink('#', false, tabs.services);
+
+ machineLink.onclick = function() {
+ close();
+ map.showByURI(machine);
+ };
+
+ machineLink.textContent = feature.properties.label;
+ } else {
+ var note = document.createTextNode(feature.properties.label);
+
+ tabs.services.appendChild(note);
+ }
+
+ var moreInfo = createBlankLink(machine, "(More Information)", tabs.services);
+ moreInfo.style.cssFloat = moreInfo.style.styleFloat = "right";
+
+ tabs.services.appendChild(document.createElement('br'));
+ });
+ }
+
+ if ("mfds" in properties.services) {
+ var title = L.DomUtil.create('h4', '', tabs.services);
+ title.textContent = "Multi-Function Devices";
+
+ properties.services.mfds.forEach(function(mfd) {
+ var feature = LS.getFeatureByURI(mfd);
+
+ if (feature === null) {
+ console.error("no feature for " + mfd);
+ return;
+ }
+
+ if ("geometry" in feature) {
+ var mfdLink = createLink('#', false, tabs.services);
+
+ mfdLink.onclick = function() {
+ close();
+ map.showByURI(mfd);
+ };
+
+ mfdLink.textContent = feature.properties.label;
+ } else {
+ var note = document.createTextNode(feature.properties.label);
+
+ tabs.services.appendChild(note);
+ }
+
+ var moreInfo = createBlankLink(mfd, "(More Information)", tabs.services);
+ moreInfo.style.cssFloat = moreInfo.style.styleFloat = "right";
+
+ tabs.services.appendChild(document.createElement('br'));
+ });
+ }
+ }
+ }
+ });
+ }
+
+ function parkingTemplate(properties) {
+ if (!('name' in properties))
+ properties.name = 'Car Park';
+
+ return getTemplateWrapper(properties, function(content) {
+ var table = createPropertyTable(
+ [
+ "Access",
+ "Spaces",
+ "Fee"
+ ],
+ [
+ properties.access,
+ properties.capacity,
+ properties.fee
+ ]
+ );
+
+ content.appendChild(table);
+ });
+ }
+
+ function bicycleParkingTemplate(properties) {
+ if (!('name' in properties))
+ properties.name = 'Bicycle Parking';
+
+ return getTemplateWrapper(properties, function(content) {
+ var table = createPropertyTable(
+ [
+ "Capacity",
+ "Type",
+ "Covered"
+ ],
+ [
+ properties.capacity,
+ properties.bicycle_parking,
+ properties.covered
+ ]
+ );
+
+ content.appendChild(table);
+ });
+ }
+
+ function busStopTemplate(properties) {
+ return getTemplateWrapper(properties, function(content) {
+
+ /*<tr><td>Routes: \
+ <% each(properties.routes, function(route) { %>\
+ <font color="<%= route.colour %>"><%= route.ref %></font>\
+ <% }); %>\
+ </td></tr>\
+ <tr>\
+ <td colspan=2><iframe width="400px" height="100%" style="border: solid 1px #000" src="<%\
+ var parts = properties.uri.split("/");\
+ var id = parts[parts.length - 1].split(".")[0];\
+ print("http://data.southampton.ac.uk/bus-stop/" + id + ".html?view=iframe");\
+ %>" frameBorder="0"></iframe></td>\
+ </tr>\*/
+
+ /*var parts = properties.uri.split("/");
+ var id = parts[parts.length - 1].split(".")[0];
+ var src = "http://data.southampton.ac.uk/bus-stop/" + id + ".html?view=iframe";
+
+ var energyIFrame = document.createElement('iframe');
+ energyIFrame.setAttribute('src', src);
+ energyIFrame.setAttribute('frameBorder', '0');
+ energyIFrame.setAttribute('style', 'width: 100%; height 100%;');
+
+ content.appendChild(energyIFrame);*/
+
+ });
+ }
+
+ function busRouteTemplate(properties) {
+ return getTemplateWrapper(properties, function(content) {
+
+ /*
+ * note
+ */
+
+ });
+ }
+
+ // Templating Utility Functions
+
+ function capitaliseFirstLetter(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1);
+ }
+
+ function getEnergyGraph(properties) {
+
+ return html;
+ }
+
+ function getTemplateWrapper(properties, contentFunction) {
+ var table = document.createElement('table');
+
+ table.classList.add("ls-content-table");
+
+ table.setAttribute('style', 'width: 100%');
+
+ var headingRow = document.createElement('tr');
+ table.appendChild(headingRow);
+
+ var titleData = document.createElement('td');
+ headingRow.appendChild(titleData);
+
+ var title = document.createElement('h2');
+ title.classList.add("ls-popup-title");
+ titleData.appendChild(title);
+
+ var titleText = "";
+
+ if ('loc_ref' in properties) {
+ titleText += properties.loc_ref + ' ';
+ }
+
+ if ('name' in properties) {
+ titleText += properties.name;
+ }
+
+ title.textContent = titleText;
+
+ var moreInfo = L.DomUtil.create('td', '', headingRow);
+ moreInfo.setAttribute('align', 'right');
+
+ if ('uri' in properties) {
+ createBlankLink(properties.uri, '(More Information)', moreInfo);
+ }
+
+ var contentRow = L.DomUtil.create('tr', '', table);
+
+ var contentData = L.DomUtil.create('td', '', contentRow);
+ contentData.setAttribute('colspan', '2');
+
+ contentFunction(contentData);
+
+ return table;
+ }
+
+ var createTabs = function(tabs, container) {
+
+ var nav = L.DomUtil.create('ul', 'ls-nav ls-nav-tabs', container);
+ var content = L.DomUtil.create('div', 'tab-content', container);
+
+ var tabDivs = {};
+
+ var activeDiv;
+ var activeLi;
+
+ tabs.forEach(function(tab) {
+ var li = L.DomUtil.create('li', '', nav);
+
+ var a = L.DomUtil.create('a', '', li);
+
+ a.setAttribute('href', '#');
+
+ a.textContent = tab.name;
+
+ // Content
+ var div = L.DomUtil.create('div', 'tab-pane', content);
+
+ if ('active' in tab && tab.active) {
+ activeDiv = div;
+ activeLi = li;
+
+ li.classList.add('active');
+ } else {
+ div.style.display = 'none';
+ }
+
+ a.onclick = function() {
+ activeDiv.style.display = 'none';
+ activeLi.classList.remove('active');
+
+ div.style.display = 'block';
+ li.classList.add('active');
+
+ activeDiv = div;
+ activeLi = li;
+
+ return false;
+ };
+
+ tabDivs[tab.id] = div;
+ });
+
+ return tabDivs;
+ };
+
+ var createLink = function(url, target, container) {
+ var link = document.createElement('a');
+
+ link.setAttribute('href', url);
+
+ if (target)
+ link.setAttribute('target', target);
+
+ if (container)
+ container.appendChild(link);
+
+ return link;
+ };
+
+ var createBlankLink = function(url, text, container) {
+ var link = createLink(url, '_blank', container);
+
+ if (text)
+ link.textContent = text;
+
+ return link;
+ };
+
+ function createPropertyTable(keys, values) {
+ var table = document.createElement('table');
+
+ keys.forEach(function(key, i) {
+ var tr = document.createElement('tr');
+
+ var keyTd = document.createElement('td');
+ keyTd.textContent = key;
+ tr.appendChild(keyTd);
+
+ var valueTd = document.createElement('td');
+ valueTd.setAttribute('align', 'right');
+ valueTd.textContent = values[i] || "Unknown";
+ tr.appendChild(valueTd);
+
+ table.appendChild(tr);
+ });
+
+ return table;
+ }
+
+ // General Utility Functions
+
+ function getBusTimes(uri) {
+ var parts = uri.split("/");
+ var id = parts[parts.length - 1].split(".")[0];
+
+ return "http://data.southampton.ac.uk/bus-stop/" + id + ".html?view=iframe";
+ }
+
+ function getJSON(options, callback) {
+ var xhttp = new XMLHttpRequest();
+ xhttp.ontimeout = function () {
+ callback(null);
+ };
+ xhttp.timeout = 2000;
+
+ options.data = options.data || null;
+
+ xhttp.open('GET', options.url, true);
+ xhttp.setRequestHeader('Accept', 'application/json');
+
+ xhttp.send(options.data);
+ xhttp.onreadystatechange = function() {
+ if (xhttp.status == 200 && xhttp.readyState == 4) {
+ callback(JSON.parse(xhttp.responseText));
+ }
+ };
+ }
+
+ function smallScreen() {
+ return window.innerWidth < 500;
+ }
+
+ // Custom Hash Support
+
+ if ("Hash" in L) {
+ LS.Hash = L.Class.extend(L.extend({}, L.Hash.prototype, {
+ initialize: function (map, showLevel) {
+ this.showLevel = showLevel;
+ L.Hash.call(this, map);
+
+ var hash = this;
+
+ map.on("levelchange", function() {
+ hash.onMapMove();
+ });
+ },
+ parseHash: function(hash) {
+ var startOfSecondPart = hash.indexOf("/");
+ var firstPart = hash.slice(0, startOfSecondPart);
+
+ if (firstPart.indexOf('#') === 0) {
+ firstPart = firstPart.substr(1);
+ }
+
+ var newLevel = parseInt(firstPart, 10);
+ if (!isNaN(newLevel) && newLevel !== this.map.getLevel()) {
+ this.map.setLevel(newLevel);
+ }
+
+ var secondPart = hash.slice(startOfSecondPart + 1);
+
+ return L.Hash.prototype.parseHash.call(this, secondPart);
+ },
+ formatHash: function(map) {
+ var hash = L.Hash.prototype.formatHash.call(this, map);
+
+ var levelString = map.getLevel() + '';
+
+ hash = "#" + levelString + '/' + hash.slice(1);
+
+ return hash;
+ }
+ }));
+ }
+
+ var WorkstationIcon = L.DivIcon.extend({
+ initialize: function(workstations, workstationState) {
+ var html = '<div style="padding-left: 26px;">';
+
+ var freeSeats = 0;
+
+ var allClosed = true;
+
+ var openIcon = {
+ iconUrl: LS.imagePath + "workstation-group.png",
+ iconSize: [66, 32],
+ iconAnchor: [33, 16],
+ className: 'ls-workstationicon'
+ }
+
+ var closedIcon = {
+ iconUrl: LS.imagePath + "workstation-closed.png",
+ iconSize: [32, 32],
+ iconAnchor: [16, 16],
+ className: 'ls-workstationicon'
+ }
+
+ workstations.forEach(function(workstation) {
+ if (workstation in workstationState) {
+ var state = workstationState[workstation];
+
+ var closed = (state.status.indexOf("closed") !== -1)
+ allClosed = allClosed && closed;
+
+ freeSeats += workstationState[workstation].free_seats;
+ }
+ });
+
+ this._closed = allClosed;
+
+ var iconUrl;
+
+ if (!this._closed) {
+ html += freeSeats + "</div>";
+
+ openIcon.html = html;
+
+ L.setOptions(this, openIcon);
+ } else {
+ html += "</div>";
+
+ L.setOptions(this, closedIcon);
+ }
+ },
+ createIcon: function (oldIcon) {
+ var div = L.DivIcon.prototype.createIcon.call(this, oldIcon);
+
+ div.style.backgroundImage = "url(" + this.options.iconUrl + ")";
+
+ return div;
+ }
+ });
+
+ if ("MarkerClusterGroup" in L) {
+ LS.WorkstationLayer = L.MarkerClusterGroup.extend({
+ initialize: function() {
+ var workstations = {};
+
+ LS.data.workstations.features.forEach(function(feature) {
+ workstations[feature.properties.uri] = feature.properties;
+ });
+
+ var workstationLayer = this;
+
+ var workstationsTemplate = function(workstationURIs) {
+ var div = document.createElement('div');
+
+ var headerText = "Workstation";
+ if (workstationURIs.length !== 1) {
+ headerText = "Workstations";
+ }
+
+ var header = document.createElement('h2');
+ header.textContent = headerText;
+ div.appendChild(header);
+
+ workstationURIs.forEach(function(uri) {
+ var workstation = workstations[uri];
+ var state = workstationData[uri];
+
+ var link = createLink("#", null, div);
+ link.textContent = workstation.label;
+
+ link.onclick = function() {
+ workstationLayer._map.showByURI(uri);
+ };
+
+ if (typeof state !== 'undefined') {
+ var text = document.createTextNode(" " + state.free_seats + " free seats (" + state.total_seats + " total seats)");
+ div.appendChild(text);
+ }
+
+ var br = document.createElement("br");
+ div.appendChild(br);
+ });
+
+ return div;
+ };
+
+ var workstationData = {};
+
+ L.MarkerClusterGroup.prototype.initialize.call(this, {
+ spiderfyOnMaxZoom: false,
+ showCoverageOnHover: false,
+ zoomToBoundsOnClick: false,
+ iconCreateFunction: function(cluster) {
+ var uris = cluster.getAllChildMarkers().map(function(marker) {
+ return marker.uri;
+ });
+
+ return new WorkstationIcon(uris, workstationData);
+ }
+ });
+
+ LS.getWorkstationData(function(data) {
+ workstationData = data;
+
+ LS.data.workstations.features.forEach(function(workstation) {
+ var icon = new WorkstationIcon([workstation.properties.uri], workstationData);
+
+ var marker = new L.Marker(L.GeoJSON.coordsToLatLng(workstation.geometry.coordinates), { icon: icon });
+
+ marker.uri = workstation.properties.uri;
+
+ workstationLayer.addLayer(marker);
+ });
+
+ workstationLayer.on('click', function (a) {
+ var uri = a.layer.uri;
+
+ var popupOptions = {offset: [0, -15]};
+ var content = workstationsTemplate([uri]);
+
+ showPopup(this._map, content, a.latlng, popupOptions);
+ }).on('clusterclick', function (a) {
+ var uris = a.layer.getAllChildMarkers().map(function(marker) {
+ return marker.uri;
+ });
+
+ var popupOptions = {offset: [0, -15]};
+
+ var content = workstationsTemplate(uris);
+
+ showPopup(this._map, content, a.latlng, popupOptions);
+ });
+ });
+
+ return this;
+ }
+ });
+
+ LS.workstationLayer = function () {
+ return new LS.WorkstationLayer();
+ };
+ }
+})();
+
+L.Control.Level = L.Control.extend({
+ includes: L.Mixin.Events,
+
+ options: {
+ position: 'bottomright',
+ parseLevel: function(level) {
+ return parseInt(level, 10);
+ }
+ },
+
+ initialize: function(options) {
+ L.setOptions(this, options);
+
+ this._map = null;
+ this._buttons = {};
+ this._listeners = [];
+ this._level = options.level;
+
+ this.addEventListener("levelchange", this._levelChange, this);
+ },
+ onAdd: function(map) {
+ var div = L.DomUtil.create('div', 'ls-levelselector');
+
+ var btnGroup = L.DomUtil.create('div', 'ls-btn-group', div);
+
+ var buttons = this._buttons;
+ var activeLevel = this._level;
+ var self = this;
+
+ this.options.levels.forEach(function(level) {
+ var cls = 'ls-btn';
+
+ var levelNum = self.options.parseLevel(level);
+
+ if (level === activeLevel || levelNum === activeLevel)
+ cls += ' active';
+
+ var levelBtn = L.DomUtil.create('button', cls, btnGroup);
+
+ levelBtn.appendChild(levelBtn.ownerDocument.createTextNode(level));
+
+ levelBtn.onclick = function() {
+ self.setLevel(level);
+ };
+
+ buttons[level] = levelBtn;
+ });
+
+ return div;
+ },
+ _levelChange: function(e) {
+ // Probably won't work in some browsers, see
+ // https://developer.mozilla.org/en-US/docs/Web/API/element.classList
+
+ if (this._map !== null) {
+ this._buttons[e.oldLevel].classList.remove('active');
+ this._buttons[e.newLevel].classList.add('active');
+ }
+ },
+ setLevel: function(level) {
+
+ if (level === this._level)
+ return;
+
+ var oldLevel = this._level;
+ this._level = level;
+
+ this.fireEvent("levelchange", {
+ oldLevel: oldLevel,
+ newLevel: level
+ });
+ },
+ getLevel: function() {
+ return this._level;
+ }
+});
+
+L.Control.level = function (options) {
+ return new L.Control.Level(options);
+};
+
+/**
+ * A layer that will display indoor data
+ *
+ * addData takes a GeoJSON feature collection, each feature must have a level
+ * property that indicates the level. If the level is a string, some function
+ * will be used to rank the levels.
+ *
+ * getLevels can be called to get the array of levels that are present.
+ *
+ *
+ */
+
+L.IndoorLayer = L.Class.extend({
+
+ initialize: function(data, options) {
+ L.setOptions(this, options);
+
+ var onEachFeature = options.onEachFeature;
+ var layers = this._layers = {};
+ this._map = null;
+ if ("level" in options) {
+ this._level = options.level;
+ } else {
+ this._level = null;
+ }
+
+ this.options.onEachFeature = function(feature, layer) {
+
+ onEachFeature(feature, layer);
+
+ var marker = options.markerForFeature(feature);
+ if (typeof(marker) !== 'undefined') {
+ marker.on('click', function(e) {
+ layer.fire('click', e);
+ });
+
+ layers[feature.properties.level].addLayer(marker);
+ }
+ };
+
+ this.addData(data);
+ },
+ addTo: function (map) {
+ map.addLayer(this);
+ return this;
+ },
+ onAdd: function (map) {
+ this._map = map;
+
+ if (this._level === null) {
+ var levels = this.getLevels();
+
+ if (levels.length !== 0) {
+ this._level = levels[0];
+ }
+ }
+
+ this._map.addLayer(this._layers[this._level]);
+ },
+ onRemove: function (map) {
+ this._map.removeLayer(this._layers[this._level]);
+ this._map = null;
+ },
+ addData: function(data) {
+ var layers = this._layers;
+
+ var options = this.options;
+
+ var features = L.Util.isArray(data) ? data : data.features;
+
+ features.forEach(function (part) {
+ var level = part.properties.level;
+ var layer;
+
+ if (typeof level === 'undefined')
+ return;
+
+ if (!("geometry" in part)) {
+ return;
+ }
+
+ if (level in layers) {
+ layer = layers[level];
+ } else {
+ layer = layers[level] = L.geoJson({ type: "FeatureCollection", features: [] }, options);
+ }
+
+ layer.addData(part);
+ });
+ },
+ getLevels: function() {
+ return Object.keys(this._layers);
+ },
+ getLevel: function() {
+ return this._level;
+ },
+ setLevel: function(level) {
+ if (typeof(level) === 'object') {
+ level = level.newLevel;
+ }
+
+ if (this._level === level)
+ return;
+
+ var oldLayer = this._layers[this._level];
+ var layer = this._layers[level];
+
+ if (this._map !== null) {
+ this._map.removeLayer(oldLayer);
+ this._map.addLayer(layer);
+ }
+
+ this._level = level;
+ }
+});
+
+L.indoorLayer = function(data, options) {
+ return new L.IndoorLayer(data, options);
+};
+
+L.SelectiveVisibilityLayer = L.Class.extend({
+ _layers: {},
+
+ initialize: function(options) {
+ L.setOptions(this, options);
+ },
+ onAdd: function (map) {
+ this._map = map;
+
+ if (this.options.level === null) {
+ var levels = this.getLevels();
+
+ if (levels.length !== 0) {
+ this.options.level = levels[0];
+ }
+ }
+
+ this._map.addLayer(this._layers[this.options.level]);
+ },
+ onRemove: function (map) {
+ this._map = null;
+ },
+ addLayer: function(level, options) {
+ var layers = this._layers;
+
+ options = this.options;
+
+ data.features.forEach(function (part) {
+ var level = part.properties.level;
+ var layer;
+
+ if (typeof level === 'undefined')
+ return;
+
+ if (level in layers) {
+ layer = layers[level];
+ } else {
+ layer = layers[level] = L.geoJson({ type: "FeatureCollection", features: [] }, options);
+ }
+
+ layer.addData(part);
+ });
+ }
+});
+
+L.SelectiveVisibilityLayer = function(data, options) {
+ return new L.IndoorLayer(data, options);
+};
+// forEach compatability
+if (!Array.prototype.forEach) {
+ Array.prototype.forEach = function (fn, scope) {
+ 'use strict';
+ var i, len;
+ for (i = 0, len = this.length; i < len; ++i) {
+ if (i in this) {
+ fn.call(scope, this[i], i, this);
+ }
+ }
+ };
+}
+
+// map function compatability
+if (!Array.prototype.map) {
+ Array.prototype.map = function(callback, thisArg) {
+
+ var T, A, k;
+
+ if (this === null) {
+ throw new TypeError(" this is null or not defined");
+ }
+
+ // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
+ var O = Object(this);
+
+ // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
+ // 3. Let len be ToUint32(lenValue).
+ var len = O.length >>> 0;
+
+ // 4. If IsCallable(callback) is false, throw a TypeError exception.
+ // See: http://es5.github.com/#x9.11
+ if (typeof callback !== "function") {
+ throw new TypeError(callback + " is not a function");
+ }
+
+ // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ if (thisArg) {
+ T = thisArg;
+ }
+
+ // 6. Let A be a new array created as if by the expression new Array(len) where Array is
+ // the standard built-in constructor with that name and len is the value of len.
+ A = new Array(len);
+
+ // 7. Let k be 0
+ k = 0;
+
+ // 8. Repeat, while k < len
+ while(k < len) {
+
+ var kValue, mappedValue;
+
+ // a. Let Pk be ToString(k).
+ // This is implicit for LHS operands of the in operator
+ // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
+ // This step can be combined with c
+ // c. If kPresent is true, then
+ if (k in O) {
+
+ // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
+ kValue = O[ k ];
+
+ // ii. Let mappedValue be the result of calling the Call internal method of callback
+ // with T as the this value and argument list containing kValue, k, and O.
+ mappedValue = callback.call(T, kValue, k, O);
+
+ // iii. Call the DefineOwnProperty internal method of A with arguments
+ // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
+ // and false.
+
+ // In browsers that support Object.defineProperty, use the following:
+ // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
+
+ // For best browser support, use the following:
+ A[ k ] = mappedValue;
+ }
+ // d. Increase k by 1.
+ k++;
+ }
+
+ // 9. return A
+ return A;
+ };
+}