kopia lustrzana https://github.com/robhawkes/vizicities
Added basic click handlers for GeoJSON features and support picking of lines
rodzic
e78864f60d
commit
790064f3ac
|
|
@ -11824,6 +11824,10 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
options.picking = true;
|
||||
}
|
||||
|
||||
if (this._options.onClick) {
|
||||
options.onClick = this._options.onClick;
|
||||
}
|
||||
|
||||
return (0, _GeoJSONTile2['default'])(quadcode, this._path, layer, options);
|
||||
}
|
||||
|
||||
|
|
@ -11958,6 +11962,7 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
picking: false,
|
||||
topojson: false,
|
||||
filter: null,
|
||||
onClick: null,
|
||||
style: this._defaultStyle
|
||||
};
|
||||
|
||||
|
|
@ -12003,6 +12008,9 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
// Clear request reference
|
||||
this._request = null;
|
||||
|
||||
// TODO: Properly dispose of picking mesh
|
||||
this._pickingMesh = null;
|
||||
|
||||
_get(Object.getPrototypeOf(GeoJSONTile.prototype), 'destroy', this).call(this);
|
||||
}
|
||||
}, {
|
||||
|
|
@ -12217,16 +12225,17 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
allFlat: true
|
||||
};
|
||||
|
||||
if (this._options.picking) {
|
||||
polygons.pickingIds = [];
|
||||
}
|
||||
|
||||
var lines = {
|
||||
vertices: [],
|
||||
colours: [],
|
||||
verticesCount: 0
|
||||
};
|
||||
|
||||
if (this._options.picking) {
|
||||
polygons.pickingIds = [];
|
||||
lines.pickingIds = [];
|
||||
}
|
||||
|
||||
var colour = new _three2['default'].Color();
|
||||
|
||||
features.forEach(function (feature) {
|
||||
|
|
@ -12267,6 +12276,23 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
lines.vertices.push(linestringAttributes.vertices);
|
||||
lines.colours.push(linestringAttributes.colours);
|
||||
|
||||
if (_this3._options.picking) {
|
||||
var pickingId = _this3._layer.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (_this3._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
_this3._world.on('pick-' + pickingId, function () {
|
||||
_this3._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += linestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -12291,6 +12317,23 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
lines.vertices.push(multiLinestringAttributes.vertices);
|
||||
lines.colours.push(multiLinestringAttributes.colours);
|
||||
|
||||
if (_this3._options.picking) {
|
||||
var pickingId = _this3._layer.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (_this3._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
_this3._world.on('pick-' + pickingId, function () {
|
||||
_this3._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += multiLinestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -12331,8 +12374,12 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
polygons.pickingIds.push(pickingId);
|
||||
|
||||
// TODO: This is probably a good point to listen for picking events
|
||||
// relating to this feature and do something with them
|
||||
if (_this3._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
_this3._world.on('pick-' + pickingId, function () {
|
||||
_this3._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (polygons.allFlat && !polygonAttributes.flat) {
|
||||
|
|
@ -12415,6 +12462,17 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
// mesh.castShadow = true;
|
||||
|
||||
this._mesh.add(mesh);
|
||||
|
||||
if (this._options.picking) {
|
||||
material = new _enginePickingMaterial2['default']();
|
||||
material.side = _three2['default'].BackSide;
|
||||
|
||||
// Make the line wider / easier to pick
|
||||
material.linewidth = style.lineWidth + material.linePadding;
|
||||
|
||||
var pickingMesh = new _three2['default'].LineSegments(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Output polygons
|
||||
|
|
@ -14722,8 +14780,15 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
var vertices = new Float32Array(lines.verticesCount * 3);
|
||||
var colours = new Float32Array(lines.verticesCount * 3);
|
||||
|
||||
var pickingIds;
|
||||
if (lines.pickingIds) {
|
||||
// One component per vertex (1)
|
||||
pickingIds = new Float32Array(lines.verticesCount);
|
||||
}
|
||||
|
||||
var _vertices;
|
||||
var _colour;
|
||||
var _pickingId;
|
||||
|
||||
var lastIndex = 0;
|
||||
|
||||
|
|
@ -14731,6 +14796,10 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
_vertices = lines.vertices[i];
|
||||
_colour = lines.colours[i];
|
||||
|
||||
if (pickingIds) {
|
||||
_pickingId = lines.pickingIds[i];
|
||||
}
|
||||
|
||||
for (var j = 0; j < _vertices.length; j++) {
|
||||
var ax = _vertices[j][0] + offset.x;
|
||||
var ay = _vertices[j][1];
|
||||
|
|
@ -14746,6 +14815,10 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
colours[lastIndex * 3 + 1] = c1[1];
|
||||
colours[lastIndex * 3 + 2] = c1[2];
|
||||
|
||||
if (pickingIds) {
|
||||
pickingIds[lastIndex] = _pickingId;
|
||||
}
|
||||
|
||||
lastIndex++;
|
||||
}
|
||||
}
|
||||
|
|
@ -14754,6 +14827,10 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
geometry.addAttribute('position', new _three2['default'].BufferAttribute(vertices, 3));
|
||||
geometry.addAttribute('color', new _three2['default'].BufferAttribute(colours, 3));
|
||||
|
||||
if (pickingIds) {
|
||||
geometry.addAttribute('pickingId', new _three2['default'].BufferAttribute(pickingIds, 1));
|
||||
}
|
||||
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
return geometry;
|
||||
|
|
@ -14929,33 +15006,35 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
var PickingMaterial = function PickingMaterial() {
|
||||
_three2['default'].ShaderMaterial.call(this, {
|
||||
// uniforms: {
|
||||
// size: {
|
||||
// type: 'f',
|
||||
// value: 0.01,
|
||||
// },
|
||||
// scale: {
|
||||
// type: 'f',
|
||||
// value: 400,
|
||||
// }
|
||||
// },
|
||||
uniforms: {
|
||||
size: {
|
||||
type: 'f',
|
||||
value: 0.01
|
||||
},
|
||||
scale: {
|
||||
type: 'f',
|
||||
value: 400
|
||||
}
|
||||
},
|
||||
// attributes: ['position', 'id'],
|
||||
vertexShader: _PickingShader2['default'].vertexShader,
|
||||
fragmentShader: _PickingShader2['default'].fragmentShader
|
||||
});
|
||||
|
||||
this.linePadding = 2;
|
||||
};
|
||||
|
||||
PickingMaterial.prototype = Object.create(_three2['default'].ShaderMaterial.prototype);
|
||||
|
||||
PickingMaterial.prototype.constructor = PickingMaterial;
|
||||
|
||||
// PickingMaterial.prototype.setPointSize = function(size) {
|
||||
// this.uniforms.size.value = size;
|
||||
// };
|
||||
//
|
||||
// PickingMaterial.prototype.setPointScale = function(scale) {
|
||||
// this.uniforms.scale.value = scale;
|
||||
// };
|
||||
PickingMaterial.prototype.setPointSize = function (size) {
|
||||
this.uniforms.size.value = size;
|
||||
};
|
||||
|
||||
PickingMaterial.prototype.setPointScale = function (scale) {
|
||||
this.uniforms.scale.value = scale;
|
||||
};
|
||||
|
||||
exports['default'] = PickingMaterial;
|
||||
module.exports = exports['default'];
|
||||
|
|
@ -15067,6 +15146,10 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
var _utilBuffer2 = _interopRequireDefault(_utilBuffer);
|
||||
|
||||
var _enginePickingMaterial = __webpack_require__(66);
|
||||
|
||||
var _enginePickingMaterial2 = _interopRequireDefault(_enginePickingMaterial);
|
||||
|
||||
var GeoJSONLayer = (function (_Layer) {
|
||||
_inherits(GeoJSONLayer, _Layer);
|
||||
|
||||
|
|
@ -15080,8 +15163,10 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
this._defaultStyle = _utilGeoJSON2['default'].defaultStyle;
|
||||
|
||||
var defaults = {
|
||||
picking: false,
|
||||
topojson: false,
|
||||
filter: null,
|
||||
onClick: null,
|
||||
style: this._defaultStyle
|
||||
};
|
||||
|
||||
|
|
@ -15092,6 +15177,8 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
} else {
|
||||
this._options.style = (0, _lodashAssign2['default'])({}, defaults.style, options.style);
|
||||
}
|
||||
|
||||
this._pickingMesh = new _three2['default'].Object3D();
|
||||
}
|
||||
|
||||
// Initialise without requiring new keyword
|
||||
|
|
@ -15099,6 +15186,8 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
_createClass(GeoJSONLayer, [{
|
||||
key: '_onAdd',
|
||||
value: function _onAdd(world) {
|
||||
this.addToPicking(this._pickingMesh);
|
||||
|
||||
// Request data from URL if needed
|
||||
if (typeof this._geojson === 'string') {
|
||||
this._requestData(this._geojson);
|
||||
|
|
@ -15173,6 +15262,11 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
verticesCount: 0
|
||||
};
|
||||
|
||||
if (this._options.picking) {
|
||||
polygons.pickingIds = [];
|
||||
lines.pickingIds = [];
|
||||
}
|
||||
|
||||
var colour = new _three2['default'].Color();
|
||||
|
||||
features.forEach(function (feature) {
|
||||
|
|
@ -15222,6 +15316,23 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
lines.vertices.push(linestringAttributes.vertices);
|
||||
lines.colours.push(linestringAttributes.colours);
|
||||
|
||||
if (_this2._options.picking) {
|
||||
var pickingId = _this2.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (_this2._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
_this2._world.on('pick-' + pickingId, function () {
|
||||
_this2._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += linestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -15255,6 +15366,23 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
|
||||
lines.vertices.push(multiLinestringAttributes.vertices);
|
||||
lines.colours.push(multiLinestringAttributes.colours);
|
||||
|
||||
if (_this2._options.picking) {
|
||||
var pickingId = _this2.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (_this2._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
_this2._world.on('pick-' + pickingId, function () {
|
||||
_this2._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += multiLinestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -15290,6 +15418,22 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
polygons.faces.push(polygonAttributes.faces);
|
||||
polygons.colours.push(polygonAttributes.colours);
|
||||
|
||||
if (_this2._options.picking) {
|
||||
var pickingId = _this2.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
polygons.pickingIds.push(pickingId);
|
||||
|
||||
if (_this2._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
_this2._world.on('pick-' + pickingId, function () {
|
||||
_this2._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (polygons.allFlat && !polygonAttributes.flat) {
|
||||
polygons.allFlat = false;
|
||||
}
|
||||
|
|
@ -15302,6 +15446,12 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
var material;
|
||||
var mesh;
|
||||
|
||||
if (this._options.picking) {
|
||||
// Move picking mesh to origin Point
|
||||
this._pickingMesh.position.x = -offset.x;
|
||||
this._pickingMesh.position.z = -offset.y;
|
||||
}
|
||||
|
||||
// Output lines
|
||||
if (lines.vertices.length > 0) {
|
||||
geometry = _utilBuffer2['default'].createLineGeometry(lines, offset);
|
||||
|
|
@ -15325,6 +15475,17 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
// mesh.castShadow = true;
|
||||
|
||||
this.add(mesh);
|
||||
|
||||
if (this._options.picking) {
|
||||
material = new _enginePickingMaterial2['default']();
|
||||
material.side = _three2['default'].BackSide;
|
||||
|
||||
// Make the line wider / easier to pick
|
||||
material.linewidth = style.lineWidth + material.linePadding;
|
||||
|
||||
var pickingMesh = new _three2['default'].LineSegments(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Output polygons
|
||||
|
|
@ -15358,6 +15519,14 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
}
|
||||
|
||||
this.add(mesh);
|
||||
|
||||
if (this._options.picking) {
|
||||
material = new _enginePickingMaterial2['default']();
|
||||
material.side = _three2['default'].BackSide;
|
||||
|
||||
var pickingMesh = new _three2['default'].Mesh(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Move layer to origin Point
|
||||
|
|
@ -15387,6 +15556,9 @@ return /******/ (function(modules) { // webpackBootstrap
|
|||
// Clear request reference
|
||||
this._request = null;
|
||||
|
||||
// TODO: Properly dispose of picking mesh
|
||||
this._pickingMesh = null;
|
||||
|
||||
// Run common destruction logic from parent
|
||||
_get(Object.getPrototypeOf(GeoJSONLayer.prototype), 'destroy', this).call(this);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -13,7 +13,7 @@ var imageTileLayer = VIZI.ImageTileLayer('http://{s}.basemaps.cartocdn.com/light
|
|||
|
||||
// Building and roads from Mapzen (polygons and linestrings)
|
||||
var topoJSONTileLayer = VIZI.TopoJSONTileLayer('https://vector.mapzen.com/osm/buildings,roads/{z}/{x}/{y}.topojson?api_key=vector-tiles-NT5Emiw', {
|
||||
picking: true,
|
||||
// picking: true,
|
||||
style: function(feature) {
|
||||
var height;
|
||||
|
||||
|
|
@ -33,10 +33,13 @@ var topoJSONTileLayer = VIZI.TopoJSONTileLayer('https://vector.mapzen.com/osm/bu
|
|||
lineRenderOrder: 2
|
||||
};
|
||||
},
|
||||
// onClick: function(feature) {
|
||||
// console.log('Clicked:', feature);
|
||||
// },
|
||||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://whosonfirst.mapzen.com#License">Who\'s On First</a>.'
|
||||
}).addTo(world);
|
||||
|
||||
// // Just buildings from Mapzen (polygons)
|
||||
// Just buildings from Mapzen (polygons)
|
||||
// var topoJSONTileLayer = VIZI.TopoJSONTileLayer('https://vector.mapzen.com/osm/buildings/{z}/{x}/{y}.topojson?api_key=vector-tiles-NT5Emiw', {
|
||||
// style: function(feature) {
|
||||
// var height;
|
||||
|
|
@ -61,8 +64,9 @@ var topoJSONTileLayer = VIZI.TopoJSONTileLayer('https://vector.mapzen.com/osm/bu
|
|||
// attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://whosonfirst.mapzen.com#License">Who\'s On First</a>.'
|
||||
// }).addTo(world);
|
||||
|
||||
// // London Underground lines
|
||||
// London Underground lines
|
||||
// var geoJSONLayer = VIZI.GeoJSONLayer('https://rawgit.com/robhawkes/4acb9d6a6a5f00a377e2/raw/30ae704a44e10f2e13fb7e956e80c3b22e8e7e81/tfl_lines.json', {
|
||||
// picking: true,
|
||||
// style: {
|
||||
// lineColor: '#f7c616',
|
||||
// // lineHeight: 20,
|
||||
|
|
@ -72,5 +76,8 @@ var topoJSONTileLayer = VIZI.TopoJSONTileLayer('https://vector.mapzen.com/osm/bu
|
|||
// lineBlending: THREE.AdditiveBlending,
|
||||
// lineRenderOrder: 2
|
||||
// },
|
||||
// onClick: function(feature) {
|
||||
// console.log('Clicked:', feature);
|
||||
// },
|
||||
// attribution: '© Transport for London.'
|
||||
// }).addTo(world);
|
||||
|
|
|
|||
297
src/World.js
297
src/World.js
|
|
@ -1,297 +0,0 @@
|
|||
import EventEmitter from 'eventemitter3';
|
||||
import extend from 'lodash.assign';
|
||||
import CRS from './geo/crs/index';
|
||||
import Point from './geo/Point';
|
||||
import LatLon from './geo/LatLon';
|
||||
import Engine from './engine/Engine';
|
||||
|
||||
// TODO: Make sure nothing is left behind in the heap after calling destroy()
|
||||
|
||||
// Pretty much any event someone using ViziCities would need will be emitted or
|
||||
// proxied by World (eg. render events, etc)
|
||||
|
||||
class World extends EventEmitter {
|
||||
constructor(domId, options) {
|
||||
super();
|
||||
|
||||
var defaults = {
|
||||
crs: CRS.EPSG3857,
|
||||
skybox: false
|
||||
};
|
||||
|
||||
this.options = extend({}, defaults, options);
|
||||
|
||||
this._layers = [];
|
||||
this._controls = [];
|
||||
|
||||
this._initContainer(domId);
|
||||
this._initEngine();
|
||||
this._initEnvironment();
|
||||
this._initEvents();
|
||||
|
||||
this._pause = false;
|
||||
|
||||
// Kick off the update and render loop
|
||||
this._update();
|
||||
}
|
||||
|
||||
_initContainer(domId) {
|
||||
this._container = document.getElementById(domId);
|
||||
}
|
||||
|
||||
_initEngine() {
|
||||
this._engine = Engine(this._container, this);
|
||||
|
||||
// Engine events
|
||||
//
|
||||
// Consider proxying these through events on World for public access
|
||||
// this._engine.on('preRender', () => {});
|
||||
// this._engine.on('postRender', () => {});
|
||||
}
|
||||
|
||||
_initEnvironment() {
|
||||
// Not sure if I want to keep this as a private API
|
||||
//
|
||||
// Makes sense to allow others to customise their environment so perhaps
|
||||
// add some method of disable / overriding the environment settings
|
||||
this._environment = VIZI.EnvironmentLayer({
|
||||
skybox: this.options.skybox
|
||||
}).addTo(this);
|
||||
}
|
||||
|
||||
_initEvents() {
|
||||
this.on('controlsMoveEnd', this._onControlsMoveEnd);
|
||||
}
|
||||
|
||||
_onControlsMoveEnd(point) {
|
||||
var _point = Point(point.x, point.z);
|
||||
this._resetView(this.pointToLatLon(_point), _point);
|
||||
}
|
||||
|
||||
// Reset world view
|
||||
_resetView(latlon, point) {
|
||||
this.emit('preResetView');
|
||||
|
||||
this._moveStart();
|
||||
this._move(latlon, point);
|
||||
this._moveEnd();
|
||||
|
||||
this.emit('postResetView');
|
||||
}
|
||||
|
||||
_moveStart() {
|
||||
this.emit('moveStart');
|
||||
}
|
||||
|
||||
_move(latlon, point) {
|
||||
this._lastPosition = latlon;
|
||||
this.emit('move', latlon, point);
|
||||
}
|
||||
_moveEnd() {
|
||||
this.emit('moveEnd');
|
||||
}
|
||||
|
||||
_update() {
|
||||
if (this._pause) {
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = this._engine.clock.getDelta();
|
||||
|
||||
// Once _update is called it will run forever, for now
|
||||
window.requestAnimationFrame(this._update.bind(this));
|
||||
|
||||
// Update controls
|
||||
this._controls.forEach(controls => {
|
||||
controls.update();
|
||||
});
|
||||
|
||||
this.emit('preUpdate', delta);
|
||||
this._engine.update(delta);
|
||||
this.emit('postUpdate', delta);
|
||||
}
|
||||
|
||||
// Set world view
|
||||
setView(latlon) {
|
||||
// Store initial geographic coordinate for the [0,0,0] world position
|
||||
//
|
||||
// The origin point doesn't move in three.js / 3D space so only set it once
|
||||
// here instead of every time _resetView is called
|
||||
//
|
||||
// If it was updated every time then coorindates would shift over time and
|
||||
// would be out of place / context with previously-placed points (0,0 would
|
||||
// refer to a different point each time)
|
||||
this._originLatlon = latlon;
|
||||
this._originPoint = this.project(latlon);
|
||||
|
||||
this._resetView(latlon);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Return world geographic position
|
||||
getPosition() {
|
||||
return this._lastPosition;
|
||||
}
|
||||
|
||||
// Transform geographic coordinate to world point
|
||||
//
|
||||
// This doesn't take into account the origin offset
|
||||
//
|
||||
// For example, this takes a geographic coordinate and returns a point
|
||||
// relative to the origin point of the projection (not the world)
|
||||
project(latlon) {
|
||||
return this.options.crs.latLonToPoint(LatLon(latlon));
|
||||
}
|
||||
|
||||
// Transform world point to geographic coordinate
|
||||
//
|
||||
// This doesn't take into account the origin offset
|
||||
//
|
||||
// For example, this takes a point relative to the origin point of the
|
||||
// projection (not the world) and returns a geographic coordinate
|
||||
unproject(point) {
|
||||
return this.options.crs.pointToLatLon(Point(point));
|
||||
}
|
||||
|
||||
// Takes into account the origin offset
|
||||
//
|
||||
// For example, this takes a geographic coordinate and returns a point
|
||||
// relative to the three.js / 3D origin (0,0)
|
||||
latLonToPoint(latlon) {
|
||||
var projectedPoint = this.project(LatLon(latlon));
|
||||
return projectedPoint._subtract(this._originPoint);
|
||||
}
|
||||
|
||||
// Takes into account the origin offset
|
||||
//
|
||||
// For example, this takes a point relative to the three.js / 3D origin (0,0)
|
||||
// and returns the exact geographic coordinate at that point
|
||||
pointToLatLon(point) {
|
||||
var projectedPoint = Point(point).add(this._originPoint);
|
||||
return this.unproject(projectedPoint);
|
||||
}
|
||||
|
||||
// Return pointscale for a given geographic coordinate
|
||||
pointScale(latlon, accurate) {
|
||||
return this.options.crs.pointScale(latlon, accurate);
|
||||
}
|
||||
|
||||
// Convert from real meters to world units
|
||||
//
|
||||
// TODO: Would be nice not to have to pass in a pointscale here
|
||||
metresToWorld(metres, pointScale, zoom) {
|
||||
return this.options.crs.metresToWorld(metres, pointScale, zoom);
|
||||
}
|
||||
|
||||
// Convert from real meters to world units
|
||||
//
|
||||
// TODO: Would be nice not to have to pass in a pointscale here
|
||||
worldToMetres(worldUnits, pointScale, zoom) {
|
||||
return this.options.crs.worldToMetres(worldUnits, pointScale, zoom);
|
||||
}
|
||||
|
||||
// Unsure if it's a good idea to expose this here for components like
|
||||
// GridLayer to use (eg. to keep track of a frustum)
|
||||
getCamera() {
|
||||
return this._engine._camera;
|
||||
}
|
||||
|
||||
addLayer(layer) {
|
||||
layer._addToWorld(this);
|
||||
|
||||
this._layers.push(layer);
|
||||
|
||||
// Could move this into Layer but it'll do here for now
|
||||
this._engine._scene.add(layer._layer);
|
||||
|
||||
this.emit('layerAdded', layer);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Remove layer from world and scene but don't destroy it entirely
|
||||
removeLayer(layer) {
|
||||
var layerIndex = this._layers.indexOf(layer);
|
||||
|
||||
if (layerIndex > -1) {
|
||||
// Remove from this._layers
|
||||
this._layers.splice(layerIndex, 1);
|
||||
};
|
||||
|
||||
this._engine._scene.remove(layer._layer);
|
||||
|
||||
this.emit('layerRemoved');
|
||||
return this;
|
||||
}
|
||||
|
||||
addControls(controls) {
|
||||
controls._addToWorld(this);
|
||||
|
||||
this._controls.push(controls);
|
||||
|
||||
this.emit('controlsAdded', controls);
|
||||
return this;
|
||||
}
|
||||
|
||||
// Remove controls from world but don't destroy them entirely
|
||||
removeControls(controls) {
|
||||
var controlsIndex = this._controls.indexOf(controlsIndex);
|
||||
|
||||
if (controlsIndex > -1) {
|
||||
this._controls.splice(controlsIndex, 1);
|
||||
};
|
||||
|
||||
this.emit('controlsRemoved', controls);
|
||||
return this;
|
||||
}
|
||||
|
||||
stop() {
|
||||
this._pause = true;
|
||||
}
|
||||
|
||||
start() {
|
||||
this._pause = false;
|
||||
this._update();
|
||||
}
|
||||
|
||||
// Destroys the world(!) and removes it from the scene and memory
|
||||
//
|
||||
// TODO: World out why so much three.js stuff is left in the heap after this
|
||||
destroy() {
|
||||
this.stop();
|
||||
|
||||
// Remove listeners
|
||||
this.off('controlsMoveEnd', this._onControlsMoveEnd);
|
||||
|
||||
var i;
|
||||
|
||||
// Remove all controls
|
||||
var controls;
|
||||
for (i = this._controls.length - 1; i >= 0; i--) {
|
||||
controls = this._controls[0];
|
||||
this.removeControls(controls);
|
||||
controls.destroy();
|
||||
};
|
||||
|
||||
// Remove all layers
|
||||
var layer;
|
||||
for (i = this._layers.length - 1; i >= 0; i--) {
|
||||
layer = this._layers[0];
|
||||
this.removeLayer(layer);
|
||||
layer.destroy();
|
||||
};
|
||||
|
||||
// Environment layer is removed with the other layers
|
||||
this._environment = null;
|
||||
|
||||
this._engine.destroy();
|
||||
this._engine = null;
|
||||
|
||||
// TODO: Probably should clean the container too / remove the canvas
|
||||
this._container = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise without requiring new keyword
|
||||
export default function(domId, options) {
|
||||
return new World(domId, options);
|
||||
};
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import EventEmitter from 'eventemitter3';
|
||||
import THREE from 'three';
|
||||
import Scene from './Scene';
|
||||
import Renderer from './Renderer';
|
||||
import Camera from './Camera';
|
||||
import Picking from './Picking';
|
||||
|
||||
class Engine extends EventEmitter {
|
||||
constructor(container, world) {
|
||||
console.log('Init Engine');
|
||||
|
||||
super();
|
||||
|
||||
this._world = world;
|
||||
this._scene = Scene;
|
||||
this._renderer = Renderer(container);
|
||||
this._camera = Camera(container);
|
||||
|
||||
// TODO: Make this optional
|
||||
this._picking = Picking(this._world, this._renderer, this._camera);
|
||||
|
||||
this.clock = new THREE.Clock();
|
||||
|
||||
this._frustum = new THREE.Frustum();
|
||||
}
|
||||
|
||||
update(delta) {
|
||||
this.emit('preRender');
|
||||
|
||||
this._renderer.render(this._scene, this._camera);
|
||||
|
||||
// Render picking scene
|
||||
// this._renderer.render(this._picking._pickingScene, this._camera);
|
||||
|
||||
this.emit('postRender');
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// Remove any remaining objects from scene
|
||||
var child;
|
||||
for (i = this._scene.children.length - 1; i >= 0; i--) {
|
||||
child = this._scene.children[i];
|
||||
|
||||
if (!child) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._scene.remove(child);
|
||||
|
||||
if (child.geometry) {
|
||||
// Dispose of mesh and materials
|
||||
child.geometry.dispose();
|
||||
child.geometry = null;
|
||||
}
|
||||
|
||||
if (child.material) {
|
||||
if (child.material.map) {
|
||||
child.material.map.dispose();
|
||||
child.material.map = null;
|
||||
}
|
||||
|
||||
child.material.dispose();
|
||||
child.material = null;
|
||||
}
|
||||
};
|
||||
|
||||
this._picking.destroy();
|
||||
this._picking = null;
|
||||
|
||||
this._world = null;
|
||||
this._scene = null;
|
||||
this._renderer = null;
|
||||
this._camera = null;
|
||||
this._clock = null;
|
||||
this._frustum = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise without requiring new keyword
|
||||
export default function(container, world) {
|
||||
return new Engine(container, world);
|
||||
};
|
||||
|
|
@ -5,32 +5,34 @@ import PickingShader from './PickingShader';
|
|||
|
||||
var PickingMaterial = function() {
|
||||
THREE.ShaderMaterial.call(this, {
|
||||
// uniforms: {
|
||||
// size: {
|
||||
// type: 'f',
|
||||
// value: 0.01,
|
||||
// },
|
||||
// scale: {
|
||||
// type: 'f',
|
||||
// value: 400,
|
||||
// }
|
||||
// },
|
||||
uniforms: {
|
||||
size: {
|
||||
type: 'f',
|
||||
value: 0.01,
|
||||
},
|
||||
scale: {
|
||||
type: 'f',
|
||||
value: 400,
|
||||
}
|
||||
},
|
||||
// attributes: ['position', 'id'],
|
||||
vertexShader: PickingShader.vertexShader,
|
||||
fragmentShader: PickingShader.fragmentShader
|
||||
});
|
||||
|
||||
this.linePadding = 2;
|
||||
};
|
||||
|
||||
PickingMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype);
|
||||
|
||||
PickingMaterial.prototype.constructor = PickingMaterial;
|
||||
|
||||
// PickingMaterial.prototype.setPointSize = function(size) {
|
||||
// this.uniforms.size.value = size;
|
||||
// };
|
||||
//
|
||||
// PickingMaterial.prototype.setPointScale = function(scale) {
|
||||
// this.uniforms.scale.value = scale;
|
||||
// };
|
||||
PickingMaterial.prototype.setPointSize = function(size) {
|
||||
this.uniforms.size.value = size;
|
||||
};
|
||||
|
||||
PickingMaterial.prototype.setPointScale = function(scale) {
|
||||
this.uniforms.scale.value = scale;
|
||||
};
|
||||
|
||||
export default PickingMaterial;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Point from '../geo/Point';
|
|||
import LatLon from '../geo/LatLon';
|
||||
import GeoJSON from '../util/GeoJSON';
|
||||
import Buffer from '../util/Buffer';
|
||||
import PickingMaterial from '../engine/PickingMaterial';
|
||||
|
||||
class GeoJSONLayer extends Layer {
|
||||
constructor(geojson, options) {
|
||||
|
|
@ -16,8 +17,10 @@ class GeoJSONLayer extends Layer {
|
|||
this._defaultStyle = GeoJSON.defaultStyle;
|
||||
|
||||
var defaults = {
|
||||
picking: false,
|
||||
topojson: false,
|
||||
filter: null,
|
||||
onClick: null,
|
||||
style: this._defaultStyle
|
||||
};
|
||||
|
||||
|
|
@ -28,9 +31,13 @@ class GeoJSONLayer extends Layer {
|
|||
} else {
|
||||
this._options.style = extend({}, defaults.style, options.style);
|
||||
}
|
||||
|
||||
this._pickingMesh = new THREE.Object3D();
|
||||
}
|
||||
|
||||
_onAdd(world) {
|
||||
this.addToPicking(this._pickingMesh);
|
||||
|
||||
// Request data from URL if needed
|
||||
if (typeof this._geojson === 'string') {
|
||||
this._requestData(this._geojson);
|
||||
|
|
@ -99,6 +106,11 @@ class GeoJSONLayer extends Layer {
|
|||
verticesCount: 0
|
||||
};
|
||||
|
||||
if (this._options.picking) {
|
||||
polygons.pickingIds = [];
|
||||
lines.pickingIds = [];
|
||||
}
|
||||
|
||||
var colour = new THREE.Color();
|
||||
|
||||
features.forEach(feature => {
|
||||
|
|
@ -152,6 +164,23 @@ class GeoJSONLayer extends Layer {
|
|||
|
||||
lines.vertices.push(linestringAttributes.vertices);
|
||||
lines.colours.push(linestringAttributes.colours);
|
||||
|
||||
if (this._options.picking) {
|
||||
var pickingId = this.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (this._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
this._world.on('pick-' + pickingId, () => {
|
||||
this._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += linestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +214,23 @@ class GeoJSONLayer extends Layer {
|
|||
|
||||
lines.vertices.push(multiLinestringAttributes.vertices);
|
||||
lines.colours.push(multiLinestringAttributes.colours);
|
||||
|
||||
if (this._options.picking) {
|
||||
var pickingId = this.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (this._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
this._world.on('pick-' + pickingId, () => {
|
||||
this._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += multiLinestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -220,6 +266,22 @@ class GeoJSONLayer extends Layer {
|
|||
polygons.faces.push(polygonAttributes.faces);
|
||||
polygons.colours.push(polygonAttributes.colours);
|
||||
|
||||
if (this._options.picking) {
|
||||
var pickingId = this.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
polygons.pickingIds.push(pickingId);
|
||||
|
||||
if (this._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
this._world.on('pick-' + pickingId, () => {
|
||||
this._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (polygons.allFlat && !polygonAttributes.flat) {
|
||||
polygons.allFlat = false;
|
||||
}
|
||||
|
|
@ -232,6 +294,12 @@ class GeoJSONLayer extends Layer {
|
|||
var material;
|
||||
var mesh;
|
||||
|
||||
if (this._options.picking) {
|
||||
// Move picking mesh to origin Point
|
||||
this._pickingMesh.position.x = -offset.x;
|
||||
this._pickingMesh.position.z = -offset.y;
|
||||
}
|
||||
|
||||
// Output lines
|
||||
if (lines.vertices.length > 0) {
|
||||
geometry = Buffer.createLineGeometry(lines, offset);
|
||||
|
|
@ -255,6 +323,17 @@ class GeoJSONLayer extends Layer {
|
|||
// mesh.castShadow = true;
|
||||
|
||||
this.add(mesh);
|
||||
|
||||
if (this._options.picking) {
|
||||
material = new PickingMaterial();
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
// Make the line wider / easier to pick
|
||||
material.linewidth = style.lineWidth + material.linePadding;
|
||||
|
||||
var pickingMesh = new THREE.LineSegments(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Output polygons
|
||||
|
|
@ -288,6 +367,14 @@ class GeoJSONLayer extends Layer {
|
|||
}
|
||||
|
||||
this.add(mesh);
|
||||
|
||||
if (this._options.picking) {
|
||||
material = new PickingMaterial();
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
var pickingMesh = new THREE.Mesh(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Move layer to origin Point
|
||||
|
|
@ -315,6 +402,9 @@ class GeoJSONLayer extends Layer {
|
|||
// Clear request reference
|
||||
this._request = null;
|
||||
|
||||
// TODO: Properly dispose of picking mesh
|
||||
this._pickingMesh = null;
|
||||
|
||||
// Run common destruction logic from parent
|
||||
super.destroy();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ class GeoJSONTile extends Tile {
|
|||
picking: false,
|
||||
topojson: false,
|
||||
filter: null,
|
||||
onClick: null,
|
||||
style: this._defaultStyle
|
||||
};
|
||||
|
||||
|
|
@ -90,6 +91,9 @@ class GeoJSONTile extends Tile {
|
|||
// Clear request reference
|
||||
this._request = null;
|
||||
|
||||
// TODO: Properly dispose of picking mesh
|
||||
this._pickingMesh = null;
|
||||
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
|
|
@ -293,16 +297,17 @@ class GeoJSONTile extends Tile {
|
|||
allFlat: true
|
||||
};
|
||||
|
||||
if (this._options.picking) {
|
||||
polygons.pickingIds = [];
|
||||
}
|
||||
|
||||
var lines = {
|
||||
vertices: [],
|
||||
colours: [],
|
||||
verticesCount: 0
|
||||
};
|
||||
|
||||
if (this._options.picking) {
|
||||
polygons.pickingIds = [];
|
||||
lines.pickingIds = [];
|
||||
}
|
||||
|
||||
var colour = new THREE.Color();
|
||||
|
||||
features.forEach(feature => {
|
||||
|
|
@ -347,6 +352,23 @@ class GeoJSONTile extends Tile {
|
|||
|
||||
lines.vertices.push(linestringAttributes.vertices);
|
||||
lines.colours.push(linestringAttributes.colours);
|
||||
|
||||
if (this._options.picking) {
|
||||
var pickingId = this._layer.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (this._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
this._world.on('pick-' + pickingId, () => {
|
||||
this._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += linestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -371,6 +393,23 @@ class GeoJSONTile extends Tile {
|
|||
|
||||
lines.vertices.push(multiLinestringAttributes.vertices);
|
||||
lines.colours.push(multiLinestringAttributes.colours);
|
||||
|
||||
if (this._options.picking) {
|
||||
var pickingId = this._layer.getPickingId();
|
||||
|
||||
// Inject picking ID
|
||||
//
|
||||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
lines.pickingIds.push(pickingId);
|
||||
|
||||
if (this._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
this._world.on('pick-' + pickingId, () => {
|
||||
this._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
lines.verticesCount += multiLinestringAttributes.vertices.length;
|
||||
}
|
||||
|
||||
|
|
@ -411,8 +450,12 @@ class GeoJSONTile extends Tile {
|
|||
// TODO: Perhaps handle this within the GeoJSON helper
|
||||
polygons.pickingIds.push(pickingId);
|
||||
|
||||
// TODO: This is probably a good point to listen for picking events
|
||||
// relating to this feature and do something with them
|
||||
if (this._options.onClick) {
|
||||
// TODO: Find a way to properly remove this listener on destroy
|
||||
this._world.on('pick-' + pickingId, () => {
|
||||
this._options.onClick(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (polygons.allFlat && !polygonAttributes.flat) {
|
||||
|
|
@ -495,6 +538,17 @@ class GeoJSONTile extends Tile {
|
|||
// mesh.castShadow = true;
|
||||
|
||||
this._mesh.add(mesh);
|
||||
|
||||
if (this._options.picking) {
|
||||
material = new PickingMaterial();
|
||||
material.side = THREE.BackSide;
|
||||
|
||||
// Make the line wider / easier to pick
|
||||
material.linewidth = style.lineWidth + material.linePadding;
|
||||
|
||||
var pickingMesh = new THREE.LineSegments(geometry, material);
|
||||
this._pickingMesh.add(pickingMesh);
|
||||
}
|
||||
}
|
||||
|
||||
// Output polygons
|
||||
|
|
|
|||
|
|
@ -111,6 +111,10 @@ class GeoJSONTileLayer extends TileLayer {
|
|||
options.picking = true;
|
||||
}
|
||||
|
||||
if (this._options.onClick) {
|
||||
options.onClick = this._options.onClick;
|
||||
}
|
||||
|
||||
return GeoJSONTile(quadcode, this._path, layer, options);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,15 @@ var Buffer = (function() {
|
|||
var vertices = new Float32Array(lines.verticesCount * 3);
|
||||
var colours = new Float32Array(lines.verticesCount * 3);
|
||||
|
||||
var pickingIds;
|
||||
if (lines.pickingIds) {
|
||||
// One component per vertex (1)
|
||||
pickingIds = new Float32Array(lines.verticesCount);
|
||||
}
|
||||
|
||||
var _vertices;
|
||||
var _colour;
|
||||
var _pickingId;
|
||||
|
||||
var lastIndex = 0;
|
||||
|
||||
|
|
@ -20,6 +27,10 @@ var Buffer = (function() {
|
|||
_vertices = lines.vertices[i];
|
||||
_colour = lines.colours[i];
|
||||
|
||||
if (pickingIds) {
|
||||
_pickingId = lines.pickingIds[i];
|
||||
}
|
||||
|
||||
for (var j = 0; j < _vertices.length; j++) {
|
||||
var ax = _vertices[j][0] + offset.x;
|
||||
var ay = _vertices[j][1];
|
||||
|
|
@ -35,6 +46,10 @@ var Buffer = (function() {
|
|||
colours[lastIndex * 3 + 1] = c1[1];
|
||||
colours[lastIndex * 3 + 2] = c1[2];
|
||||
|
||||
if (pickingIds) {
|
||||
pickingIds[lastIndex] = _pickingId;
|
||||
}
|
||||
|
||||
lastIndex++;
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +58,10 @@ var Buffer = (function() {
|
|||
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||
geometry.addAttribute('color', new THREE.BufferAttribute(colours, 3));
|
||||
|
||||
if (pickingIds) {
|
||||
geometry.addAttribute('pickingId', new THREE.BufferAttribute(pickingIds, 1));
|
||||
}
|
||||
|
||||
geometry.computeBoundingBox();
|
||||
|
||||
return geometry;
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue