Skip to content

Commit

Permalink
better organization for the OSMBuildingLayer
Browse files Browse the repository at this point in the history
  • Loading branch information
kilsedar committed Jul 16, 2017
1 parent 269af23 commit 1bf9a93
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 47 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -26,3 +26,7 @@ sudo npm install
sudo npm install -g karma-cli

karma start

### To generate the documentation run the following command:

npm install -g jsdoc
25 changes: 12 additions & 13 deletions example/index.js
Expand Up @@ -24,6 +24,16 @@ define(['libraries/WebWorldWind/src/WorldWind', 'src/OSMBuildingLayer'],
worldWindow.addLayer(layers[l].layer);
}

// var source = {type: "boundingBox", coordinates: [-74.0232, 40.6998, -73.97, 40.74]}; // New York (big)
var source = {type: "boundingBox", coordinates: [-74.03, 40.70, -74.0, 40.72]}; // New York (small)
// var source = {type: "boundingBox", coordinates: [9.04284, 45.3871, 9.27791, 45.536]}; // Milan (PRIN & big)
// var source = {type: "boundingBox", coordinates: [9.05, 45.45, 9.10, 45.50]}; // Milan (medium)
// var source = {type: "boundingBox", coordinates: [9.45, 45.48, 9.50, 45.50]}; // Milan (small)
// var source = {type: "boundingBox", coordinates: [9.1705, 45.4557, 9.2021, 45.4735]}; // Milan (center)
// var source = {type: "boundingBox", coordinates: [9.2, 45.48, 9.21, 45.49]}; // Milan (buggy region - nodes)
// var source = {type: "boundingBox", coordinates: [9.48, 45.18, 9.53, 45.19]}; // region tested in GRASS
// var source = {type: "GeoJSONFile", path: "data/test.geojson"};

