argon-osm-occlusion
Version:
Occlusion handling using OpenStreetMap building data in an Argon.js environment.
387 lines (331 loc) • 11.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _three = require('three');
var THREE = _interopRequireWildcard(_three);
var _argon = require('@argonjs/argon');
var Argon = _interopRequireWildcard(_argon);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var app, scene;
var ArgonOsmOcclusion = function () {
/**
* @constructor
* @param {app} Argon app
* @param {scene} THREE scene
*/
function ArgonOsmOcclusion(_app, _scene) {
_classCallCheck(this, ArgonOsmOcclusion);
app = _app;
scene = _scene;
this.debugMode = false;
this.featureGroups = [];
// Bind this to methods
this.add.bind(this);
this.remove.bind(this);
this.removeAll.bind(this);
this.setDebug.bind(this);
app.updateEvent.addEventListener(this.update.bind(this));
}
/**
* @method
* @name add
* @description Function for adding occlusion with real world buildings from OSM around coordinate
* @param {options} Options
* {longitude} Coordinate longitude
* {latitude} Coordinate latitude
* {altitude} Altitude of the added objects. Default 0. Optional.
* {radius} Radius of bounding circle when querying for OSM features. Default 500 m. Optional
* {name} Name of this feature group. Optional.
* {levels} Fallback number of levels if tag is not available on feature in Overpass API. Optional.
* @param {callback} Callback with id of the feature group or error. Optional.
*/
_createClass(ArgonOsmOcclusion, [{
key: 'add',
value: function add(options, callback) {
if (options.longitude == null || options.latitude == null) {
if (callback) {
callback({
message: "Wrong params provided to method. Lng, lat, alt, r required."
});
}
return;
}
options.altitude = options.altitude != null ? options.altitude : 0;
options.radius = options.radius != null ? options.radius : 500;
options.levels = options.levels != null ? options.levels : 3;
var boundingCircle = '(around:' + options.radius + ',' + options.latitude + ',' + options.longitude + ')';
var query = '[out:json];(way["building"]' + boundingCircle + ';relation["building"]' + boundingCircle + ';);out body;>;out skel qt;';
query_overpass(query, function (error, data) {
if (error) {
if (callback) {
return callback(error, undefined);
}
return;
}
var features = [];
data.features.forEach(function (osmFeature) {
features.push(createFeatureEntities(osmFeature, options.altitude, options.levels));
});
var featureGroup = {
id: this.featureGroups.length,
name: options.name,
longitude: options.longitude,
latitude: options.latitude,
altitude: options.altitude,
radius: options.radius,
levels: options.levels,
features: features,
geoObjects: [],
geoEntities: []
};
this.featureGroups.push(featureGroup);
if (callback) {
callback(undefined, featureGroup.id);
}
}.bind(this));
}
/**
* @method
* @name remove
* @description Function for removing all occlusion objects tied to featureGroup in scene
* @param {id} Id of the feature group which associated occlusion objects should be removed
*/
}, {
key: 'remove',
value: function remove(id) {
var featureGroups = this.featureGroups.filter(function (group) {
return group.id === id;
});
if (featureGroups[0] != null) {
featureGroups[0].geoObjects.forEach(function (obj) {
scene.remove(obj);
}.bind(this));
this.featureGroups = this.featureGroups.filter(function (group) {
return group.is !== id;
});
} else {
console.log("ArgonOsmOcclusion: Cannot remove feature group with id " + id + ", it does not exist.");
}
}
/**
* @method
* @name removeAll
* @description Function for removing all occlusion objects in scene
*/
}, {
key: 'removeAll',
value: function removeAll() {
var temp = this.featureGroups;
temp.forEach(function (group) {
this.remove(group.id);
}.bind(this));
}
/**
* @method
* @name enable
* @description Enables all buildings in the scene
*/
}, {
key: 'enable',
value: function enable() {
this.featureGroups.forEach(function (group) {
group.geoObjects.forEach(function (object) {
object.visible = true;
});
});
}
/**
* @method
* @name disable
* @description Disables all buildings in the scene
*/
}, {
key: 'disable',
value: function disable() {
this.featureGroups.forEach(function (group) {
group.geoObjects.forEach(function (object) {
object.visible = false;
});
});
}
/**
* @method
* @name setDebug
* @description Shows/hides all the OSM buildings in the scene
* @param {debug} Bool
*/
}, {
key: 'setDebug',
value: function setDebug(debug) {
this.debugMode = debug;
this.featureGroups.forEach(function (group) {
group.geoObjects.forEach(function (geoObject) {
geoObject.children[0].material.colorWrite = debug;
});
});
}
/**
* @method
* @name update
* @description Triggered on every Argon UpdateEvent
*/
}, {
key: 'update',
value: function update() {
this.featureGroups.forEach(function (group) {
if (group.geoObjects.length < group.features.length) {
group.features.forEach(function (feature) {
if (feature.hasCreatedGeometry == false) {
var result = createGeometry(feature, this.debugMode, app.context, scene);
if (result !== null) {
group.geoEntities.push(result.geoEntity);
group.geoObjects.push(result.geoObject);
}
}
}.bind(this));
} else {
group.geoEntities.forEach(function (geoEntity, index) {
var targetPose = app.context.getEntityPose(geoEntity);
group.geoObjects[index].position.copy(targetPose.position);
}.bind(this));
}
}.bind(this));
}
}]);
return ArgonOsmOcclusion;
}();
exports.default = ArgonOsmOcclusion;
function createFeatureEntities(osmFeature, alt, levelsFallback) {
if (osmFeature.geometry.type !== "Polygon") {
return;
}
var feature = {
shape: [],
holes: [],
hasCreatedGeometry: false,
position: null,
height: 0
};
osmFeature.geometry.coordinates.forEach(function (coordinateGroup, index) {
var entities = [];
coordinateGroup.forEach(function (coordinate, index) {
var geoEntity = new Argon.Cesium.Entity({
name: "",
position: Argon.Cesium.Cartesian3.fromDegrees(coordinate[0], coordinate[1], alt),
orientation: Argon.Cesium.Quaternion.IDENTITY
});
entities.push(geoEntity);
});
if (index == 0) {
feature.shape = entities;
feature.position = entities[0].position;
} else {
feature.holes.push(entities);
}
});
var levels = levelsFallback;
if (osmFeature.properties.tags['building:levels']) {
levels = parseInt(osmFeature.properties.tags['building:levels']);
}
if (osmFeature.properties.tags['roof:shape'] && osmFeature.properties.tags['roof:shape'] !== 'flat') {
levels += 1;
}
feature.height = levels * 3;
return feature;
}
function getVertices(entites, context) {
var vertices = [];
entites.forEach(function (entity) {
var geoPos = context.getEntityPose(entity);
if (geoPos.poseStatus == 0) {
return;
}
var v = new THREE.Vector2(geoPos.position.x, geoPos.position.z);
vertices.push(v);
});
return vertices;
}
function createGeometry(feature, debug, context, scene) {
var vertices = getVertices(feature.shape, context);
if (vertices.length == 0) {
return null;
}
var holes = [];
feature.holes.forEach(function (holeEntites) {
holes.concat(getVertices(holeEntites, context));
});
var shape = new THREE.Shape(vertices, holes);
var extrudeSettings = { amount: feature.height, step: 1, bevelEnabled: false };
var geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
geometry.center();
geometry.rotateX(Math.PI / 2);
geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-geometry.vertices[0].x, geometry.vertices[0].y, -geometry.vertices[0].z));
var material = new THREE.MeshPhongMaterial({
color: 0x0000ff,
colorWrite: debug,
side: THREE.DoubleSide,
shading: THREE.FlatShading
});
var mesh = new THREE.Mesh(geometry, material);
mesh.renderOrder = 1;
var geoObject = new THREE.Object3D();
geoObject.add(mesh);
scene.add(geoObject);
var geoEntity = new Argon.Cesium.Entity({
name: "",
position: feature.position,
orientation: Argon.Cesium.Quaternion.IDENTITY
});
feature.hasCreatedGeometry = true;
return {
geoObject: geoObject,
geoEntity: geoEntity
};
}
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.query_overpass = query_overpass;
var _osmtogeojson = require('osmtogeojson');
var _osmtogeojson2 = _interopRequireDefault(_osmtogeojson);
var _request = require('request');
var _request2 = _interopRequireDefault(_request);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function query_overpass(query, cb, options) {
options = options || {};
var defaultUrl;
if (window.location.protocol === 'https:') {
defaultUrl = 'https://overpass-api.de:443/api/interpreter';
} else {
defaultUrl = 'http://overpass-api.de:80/api/interpreter';
}
_request2.default.post({
url: options.overpassUrl || defaultUrl,
withCredentials: false
}, function (error, response, body) {
var geojson;
if (!error && response.statusCode === 200) {
geojson = (0, _osmtogeojson2.default)(JSON.parse(body), {
flatProperties: options.flatProperties || false
});
cb(undefined, geojson);
} else if (error) {
cb(error);
} else if (response) {
cb({
message: 'Request failed: HTTP ' + response.statusCode,
statusCode: response.statusCode
});
} else {
cb({
message: 'Unknown error.'
});
}
}).form({
data: query
});
};