diff options
author | Christopher Baines <cb15g11@soton.ac.uk> | 2014-02-19 22:32:59 +0000 |
---|---|---|
committer | Christopher Baines <cb15g11@soton.ac.uk> | 2014-02-19 22:32:59 +0000 |
commit | e4d1c918444c15e2e1480ecb332e61ec8feb7629 (patch) | |
tree | d1ca58de732b894ab015a8b5df4581d6ec3be495 /create-data.js | |
download | leaflet-soton-e4d1c918444c15e2e1480ecb332e61ec8feb7629.tar leaflet-soton-e4d1c918444c15e2e1480ecb332e61ec8feb7629.tar.gz |
Initial commit
Diffstat (limited to 'create-data.js')
-rwxr-xr-x | create-data.js | 1419 |
1 files changed, 1419 insertions, 0 deletions
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); +} |