- Floor is now a circle

- Added option to disable Overpass update on grid move
- Added aeroway tags to Overpass
- Added ability to load Overpass objects that intersect another way (like an aerodrome boundary)
- Stopped initial Overpass requests from blocking main loading
0.1
Robin Hawkes 2014-02-28 00:11:57 +00:00
rodzic a5875435d9
commit af771e4153
4 zmienionych plików z 157 dodań i 39 usunięć

Wyświetl plik

@ -62,7 +62,11 @@
}
_.defaults(options, {
coords: [-0.01924, 51.50358]
coords: [-0.01924, 51.50358],
capZoom: true,
capOrbit: true,
overpassGridUpdate: true,
overpassWayIntersect: false
});
// Output city options
@ -109,7 +113,9 @@
self.publish("loadingProgress", 0.5);
// Set up data loader
self.data = new VIZI.DataOverpass();
self.data = new VIZI.DataOverpass({
gridUpdate: options.overpassGridUpdate
});
// TODO: Work out a way to use progress event of each promises to increment loading progress
// Perhaps by looping through each promises individually and working out progress fraction by num. of promises / amount processed
@ -121,7 +127,7 @@
promises.push(self.loadCoreObjects());
// Load data from the OSM Overpass API
promises.push(self.loadOverpass());
promises.push(self.loadOverpass(options.overpassWayIntersect));
return Q.allSettled(promises);
}).then(function (results) {
@ -250,18 +256,25 @@
return Q.fcall(function() {});
};
VIZI.City.prototype.loadOverpass = function() {
VIZI.City.prototype.loadOverpass = function(wayIntersect) {
VIZI.Log("Loading data from OSM Overpass API");
var startTime = Date.now();
var deferred = Q.defer();
// var deferred = Q.defer();
this.data.update().done(function() {
VIZI.Log("Finished loading Overpass data in " + (Date.now() - startTime) + "ms");
deferred.resolve();
});
if (wayIntersect) {
this.data.updateByWayIntersect(wayIntersect).done(function() {
VIZI.Log("Finished loading Overpass data using way intersection in " + (Date.now() - startTime) + "ms");
});
} else {
this.data.update().done(function() {
VIZI.Log("Finished loading Overpass data in " + (Date.now() - startTime) + "ms");
// deferred.resolve();
});
}
return deferred.promise;
// return deferred.promise;
return Q.fcall(function() {});
};
}());

Wyświetl plik

