kopia lustrzana https://github.com/robhawkes/vizicities
327 wiersze
8.5 KiB
JavaScript
327 wiersze
8.5 KiB
JavaScript
|
import Layer from './Layer';
|
||
|
import THREE from 'three';
|
||
|
import reqwest from 'reqwest';
|
||
|
import extend from 'lodash.assign';
|
||
|
import Point from '../geo/Point';
|
||
|
import LatLon from '../geo/LatLon';
|
||
|
import GeoJSON from '../util/GeoJSON';
|
||
|
import Buffer from '../util/Buffer';
|
||
|
|
||
|
class GeoJSONLayer extends Layer {
|
||
|
constructor(geojson, options) {
|
||
|
super(options);
|
||
|
|
||
|
this._geojson = geojson;
|
||
|
|
||
|
this._defaultStyle = GeoJSON.defaultStyle;
|
||
|
|
||
|
var defaults = {
|
||
|
topojson: false,
|
||
|
filter: null,
|
||
|
style: this._defaultStyle
|
||
|
};
|
||
|
|
||
|
this._options = extend({}, defaults, options);
|
||
|
|
||
|
if (typeof options.style === 'function') {
|
||
|
this._options.style = options.style;
|
||
|
} else {
|
||
|
this._options.style = extend({}, defaults.style, options.style);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_onAdd(world) {
|
||
|
// Request data from URL if needed
|
||
|
if (typeof this._geojson === 'string') {
|
||
|
this._requestData(this._geojson);
|
||
|
} else {
|
||
|
// Process and add GeoJSON to layer
|
||
|
this._processData(this._geojson);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_requestData(url) {
|
||
|
this._request = reqwest({
|
||
|
url: url,
|
||
|
type: 'json',
|
||
|
crossOrigin: true
|
||
|
}).then(res => {
|
||
|
// Clear request reference
|
||
|
this._request = null;
|
||
|
this._processData(res);
|
||
|
}).catch(err => {
|
||
|
console.error(err);
|
||
|
|
||
|
// Clear request reference
|
||
|
this._request = null;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_processData(data) {
|
||
|
console.time('GeoJSON');
|
||
|
|
||
|
var geojson = GeoJSON.mergeFeatures(data, this._options.topojson);
|
||
|
|
||
|
// TODO: Check that GeoJSON is valid / usable
|
||
|
|
||
|
var features = geojson.features;
|
||
|
|
||
|
// Run filter, if provided
|
||
|
if (this._options.filter) {
|
||
|
features = geojson.features.filter(this._options.filter);
|
||
|
}
|
||
|
|
||
|
var style = this._options.style;
|
||
|
|
||
|
var offset;
|
||
|
|
||
|
// TODO: Wrap into a helper method so this isn't duplicated in the tiled
|
||
|
// GeoJSON output layer
|
||
|
//
|
||
|
// Need to be careful as to not make it impossible to fork this off into a
|
||
|
// worker script at a later stage
|
||
|
//
|
||
|
// Also unsure as to whether it's wise to lump so much into a black box
|
||
|
//
|
||
|
// var meshes = GeoJSON.createMeshes(features, offset, style);
|
||
|
|
||
|
var polygons = {
|
||
|
vertices: [],
|
||
|
faces: [],
|
||
|
colours: [],
|
||
|
facesCount: 0,
|
||
|
allFlat: true
|
||
|
};
|
||
|
|
||
|
var lines = {
|
||
|
vertices: [],
|
||
|
colours: [],
|
||
|
verticesCount: 0
|
||
|
};
|
||
|
|
||
|
var colour = new THREE.Color();
|
||
|
|
||
|
features.forEach(feature => {
|
||
|
// feature.geometry, feature.properties
|
||
|
|
||
|
// Skip features that aren't supported
|
||
|
//
|
||
|
// TODO: Add support for all GeoJSON geometry types, including Multi...
|
||
|
// geometry types
|
||
|
if (
|
||
|
feature.geometry.type !== 'Polygon' &&
|
||
|
feature.geometry.type !== 'LineString' &&
|
||
|
feature.geometry.type !== 'MultiLineString'
|
||
|
) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Get style object, if provided
|
||
|
if (typeof this._options.style === 'function') {
|
||
|
style = extend(this._defaultStyle, this._options.style(feature));
|
||
|
}
|
||
|
|
||
|
var coordinates = feature.geometry.coordinates;
|
||
|
|
||
|
// if (feature.geometry.type === 'LineString') {
|
||
|
if (feature.geometry.type === 'LineString') {
|
||
|
colour.set(style.lineColor);
|
||
|
|
||
|
coordinates = coordinates.map(coordinate => {
|
||
|
var latlon = LatLon(coordinate[1], coordinate[0]);
|
||
|
var point = this._world.latLonToPoint(latlon);
|
||
|
|
||
|
if (!offset) {
|
||
|
offset = Point(0, 0);
|
||
|
offset.x = -1 * point.x;
|
||
|
offset.y = -1 * point.y;
|
||
|
|
||
|
this._pointScale = this._world.pointScale(latlon);
|
||
|
}
|
||
|
|
||
|
return [point.x, point.y];
|
||
|
});
|
||
|
|
||
|
var height = 0;
|
||
|
|
||
|
if (style.lineHeight) {
|
||
|
height = this._world.metresToWorld(style.lineHeight, this._pointScale);
|
||
|
}
|
||
|
|
||
|
var linestringAttributes = GeoJSON.lineStringAttributes(coordinates, colour, height);
|
||
|
|
||
|
lines.vertices.push(linestringAttributes.vertices);
|
||
|
lines.colours.push(linestringAttributes.colours);
|
||
|
lines.verticesCount += linestringAttributes.vertices.length;
|
||
|
}
|
||
|
|
||
|
if (feature.geometry.type === 'MultiLineString') {
|
||
|
colour.set(style.lineColor);
|
||
|
|
||
|
coordinates = coordinates.map(_coordinates => {
|
||
|
return _coordinates.map(coordinate => {
|
||
|
var latlon = LatLon(coordinate[1], coordinate[0]);
|
||
|
var point = this._world.latLonToPoint(latlon);
|
||
|
|
||
|
if (!offset) {
|
||
|
offset = Point(0, 0);
|
||
|
offset.x = -1 * point.x;
|
||
|
offset.y = -1 * point.y;
|
||
|
|
||
|
this._pointScale = this._world.pointScale(latlon);
|
||
|
}
|
||
|
|
||
|
return [point.x, point.y];
|
||
|
});
|
||
|
});
|
||
|
|
||
|
var height = 0;
|
||
|
|
||
|
if (style.lineHeight) {
|
||
|
height = this._world.metresToWorld(style.lineHeight, this._pointScale);
|
||
|
}
|
||
|
|
||
|
var multiLinestringAttributes = GeoJSON.multiLineStringAttributes(coordinates, colour, height);
|
||
|
|
||
|
lines.vertices.push(multiLinestringAttributes.vertices);
|
||
|
lines.colours.push(multiLinestringAttributes.colours);
|
||
|
lines.verticesCount += multiLinestringAttributes.vertices.length;
|
||
|
}
|
||
|
|
||
|
if (feature.geometry.type === 'Polygon') {
|
||
|
colour.set(style.color);
|
||
|
|
||
|
coordinates = coordinates.map(ring => {
|
||
|
return ring.map(coordinate => {
|
||
|
var latlon = LatLon(coordinate[1], coordinate[0]);
|
||
|
var point = this._world.latLonToPoint(latlon);
|
||
|
|
||
|
if (!offset) {
|
||
|
offset = Point(0, 0);
|
||
|
offset.x = -1 * point.x;
|
||
|
offset.y = -1 * point.y;
|
||
|
|
||
|
this._pointScale = this._world.pointScale(latlon);
|
||
|
}
|
||
|
|
||
|
return [point.x, point.y];
|
||
|
});
|
||
|
});
|
||
|
|
||
|
var height = 0;
|
||
|
|
||
|
if (style.height) {
|
||
|
height = this._world.metresToWorld(style.height, this._pointScale);
|
||
|
}
|
||
|
|
||
|
var polygonAttributes = GeoJSON.polygonAttributes(coordinates, colour, height);
|
||
|
|
||
|
polygons.vertices.push(polygonAttributes.vertices);
|
||
|
polygons.faces.push(polygonAttributes.faces);
|
||
|
polygons.colours.push(polygonAttributes.colours);
|
||
|
|
||
|
if (polygons.allFlat && !polygonAttributes.flat) {
|
||
|
polygons.allFlat = false;
|
||
|
}
|
||
|
|
||
|
polygons.facesCount += polygonAttributes.faces.length;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
var geometry;
|
||
|
var material;
|
||
|
var mesh;
|
||
|
|
||
|
// Output lines
|
||
|
if (lines.vertices.length > 0) {
|
||
|
geometry = Buffer.createLineGeometry(lines, offset);
|
||
|
|
||
|
material = new THREE.LineBasicMaterial({
|
||
|
vertexColors: THREE.VertexColors,
|
||
|
linewidth: style.lineWidth,
|
||
|
transparent: style.lineTransparent,
|
||
|
opacity: style.lineOpacity,
|
||
|
blending: style.lineBlending
|
||
|
});
|
||
|
|
||
|
mesh = new THREE.LineSegments(geometry, material);
|
||
|
|
||
|
if (style.lineRenderOrder !== undefined) {
|
||
|
material.depthWrite = false;
|
||
|
mesh.renderOrder = style.lineRenderOrder;
|
||
|
}
|
||
|
|
||
|
// TODO: Can a line cast a shadow?
|
||
|
// mesh.castShadow = true;
|
||
|
|
||
|
this.add(mesh);
|
||
|
}
|
||
|
|
||
|
// Output polygons
|
||
|
if (polygons.facesCount > 0) {
|
||
|
geometry = Buffer.createGeometry(polygons, offset);
|
||
|
|
||
|
if (!this._world._environment._skybox) {
|
||
|
material = new THREE.MeshPhongMaterial({
|
||
|
vertexColors: THREE.VertexColors,
|
||
|
side: THREE.BackSide
|
||
|
});
|
||
|
} else {
|
||
|
material = new THREE.MeshStandardMaterial({
|
||
|
vertexColors: THREE.VertexColors,
|
||
|
side: THREE.BackSide
|
||
|
});
|
||
|
material.roughness = 1;
|
||
|
material.metalness = 0.1;
|
||
|
material.envMapIntensity = 3;
|
||
|
material.envMap = this._world._environment._skybox.getRenderTarget();
|
||
|
}
|
||
|
|
||
|
mesh = new THREE.Mesh(geometry, material);
|
||
|
|
||
|
mesh.castShadow = true;
|
||
|
mesh.receiveShadow = true;
|
||
|
|
||
|
if (polygons.allFlat) {
|
||
|
material.depthWrite = false;
|
||
|
mesh.renderOrder = 1;
|
||
|
}
|
||
|
|
||
|
this.add(mesh);
|
||
|
}
|
||
|
|
||
|
// Move layer to origin Point
|
||
|
//
|
||
|
// TODO: Is there a better way to ensure everything is aligned right and
|
||
|
// able to be frustum-culled?
|
||
|
this._layer.position.x = -offset.x;
|
||
|
this._layer.position.z = -offset.y;
|
||
|
|
||
|
console.timeEnd('GeoJSON');
|
||
|
}
|
||
|
|
||
|
_abortRequest() {
|
||
|
if (!this._request) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this._request.abort();
|
||
|
}
|
||
|
|
||
|
destroy() {
|
||
|
// Cancel any pending requests
|
||
|
this._abortRequest();
|
||
|
|
||
|
// Clear request reference
|
||
|
this._request = null;
|
||
|
|
||
|
// Run common destruction logic from parent
|
||
|
super.destroy();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Initialise without requiring new keyword
|
||
|
export default function(geojson, options) {
|
||
|
return new GeoJSONLayer(geojson, options);
|
||
|
};
|