var configuration = {
// interiorColor: new WorldWind.Color(0.67, 0.25, 0.020, 1.0),
interiorColor: new WorldWind.Color(0.02, 0.2, 0.7, 1.0),
Expand All @@ -34,17 +44,6 @@ define(['libraries/WebWorldWind/src/WorldWind', 'src/OSMBuildingLayer'],
altitudeMode: WorldWind.RELATIVE_TO_GROUND
};

// var osmNewYork = new OSMBuildingLayer(worldWindow, configuration);
// osmNewYork.addByBoundingBox([-74.0232, 40.6998, -73.97, 40.74]);
// osmNewYork.addByBoundingBox([-74.03, 40.70, -74.0, 40.72]);
// var osmMilan = new OSMBuildingLayer(worldWindow, configuration);
// osmMilan.addByBoundingBox([9.45, 45.48, 9.50, 45.50]);
// osmMilan.addByBoundingBox([9.05, 45.45, 9.10, 45.50]);
// osmMilan.addByBoundingBox([9.04284, 45.3871, 9.27791, 45.536]);
// osmMilan.addByBoundingBox([9.2, 45.48, 9.21, 45.49]); // buggy region (nodes)
// osmMilan.addByBoundingBox([9.1705, 45.4557, 9.2021, 45.4735]); // center
// osmMilan.addByBoundingBox([9.48, 45.18, 9.53, 45.19]); // region tested in GRASS
// osmMilan.zoom();
var test = new OSMBuildingLayer(worldWindow, configuration);
test.addByGeoJSONFile("data/test.geojson");
var test = new OSMBuildingLayer(worldWindow, source, configuration);
test.add();
});
3 changes: 1 addition & 2 deletions src/GeoJSONParserTriangulation.js
Expand Up @@ -4,9 +4,8 @@
define(['libraries/WebWorldWind/src/formats/geojson/GeoJSONParser',
'libraries/WebWorldWind/src/geom/Position',
'libraries/WebWorldWind/src/shapes/TriangleMesh',
'src/shapes/BuildingShape',
'earcut'],
function (GeoJSONParser, Position, TriangleMesh, BuildingShape, earcut) {
function (GeoJSONParser, Position, TriangleMesh, earcut) {
"use strict";

/**
Expand Down
141 changes: 109 additions & 32 deletions src/OSMBuildingLayer.js
Expand Up @@ -2,11 +2,13 @@
* @exports OSMBuildingLayer
*/
define(['libraries/WebWorldWind/src/cache/MemoryCache',
'libraries/WebWorldWind/src/error/ArgumentError',
'libraries/WebWorldWind/src/util/Logger',
'src/OSMLayer',
'src/GeoJSONParserTriangulationOSM',
'jquery',
'osmtogeojson'],
function (MemoryCache, OSMLayer, GeoJSONParserTriangulationOSM, $, osmtogeojson) {
function (MemoryCache, ArgumentError, Logger, OSMLayer, GeoJSONParserTriangulationOSM, $, osmtogeojson) {
"use strict";

/**
Expand All @@ -15,19 +17,49 @@ define(['libraries/WebWorldWind/src/cache/MemoryCache',
* @constructor
* @classdesc Fetches OSM buildings, converts them to GeoJSON, and adds them to the WorldWindow.
* @param {WorldWindow} worldWindow The WorldWindow where the OSMBuildingLayer is added to.
* @param {Object} source Defines the data source of the {@link OSMBuildingLayer}.
* @param {Object} configuration Configuration is used to set the attributes of {@link ShapeAttributes}. Four more attributes can be defined, which are "extrude", "heatmap", "altitude" and "altitudeMode".
*/
var OSMBuildingLayer = function (worldWindow, configuration) {
var OSMBuildingLayer = function (worldWindow, source, configuration) {
OSMLayer.call(this, worldWindow, configuration);
this.type = "way";
this.tag = "building";
this._source = source;
this._geometryCache = new MemoryCache(30000, 24000);
this._propertiesCache = new MemoryCache(50000, 40000);
};

OSMBuildingLayer.prototype = Object.create(OSMLayer.prototype);

Object.defineProperties (OSMBuildingLayer.prototype, {
/**
* Defines the data source of the {@link OSMBuildingLayer}. Its "type" can be either "boundingBox" or "GeoJSONFile".
* If the "type" is "boundingBox", "coordinates" must be defined. The order of the "coordinates" is "x1, y1, x2, y2".
* If the "type" is "GeoJSONFile", "path" where the file resides must be defined.
* @memberof OSMBuildingLayer.prototype
* @type {Object}
* @throws {ArgumentError} If the source definition is wrong.
*/
source: {
get: function() {
return this._source;
},
set: function(source) {
if (source.type == "boundingBox" && source.coordinates) {
this._source.type = "boundingBox";
this._source.coordinates = source.coordinates;
}
else if (source.type == "GeoJSONFile" && source.path) {
this._source.type = "GeoJSONFile";
this._source.path = source.path;
}
else {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OSMBuildingLayer", "source", "The source definition of the layer is wrong.")
);
}
}
},
/**
* The cache for the geometry of each feature of the OSMBuildingLayer.
* @memberof OSMBuildingLayer.prototype
Expand Down Expand Up @@ -56,6 +88,10 @@ define(['libraries/WebWorldWind/src/cache/MemoryCache',
}
});

/**
* Caches the features of the {@link OSMBuildingLayer}. The features' geometry is cached in the layer's "geometryCache" member variable, properties are cached in the layer's "propertiesCache" member variable.
* @param {Object} dataOverpassGeoJSON GeoJSON object to be cached.
*/
OSMBuildingLayer.prototype.cache = function(dataOverpassGeoJSON) {
// console.log(JSON.stringify(dataOverpassGeoJSON));
for (var featureIndex = 0; featureIndex < dataOverpassGeoJSON.features.length; featureIndex++) {
Expand Down Expand Up @@ -93,38 +129,60 @@ define(['libraries/WebWorldWind/src/cache/MemoryCache',
};

/**
* Using the boundingBox fetches the OSM building data using Overpass API, converts it to GeoJSON using osmtogeojson API,
* adds the GeoJSON to the WorldWindow using the {@link GeoJSONParserTriangulationOSM}.
* It also sets the boundingBox of the {@link OSMLayer}.
* @param {Float[]} boundingBox It defines the bounding box of the OSM data to add.
* Calls [addByBoundingBox]{@link OSMBuildingLayer#addByBoundingBox} if the "type" of the layer's "source" member variable is "boundingBox" and the "coordinates" of the layer's "source" member variable is defined.
* Calls [addByGeoJSONFile]{@link OSMBuildingLayer#addByGeoJSONFile} if the "type" of the layer's "source" member variable is "GeoJSONFile" and the "path" of the layer's "source" member variable is defined.
* @throws {ArgumentError} If the source definition is wrong.
*/
OSMBuildingLayer.prototype.addByBoundingBox = function (boundingBox) {
OSMBuildingLayer.prototype.add = function () {
if (this.source.type == "boundingBox" && this.source.coordinates)
this.addByBoundingBox();
else if (this.source.type == "GeoJSONFile" && this.source.path)
this.addByGeoJSONFile();
else {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OSMBuildingLayer", "add", "The source definition of the layer is wrong.")
);
}
};

this.boundingBox = boundingBox;
/**
* Makes an AJAX request to fetch the OSM building data using the "coordinates" of the layer's "source" member variable and Overpass API, converts it to GeoJSON using osmtogeojson API,
* adds the GeoJSON to the {@link WorldWindow} using the {@link GeoJSONParserTriangulationOSM}.
* It also sets the "boundingBox" member variable of the layer.
* @throws {ArgumentError} If the "coordinates" of the layer's "source" member variable doesn't have four values.
* @throws {ArgumentError} If the request to OSM fails.
*/
OSMBuildingLayer.prototype.addByBoundingBox = function () {

if (this.source.coordinates.length != 4) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OSMBuildingLayer", "addByBoundingBox", "The bounding box is invalid.")
);
}

this.boundingBox = this.source.coordinates;
var worldWindow = this.worldWindow;
/* var dc = this.worldWindow.drawContext;
console.log("dc.globe --> " + dc.globe); */
var _self = this;

var data = '[out:json][timeout:25];';
data += '(' + this._type + '[' + this._tag + '](' + boundingBox[1] + ',' + boundingBox[0] + ',' + boundingBox[3] + ',' + boundingBox[2] + '); ';
// data += 'relation[' + this._tag + '](' + boundingBox[1] + ',' + boundingBox[0] + ',' + boundingBox[3] + ',' + boundingBox[2] + ');); (._;>;); out body qt;';
data += 'relation[' + this._tag + '](' + boundingBox[1] + ',' + boundingBox[0] + ',' + boundingBox[3] + ',' + boundingBox[2] + ');); out body; >; out skel qt;';
data += '(' + this.type + '[' + this.tag + '](' + this.boundingBox[1] + ',' + this.boundingBox[0] + ',' + this.boundingBox[3] + ',' + this.boundingBox[2] + '); ';
// data += 'relation[' + this.tag + '](' + this.boundingBox[1] + ',' + this.boundingBox[0] + ',' + this.boundingBox[3] + ',' + this.boundingBox[2] + ');); (._;>;); out body qt;';
data += 'relation[' + this.tag + '](' + this.boundingBox[1] + ',' + this.boundingBox[0] + ',' + this.boundingBox[3] + ',' + this.boundingBox[2] + ');); out body; >; out skel qt;';
// console.log("data --> " + data);

$.ajax({
url: 'http://overpass-api.de/api/interpreter',
data: data,
processData: false,
contentType: false,
type: 'POST',
success: function (dataOverpass) {
success: function(dataOverpass) {
// console.log("dataOverpass --> " + JSON.stringify(dataOverpass));
// var dataOverpassGeoJSON = osmtogeojson(dataOverpass, {flatProperties: true, polygonFeatures: {"building": true}});
var dataOverpassGeoJSON = osmtogeojson(dataOverpass);
_self.cache(dataOverpassGeoJSON);
var dataOverpassGeoJSONString = JSON.stringify(dataOverpassGeoJSON);
console.log("dataOverpassGeoJSONString --> " + dataOverpassGeoJSONString);
// console.log("dataOverpassGeoJSONString --> " + dataOverpassGeoJSONString);
// console.log("dataOverpassGeoJSON.features.length (number of polygons) --> " + dataOverpassGeoJSON.features.length);
// console.time("creatingOSMBuildingLayer");
var OSMBuildingLayer = new WorldWind.RenderableLayer("OSMBuildingLayer");
Expand All @@ -135,17 +193,20 @@ define(['libraries/WebWorldWind/src/cache/MemoryCache',
worldWindow.addLayer(OSMBuildingLayer);
_self.zoom();
},
error: function (e) {
console.log("Error: " + JSON.stringify(e));
error: function(e) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OSMBuildingLayer", "addByBoundingBox", "Request failed. Error: " + JSON.stringify(e))
);
}
});
};

/**
*
* @param
* Calculates the bounding box of a GeoJSON object, where its features are expected to be of type "Polygon" or "MultiPolygon".
* It also sets the "boundingBox" member variable of the layer.
* @param {Object} dataOverpassGeoJSON GeoJSON object of which the bounding box is calculated.
*/
OSMBuildingLayer.prototype.calculateBoundingBox = function (GeoJSON) {
OSMBuildingLayer.prototype.calculateBoundingBox = function (dataGeoJSON) {
var boundingBox = [Infinity, Infinity, -Infinity, -Infinity], polygons, coordinates, latitude, longitude;

for (var featureIndex = 0; featureIndex < GeoJSON.features.length; featureIndex++) {
Expand All @@ -168,22 +229,38 @@ define(['libraries/WebWorldWind/src/cache/MemoryCache',
};

/**
*
* @param
* Makes an AJAX request using the "path" of the layer's "source" member variable to fetch the GeoJSON file, adds the GeoJSON to the {@link WorldWindow} using the {@link GeoJSONParserTriangulationOSM}.
* It also sets the "boundingBox" member variable of the layer by calling [calculateBoundingBox]{@link OSMBuildingLayer#calculateBoundingBox}.
* @throws {ArgumentError} If the data returned from the request is empty.
* @throws {ArgumentError} If the request fails.
*/
OSMBuildingLayer.prototype.addByGeoJSONFile = function (path) {
OSMBuildingLayer.prototype.addByGeoJSONFile = function () {
var worldWindow = this.worldWindow;
var _self = this;

$.getJSON(path, function(data) {
_self.calculateBoundingBox(data);
var GeoJSONString = JSON.stringify(data);
var OSMBuildingLayer = new WorldWind.RenderableLayer("OSMBuildingLayer");
var OSMBuildingLayerGeoJSON = new GeoJSONParserTriangulationOSM(GeoJSONString);
OSMBuildingLayerGeoJSON.load(null, _self.shapeConfigurationCallback.bind(_self), OSMBuildingLayer);
worldWindow.addLayer(OSMBuildingLayer);
_self.zoom();
})
$.ajax({
dataType: "json",
url: this.source.path,
success: function(data) {
if (data.length == 0) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OSMBuildingLayer", "addByGeoJSONFile", "File is empty.")
);
}
_self.calculateBoundingBox(data);
var GeoJSONString = JSON.stringify(data);
var OSMBuildingLayer = new WorldWind.RenderableLayer("OSMBuildingLayer");
var OSMBuildingLayerGeoJSON = new GeoJSONParserTriangulationOSM(GeoJSONString);
OSMBuildingLayerGeoJSON.load(null, _self.shapeConfigurationCallback.bind(_self), OSMBuildingLayer);
worldWindow.addLayer(OSMBuildingLayer);
_self.zoom();
},
error: function(e) {
throw new ArgumentError(
Logger.logMessage(Logger.LEVEL_SEVERE, "OSMBuildingLayer", "addByGeoJSONFile", "Request failed. Error: " + JSON.stringify(e))
);
}
});
};

return OSMBuildingLayer;
Expand Down

0 comments on commit 1bf9a93

Please sign in to comment.