diff options
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 Binary files differnew file mode 100644 index 0000000..6862980 --- /dev/null +++ b/resources/images/candy.png diff --git a/resources/images/coffee.png b/resources/images/coffee.png Binary files differnew file mode 100644 index 0000000..47da029 --- /dev/null +++ b/resources/images/coffee.png diff --git a/resources/images/loader.gif b/resources/images/loader.gif Binary files differnew file mode 100644 index 0000000..d3ef195 --- /dev/null +++ b/resources/images/loader.gif diff --git a/resources/images/locate.png b/resources/images/locate.png Binary files differnew file mode 100644 index 0000000..14faeb7 --- /dev/null +++ b/resources/images/locate.png 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 Binary files differnew file mode 100644 index 0000000..acebf30 --- /dev/null +++ b/resources/images/locate_alt.png 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 Binary files differnew file mode 100644 index 0000000..9669ff4 --- /dev/null +++ b/resources/images/locate_touch.png diff --git a/resources/images/locate_touch_alt.png b/resources/images/locate_touch_alt.png Binary files differnew file mode 100644 index 0000000..a3b5196 --- /dev/null +++ b/resources/images/locate_touch_alt.png diff --git a/resources/images/logos/stags-head.png b/resources/images/logos/stags-head.png Binary files differnew file mode 100755 index 0000000..9b65634 --- /dev/null +++ b/resources/images/logos/stags-head.png diff --git a/resources/images/logos/stags-head2.png b/resources/images/logos/stags-head2.png Binary files differnew file mode 100755 index 0000000..e61552b --- /dev/null +++ b/resources/images/logos/stags-head2.png diff --git a/resources/images/logos/susu-cafe.png b/resources/images/logos/susu-cafe.png Binary files differnew file mode 100755 index 0000000..49a7e86 --- /dev/null +++ b/resources/images/logos/susu-cafe.png diff --git a/resources/images/logos/susu-cafe2.png b/resources/images/logos/susu-cafe2.png Binary files differnew file mode 100755 index 0000000..1af5a40 --- /dev/null +++ b/resources/images/logos/susu-cafe2.png diff --git a/resources/images/logos/susu-shop.png b/resources/images/logos/susu-shop.png Binary files differnew file mode 100755 index 0000000..f6820a4 --- /dev/null +++ b/resources/images/logos/susu-shop.png diff --git a/resources/images/logos/susu-shop2.png b/resources/images/logos/susu-shop2.png Binary files differnew file mode 100755 index 0000000..6bbfb90 --- /dev/null +++ b/resources/images/logos/susu-shop2.png diff --git a/resources/images/logos/susu.png b/resources/images/logos/susu.png Binary files differnew file mode 100755 index 0000000..44aa056 --- /dev/null +++ b/resources/images/logos/susu.png diff --git a/resources/images/logos/susu2.png b/resources/images/logos/susu2.png Binary files differnew file mode 100755 index 0000000..d9942f2 --- /dev/null +++ b/resources/images/logos/susu2.png diff --git a/resources/images/logos/the-bridge.png b/resources/images/logos/the-bridge.png Binary files differnew file mode 100755 index 0000000..e803595 --- /dev/null +++ b/resources/images/logos/the-bridge.png diff --git a/resources/images/logos/the-bridge2.png b/resources/images/logos/the-bridge2.png Binary files differnew file mode 100755 index 0000000..b5ad5c8 --- /dev/null +++ b/resources/images/logos/the-bridge2.png diff --git a/resources/images/printer.png b/resources/images/printer.png Binary files differnew file mode 100644 index 0000000..1a5ee39 --- /dev/null +++ b/resources/images/printer.png diff --git a/resources/images/search-icon-mobile.png b/resources/images/search-icon-mobile.png Binary files differnew file mode 100644 index 0000000..920fa27 --- /dev/null +++ b/resources/images/search-icon-mobile.png diff --git a/resources/images/search-icon.png b/resources/images/search-icon.png Binary files differnew file mode 100644 index 0000000..b51c165 --- /dev/null +++ b/resources/images/search-icon.png diff --git a/resources/images/spinner.gif b/resources/images/spinner.gif Binary files differnew file mode 100644 index 0000000..aa70283 --- /dev/null +++ b/resources/images/spinner.gif diff --git a/resources/images/toilets-f.png b/resources/images/toilets-f.png Binary files differnew file mode 100644 index 0000000..4117ab4 --- /dev/null +++ b/resources/images/toilets-f.png diff --git a/resources/images/toilets-m.png b/resources/images/toilets-m.png Binary files differnew file mode 100644 index 0000000..93cdbfa --- /dev/null +++ b/resources/images/toilets-m.png diff --git a/resources/images/toilets.png b/resources/images/toilets.png Binary files differnew file mode 100644 index 0000000..17ccfd8 --- /dev/null +++ b/resources/images/toilets.png diff --git a/resources/images/toilets_disability.png b/resources/images/toilets_disability.png Binary files differnew file mode 100644 index 0000000..240539c --- /dev/null +++ b/resources/images/toilets_disability.png diff --git a/resources/images/workstation-big.png b/resources/images/workstation-big.png Binary files differnew file mode 100644 index 0000000..090d182 --- /dev/null +++ b/resources/images/workstation-big.png diff --git a/resources/images/workstation-closed.png b/resources/images/workstation-closed.png Binary files differnew file mode 100644 index 0000000..d8ff7b7 --- /dev/null +++ b/resources/images/workstation-closed.png diff --git a/resources/images/workstation-group.png b/resources/images/workstation-group.png Binary files differnew file mode 100644 index 0000000..d334886 --- /dev/null +++ b/resources/images/workstation-group.png diff --git a/resources/images/workstation.png b/resources/images/workstation.png Binary files differnew file mode 100644 index 0000000..a60de27 --- /dev/null +++ b/resources/images/workstation.png 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 © <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; + }; +} |