@ -72,7 +72,7 @@
return;
}
var features = self.process(data);
var features = self.process(data, true);
// Add data to cache (including dupes on boundaries)
self.cache.add(cacheKey, features);
@ -82,6 +82,12 @@
// Skip duplicate features
_.each(features, function(feature) {
// Skip if feature is undefined
if (!feature) {
VIZI.Log("Skipping undefined feature");
return;
}
if (self.processedIds[feature.id]) {
VIZI.Log("Skipping duplicated feature:", feature.id);
return;

Wyświetl plik

@ -2,11 +2,15 @@
(function() {
"use strict";
VIZI.DataOverpass = function() {
VIZI.DataOverpass = function(options) {
VIZI.Log("Inititialising Overpass API manager");
VIZI.Data.call(this);
_.defaults(options, {
gridUpdate: true
});
// TODO: It's entirely possible that these queries are picking up duplicate ways. Need to look into it.
// TODO: Ways that cross over tile boundaries will likely get picked up by a query for each tile. Look into that.
// OSM Buildings handles this by not rendering items with an id that it already knows about
@ -21,7 +25,7 @@
// "rel({s},{w},{n},{e})[landuse~%22grass|meadow|forest%22];" +
// ");(._;way(r););(._;node(w););(" +
"way({s},{w},{n},{e})[%22building%22];" +
"way({s},{w},{n},{e})[aerodrome=%22airport%22];" +
"way({s},{w},{n},{e})[aeroway~%22aerodrome|runway%22];" +
"way({s},{w},{n},{e})[waterway~%22riverbank|dock%22];" +
"way({s},{w},{n},{e})[waterway=%22canal%22][area=%22yes%22];" +
"way({s},{w},{n},{e})[natural~%22water|scrub%22];" +
@ -51,7 +55,9 @@
this.urlHigh = this.urlBase + this.queryHigh;
this.urlLow = this.urlBase + this.queryLow;
this.subscribe("gridUpdated", this.update);
if (options.gridUpdate) {
this.subscribe("gridUpdated", this.update);
}
};
VIZI.DataOverpass.prototype = Object.create( VIZI.Data.prototype );
@ -124,9 +130,97 @@
return deferred.promise;
};
VIZI.DataOverpass.prototype.updateByWayIntersect = function(wayId) {
var self = this;
var deferred = Q.defer();
var url = this.urlBase + "[out:json];(way(" + wayId + "));(._;node(w););out;";
// Get way to intersect with
VIZI.Log("Requesting URL", url);
d3.json(url, function(error, data) {
VIZI.Log("Response for URL", url);
if (error) {
deferred.reject(new Error(error));
} else {
// No features
if (data.elements.length === 0) {
deferred.resolve();
return;
}
var way = self.process(data, false, false)[0];
VIZI.Log(way);
var coordinates = way.coordinates;
var tiles = {};
// Find tiles that intersect way
_.each(coordinates, function(coordinate) {
var tile = self.grid.lonlat2tile(coordinate[0], coordinate[1], self.geo.tileZoom);
if (!tiles[tile[1]]) {
tiles[tile[1]] = [];
}
if (_.indexOf(tiles[tile[1]], tile[0]) === -1) {
tiles[tile[1]].push(tile[0]);
}
});
VIZI.Log(tiles);
var promiseQueue = [];
_.each(tiles, function(tilesX, tileY) {
// Fill in gaps
var minX = _.min(tilesX);
var maxX = _.max(tilesX);
var tilesXFilled = _.range(minX, maxX + 1);
_.each(tilesXFilled, function(tileX) {
var tileBounds = {
n: Number(tileY),
e: tileX + 1,
s: Number(tileY) + 1,
w: tileX
};
var tileBoundsLonLat = self.grid.getBoundsLonLat(tileBounds);
// VIZI.Log(tileBoundsLonLat);
var cacheKey = tileX + ":" + tileY;
// TODO: Handle load promise without actually running the function
// - At the moment, the load function is run in at this point
promiseQueue.push([self.load, [self.urlHigh, tileBoundsLonLat, cacheKey]]);
});
});
// Use throat to limit simultaneous Overpass requests
// Without limitation the Overpass API will rate-limit
Q.all(promiseQueue.map(throat(1, function(promiseFunc) {
return promiseFunc[0].apply(self, promiseFunc[1]);
}))).done(function() {
// deferred.resolve();
}, function(error) {
// deferred.reject(error);
});
deferred.resolve();
}
});
return deferred.promise;
};
// Process data (convert from Overpass format to a common ViziCities format)
// http://wiki.openstreetmap.org/wiki/Talk:Overpass_API/Language_Guide#JSON_Syntax
VIZI.DataOverpass.prototype.process = function(data) {
VIZI.DataOverpass.prototype.process = function(data, simple, project) {
var self = this;
var nodes = {};
@ -137,10 +231,10 @@
// Find a way to do this without passing the node and way objects
switch (element.type) {
case "node":
self.processNode(nodes, element);
nodes[element.id] = self.processNode(nodes, element, project);
break;
case "way":
self.processWay(nodes, ways, element);
ways.push(self.processWay(nodes, element, simple));
break;
}
});
@ -148,13 +242,13 @@
return ways;
};
VIZI.DataOverpass.prototype.processNode = function(nodes, element) {
nodes[element.id] = this.geo.projection([element.lon, element.lat]);
VIZI.DataOverpass.prototype.processNode = function(nodes, element, project) {
return (project === false) ? [element.lon, element.lat] : this.geo.projection([element.lon, element.lat]);
};
// TODO: Validate polygon to make sure it's renderable (eg. complete, and no cross-overs)
// TODO: Use simplify.js to reduce complexity of polygons
VIZI.DataOverpass.prototype.processWay = function(nodes, ways, element) {
VIZI.DataOverpass.prototype.processWay = function(nodes, element, simple) {
var self = this;
var points = element.nodes;
@ -183,20 +277,22 @@
return;
}
// Simplify coordinates
// TODO: Perform this in the worker thread
var simplifyTolerance = 1; // Three.js units
var simplifiedCoords = simplify(coordinates, simplifyTolerance);
if (simple) {
// Simplify coordinates
// TODO: Perform this in the worker thread
var simplifyTolerance = 1; // Three.js units
coordinates = simplify(coordinates, simplifyTolerance);
// VIZI.Log("Original coord count:", coordinates.length);
// VIZI.Log("Simplified coord count:", simplifiedCoords.length);
// VIZI.Log("Original coord count:", coordinates.length);
// VIZI.Log("Simplified coord count:", simplifiedCoords.length);
// VIZI.Log("Original coord example:", coordinates[0], coordinates[1]);
// VIZI.Log("Simplified coord example:", simplifiedCoords[0], simplifiedCoords[1]);
// VIZI.Log("Original coord example:", coordinates[0], coordinates[1]);
// VIZI.Log("Simplified coord example:", simplifiedCoords[0], simplifiedCoords[1]);
// Not enough points to make an object
if (simplifiedCoords.length < 4) {
return;
// Not enough points to make an object
if (coordinates.length < 4) {
return;
}
}
tags.height = this.processHeight(tags);
@ -207,11 +303,11 @@
// More info: http://gis.stackexchange.com/a/8496/14967
// tags.area;
ways.push({
return {
id: element.id,
coordinates: simplifiedCoords,
coordinates: coordinates,
properties: tags
});
};
};
VIZI.DataOverpass.prototype.processHeight = function(tags) {
@ -230,12 +326,12 @@
} else if (tags["building"]) {
height = 10 + Math.random() * 10;
} else if (tags["landuse"] === "forest") {
height = 6;
height = 7;
// } else if (tags["waterway"] || tags["natural"] && /water|scrub/.test(tags["natural"]) || tags["leisure"] && /park|pitch/.test(tags["leisure"]) || tags["landuse"] && /grass|meadow|commercial|retail|industrial|brownfield/.test(tags["landuse"])) {
} else if (tags["waterway"] || tags["natural"] === "water") {
height = 4;
} else if (tags["natural"] === "scrub" || tags["leisure"] && /park|pitch/.test(tags["leisure"]) || tags["landuse"] && /grass|meadow/.test(tags["landuse"]) || tags["aeroway"] === "runway") {
height = 3;
} else if (tags["natural"] === "scrub" || tags["leisure"] && /park|pitch/.test(tags["leisure"]) || tags["landuse"] && /grass|meadow/.test(tags["landuse"])) {
height = 2;
} else {
height = 1;
}
@ -259,8 +355,10 @@
colour = 0xd8c7b5;
} else if (tags["landuse"] && /commercial|retail/.test(tags["landuse"])) {
colour = 0xa9bbd6;
} else if (tags["aerodrome"] === "airport") {
} else if (tags["aeroway"] === "aerodrome") {
colour = 0xffffff;
} else if (tags["aeroway"] === "runway") {
colour = 0x666666;
} else {
VIZI.Log("Setting default colour for feaure", tags);
colour = 0xFF0000;

Wyświetl plik

@ -23,7 +23,8 @@
// floorContainer.add(floorWire);
var floorGeom = new THREE.PlaneGeometry(40000, 40000, 4, 4);
// var floorGeom = new THREE.PlaneGeometry(40000, 40000, 4, 4);
var floorGeom = new THREE.CircleGeometry(20000, 32);
var floorMat = new THREE.MeshBasicMaterial({color: 0xdddddd});
var floor = new THREE.Mesh(floorGeom, floorMat);
floor.position.y = -0.4;