Refined GeoJSON and geometry layers

master
Robin Hawkes 2016-03-09 12:26:41 +00:00
rodzic f6b3fa86fd
commit b9bca78d32
8 zmienionych plików z 1812 dodań i 1994 usunięć

3385
dist/vizicities.js vendored

Plik diff jest za duży Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -5,11 +5,11 @@ var world = VIZI.world('world', {
// Add controls
VIZI.Controls.orbit().addTo(world);
// // http://{s}.tile.osm.org/{z}/{x}/{y}.png
// // http://{s}.tiles.wmflabs.org/osm-no-labels/{z}/{x}/{y}.png
// var imageTileLayer = VIZI.imageTileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {
// attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
// }).addTo(world);
// http://{s}.tile.osm.org/{z}/{x}/{y}.png
// http://{s}.tiles.wmflabs.org/osm-no-labels/{z}/{x}/{y}.png
var imageTileLayer = VIZI.imageTileLayer('http://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="http://cartodb.com/attributions">CartoDB</a>'
}).addTo(world);
// var layer = VIZI.geoJSONLayer('http://vector.mapzen.com/osm/buildings,roads/13/4088/2722.json', {
// output: true,
@ -24,14 +24,19 @@ VIZI.Controls.orbit().addTo(world);
// }
// }).addTo(world);
var layer = VIZI.pointLayer([-0.09, 51.505], {
interactive: true
var layer = VIZI.geoJSONLayer('http://vector.mapzen.com/osm/pois/13/4088/2722.json', {
output: true,
interactive: true,
style: {
pointColor: '#ff0000'
},
onEachFeature: function(feature, layer) {
layer.on('click', function(layer, point2d, point3d, intersects) {
console.log(layer, point2d, point3d, intersects);
});
}
}).addTo(world);
layer.on('click', function(layer, point2d, point3d, intersects) {
console.log(layer, point2d, point3d, intersects);
});
// // 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', {
// style: function(feature) {
@ -82,28 +87,29 @@ layer.on('click', function(layer, point2d, point3d, intersects) {
// }).addTo(world);
// London Underground lines
// var geoJSONLayer = VIZI.geoJSONLayer('https://rawgit.com/robhawkes/4acb9d6a6a5f00a377e2/raw/30ae704a44e10f2e13fb7e956e80c3b22e8e7e81/tfl_lines.json', {
// interactive: true,
// style: function(feature) {
// var colour = feature.properties.lines[0].colour || '#ffffff';
//
// return {
// lineColor: colour,
// // lineHeight: 20,
// lineWidth: 3,
// // lineTransparent: true,
// // lineOpacity: 0.5,
// // lineBlending: THREE.AdditiveBlending,
// lineRenderOrder: 2
// };
// },
// onEachFeature: function(feature, layer) {
// layer.on('click', function(layer, point2d, point3d, intersects) {
// console.log(layer, point2d, point3d, intersects);
// });
// },
// attribution: '&copy; Transport for London.'
// }).addTo(world);
var geoJSONLayer = VIZI.geoJSONLayer('https://rawgit.com/robhawkes/4acb9d6a6a5f00a377e2/raw/30ae704a44e10f2e13fb7e956e80c3b22e8e7e81/tfl_lines.json', {
output: true,
interactive: true,
style: function(feature) {
var colour = feature.properties.lines[0].colour || '#ffffff';
return {
lineColor: colour,
// lineHeight: 20,
lineWidth: 3,
// lineTransparent: true,
// lineOpacity: 0.5,
// lineBlending: THREE.AdditiveBlending,
lineRenderOrder: 2
};
},
onEachFeature: function(feature, layer) {
layer.on('click', function(layer, point2d, point3d, intersects) {
console.log(layer, point2d, point3d, intersects);
});
},
attribution: '&copy; Transport for London.'
}).addTo(world);
// Set up render debug stats
var rendererStats = new THREEx.RendererStats();

Wyświetl plik

@ -6,6 +6,7 @@ import Buffer from '../util/Buffer';
import PickingMaterial from '../engine/PickingMaterial';
import PolygonLayer from './geometry/PolygonLayer';
import PolylineLayer from './geometry/PolylineLayer';
import PointLayer from './geometry/PointLayer';
class GeoJSONLayer extends LayerGroup {
constructor(geojson, options) {
@ -76,15 +77,15 @@ class GeoJSONLayer extends LayerGroup {
// Collects features into a single FeatureCollection
//
// Also converts TopoJSON to GeoJSON if instructed
var geojson = GeoJSON.collectFeatures(data, this._options.topojson);
this._geojson = GeoJSON.collectFeatures(data, this._options.topojson);
// TODO: Check that GeoJSON is valid / usable
var features = geojson.features;
var features = this._geojson.features;
// Run filter, if provided
if (this._options.filter) {
features = geojson.features.filter(this._options.filter);
features = this._geojson.features.filter(this._options.filter);
}
var defaults = {};
@ -96,7 +97,7 @@ class GeoJSONLayer extends LayerGroup {
features.forEach(feature => {
// Get per-feature style object, if provided
if (typeof this._options.style === 'function') {
style = extend(GeoJSON.defaultStyle, this._options.style(feature));
style = extend({}, GeoJSON.defaultStyle, this._options.style(feature));
}
options = extend({}, defaults, {
@ -137,6 +138,7 @@ class GeoJSONLayer extends LayerGroup {
var polygonFlat = true;
var polylineAttributes = [];
var pointAttributes = [];
this._layers.forEach(layer => {
if (layer instanceof PolygonLayer) {
@ -147,23 +149,36 @@ class GeoJSONLayer extends LayerGroup {
}
} else if (layer instanceof PolylineLayer) {
polylineAttributes.push(layer.getBufferAttributes());
} else if (layer instanceof PointLayer) {
pointAttributes.push(layer.getBufferAttributes());
}
});
var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes);
var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes);
if (polygonAttributes.length > 0) {
var mergedPolygonAttributes = Buffer.mergeAttributes(polygonAttributes);
this._setPolygonMesh(mergedPolygonAttributes, polygonFlat);
this.add(this._polygonMesh);
}
this._setPolygonMesh(mergedPolygonAttributes, polygonFlat);
this.add(this._polygonMesh);
if (polylineAttributes.length > 0) {
var mergedPolylineAttributes = Buffer.mergeAttributes(polylineAttributes);
this._setPolylineMesh(mergedPolylineAttributes);
this.add(this._polylineMesh);
}
this._setPolylineMesh(mergedPolylineAttributes);
this.add(this._polylineMesh);
if (pointAttributes.length > 0) {
var mergedPointAttributes = Buffer.mergeAttributes(pointAttributes);
this._setPointMesh(mergedPointAttributes);
this.add(this._pointMesh);
}
}
// Create and store mesh from buffer attributes
//
// TODO: De-dupe this from the individual mesh creation logic within each
// geometry layer (materials, settings, etc)
//
// Could make this an abstract method for each geometry layer
_setPolygonMesh(attributes, flat) {
var geometry = new THREE.BufferGeometry();
@ -230,7 +245,9 @@ class GeoJSONLayer extends LayerGroup {
geometry.computeBoundingBox();
// TODO: Make this work when style is a function per feature
var style = this._options.style;
var style = (typeof this._options.style === 'function') ? this._options.style(this._geojson.features[0]) : this._options.style;
style = extend({}, GeoJSON.defaultStyle, style);
var material = new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors,
linewidth: style.lineWidth,
@ -246,12 +263,12 @@ class GeoJSONLayer extends LayerGroup {
mesh.renderOrder = style.lineRenderOrder;
}
// TODO: Can a line cast a shadow?
// mesh.castShadow = true;
mesh.castShadow = true;
// mesh.receiveShadow = true;
if (this._options.interactive && this._pickingMesh) {
material = new PickingMaterial();
material.side = THREE.BackSide;
// material.side = THREE.BackSide;
// Make the line wider / easier to pick
material.linewidth = style.lineWidth + material.linePadding;
@ -263,6 +280,53 @@ class GeoJSONLayer extends LayerGroup {
this._polylineMesh = mesh;
}
_setPointMesh(attributes) {
var geometry = new THREE.BufferGeometry();
// itemSize = 3 because there are 3 values (components) per vertex
geometry.addAttribute('position', new THREE.BufferAttribute(attributes.vertices, 3));
geometry.addAttribute('normal', new THREE.BufferAttribute(attributes.normals, 3));
geometry.addAttribute('color', new THREE.BufferAttribute(attributes.colours, 3));
if (attributes.pickingIds) {
geometry.addAttribute('pickingId', new THREE.BufferAttribute(attributes.pickingIds, 1));
}
geometry.computeBoundingBox();
var material;
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 (this._options.interactive && this._pickingMesh) {
material = new PickingMaterial();
// material.side = THREE.BackSide;
var pickingMesh = new THREE.Mesh(geometry, material);
this._pickingMesh.add(pickingMesh);
}
this._pointMesh = mesh;
}
// TODO: Support all GeoJSON geometry types
_featureToLayer(feature, options) {
var geometry = feature.geometry;
@ -279,6 +343,10 @@ class GeoJSONLayer extends LayerGroup {
if (geometry.type === 'LineString' || geometry.type === 'MultiLineString') {
return new PolylineLayer(coordinates, options);
}
if (geometry.type === 'Point' || geometry.type === 'MultiPoint') {
return new PointLayer(coordinates, options);
}
}
_abortRequest() {

Wyświetl plik

@ -10,6 +10,10 @@
// Can probably use fromGeometry() or setFromObject() from THREE.BufferGeometry
// and pull out the attributes
// TODO: Support sprite objects using textures
// TODO: Provide option to billboard geometry so it always faces the camera
import Layer from '../Layer';
import extend from 'lodash.assign';
import THREE from 'three';
@ -23,10 +27,13 @@ class PointLayer extends Layer {
var defaults = {
output: true,
interactive: false,
// THREE.Geometry or THREE.BufferGeometry to use for point output
//
// TODO: Make this customisable per point via a callback (like style)
geometry: null,
// This default style is separate to Util.GeoJSON.defaultStyle
style: {
pointColor: '#ff0000',
pointHeight: 0
pointColor: '#ff0000'
}
};
@ -115,15 +122,26 @@ class PointLayer extends Layer {
var colour = new THREE.Color();
colour.set(this._options.style.pointColor);
// Debug geometry for points
//
// TODO: Allow point geometry to be customised / overridden
var debugGeomWidth = this._world.metresToWorld(5, this._pointScale);
var debugGeomHeight = this._world.metresToWorld(100, this._pointScale);
var debugGeom = new THREE.BoxGeometry(debugGeomWidth, debugGeomHeight, debugGeomWidth);
var geometry;
// Pull attributes out of debug geometry
var debugBufferGeom = new THREE.BufferGeometry().fromGeometry(debugGeom);
// Use default geometry if none has been provided or the provided geometry
// isn't valid
if (!this._options.geometry || (!this._options.geometry instanceof THREE.Geometry || !this._options.geometry instanceof THREE.BufferGeometry)) {
// Debug geometry for points is a thin bar
//
// TODO: Allow point geometry to be customised / overridden
var geometryWidth = this._world.metresToWorld(5, this._pointScale);
var geometryHeight = this._world.metresToWorld(200, this._pointScale);
var _geometry = new THREE.BoxGeometry(geometryWidth, geometryHeight, geometryWidth);
// Shift geometry up so it sits on the ground
_geometry.translate(0, geometryHeight * 0.5, 0);
// Pull attributes out of debug geometry
geometry = new THREE.BufferGeometry().fromGeometry(_geometry);
} else {
geometry = this._options.geometry;
}
// For each point
var attributes = this._projectedCoordinates.map(coordinate => {
@ -131,13 +149,13 @@ class PointLayer extends Layer {
var _normals = [];
var _colours = [];
var _debugBufferGeom = debugBufferGeom.clone();
var _geometry = geometry.clone();
_debugBufferGeom.translate(coordinate.x, height, coordinate.y);
_geometry.translate(coordinate.x, height, coordinate.y);
var _vertices = _debugBufferGeom.attributes.position.clone().array;
var _normals = _debugBufferGeom.attributes.normal.clone().array;
var _colours = _debugBufferGeom.attributes.color.clone().array;
var _vertices = _geometry.attributes.position.clone().array;
var _normals = _geometry.attributes.normal.clone().array;
var _colours = _geometry.attributes.color.clone().array;
for (var i = 0; i < _colours.length; i += 3) {
_colours[i] = colour.r;
@ -145,26 +163,10 @@ class PointLayer extends Layer {
_colours[i + 2] = colour.b;
}
// Connect coordinate with the next to make a pair
//
// LineSegments requires pairs of vertices so repeat the last point if
// there's an odd number of vertices
// var nextCoord;
// _projectedCoordinates.forEach((coordinate, index) => {
// _colours.push([colour.r, colour.g, colour.b]);
// _vertices.push([coordinate.x, height, coordinate.y]);
//
// nextCoord = (_projectedCoordinates[index + 1]) ? _projectedCoordinates[index + 1] : coordinate;
//
// _colours.push([colour.r, colour.g, colour.b]);
// _vertices.push([nextCoord.x, height, nextCoord.y]);
// });
var _point = {
vertices: _vertices,
normals: _normals,
colours: _colours
// verticesCount: _vertices.length
};
if (this._options.interactive && this._pickingId) {
@ -225,7 +227,7 @@ class PointLayer extends Layer {
var mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
// mesh.receiveShadow = true;
if (this.isFlat()) {
material.depthWrite = false;

Wyświetl plik

@ -107,7 +107,7 @@ class PolylineLayer extends Layer {
}
var colour = new THREE.Color();
colour.set(this._options.style.color);
colour.set(this._options.style.lineColor);
// For each line
var attributes = this._projectedCoordinates.map(_projectedCoordinates => {
@ -183,8 +183,8 @@ class PolylineLayer extends Layer {
mesh.renderOrder = style.lineRenderOrder;
}
// TODO: Can a line cast a shadow?
// mesh.castShadow = true;
mesh.castShadow = true;
// mesh.receiveShadow = true;
if (this._options.interactive && this._pickingMesh) {
material = new PickingMaterial();

Wyświetl plik

@ -5,8 +5,6 @@
import THREE from 'three';
import topojson from 'topojson';
import geojsonMerge from 'geojson-merge';
import earcut from 'earcut';
import extrudePolygon from './extrudePolygon';
// TODO: Make it so height can be per-coordinate / point but connected together
// as a linestring (eg. GPS points with an elevation at each point)
@ -69,170 +67,9 @@ var GeoJSON = (function() {
}
};
var lineStringAttributes = function(coordinates, colour, height) {
var _coords = [];
var _colours = [];
var nextCoord;
// Connect coordinate with the next to make a pair
//
// LineSegments requires pairs of vertices so repeat the last point if
// there's an odd number of vertices
coordinates.forEach((coordinate, index) => {
_colours.push([colour.r, colour.g, colour.b]);
_coords.push([coordinate[0], height, coordinate[1]]);
nextCoord = (coordinates[index + 1]) ? coordinates[index + 1] : coordinate;
_colours.push([colour.r, colour.g, colour.b]);
_coords.push([nextCoord[0], height, nextCoord[1]]);
});
return {
vertices: _coords,
colours: _colours
};
};
var multiLineStringAttributes = function(coordinates, colour, height) {
var _coords = [];
var _colours = [];
var result;
coordinates.forEach(coordinate => {
result = lineStringAttributes(coordinate, colour, height);
result.vertices.forEach(coord => {
_coords.push(coord);
});
result.colours.forEach(colour => {
_colours.push(colour);
});
});
return {
vertices: _coords,
colours: _colours
};
};
var polygonAttributes = function(coordinates, colour, height) {
var earcutData = _toEarcut(coordinates);
var faces = _triangulate(earcutData.vertices, earcutData.holes, earcutData.dimensions);
var groupedVertices = [];
for (i = 0, il = earcutData.vertices.length; i < il; i += earcutData.dimensions) {
groupedVertices.push(earcutData.vertices.slice(i, i + earcutData.dimensions));
}
var extruded = extrudePolygon(groupedVertices, faces, {
bottom: 0,
top: height
});
var topColor = colour.clone().multiply(light);
var bottomColor = colour.clone().multiply(shadow);
var _vertices = extruded.positions;
var _faces = [];
var _colours = [];
var _colour;
extruded.top.forEach((face, fi) => {
_colour = [];
_colour.push([colour.r, colour.g, colour.b]);
_colour.push([colour.r, colour.g, colour.b]);
_colour.push([colour.r, colour.g, colour.b]);
_faces.push(face);
_colours.push(_colour);
});
var allFlat = true;
if (extruded.sides) {
if (allFlat) {
allFlat = false;
}
// Set up colours for every vertex with poor-mans AO on the sides
extruded.sides.forEach((face, fi) => {
_colour = [];
// First face is always bottom-bottom-top
if (fi % 2 === 0) {
_colour.push([bottomColor.r, bottomColor.g, bottomColor.b]);
_colour.push([bottomColor.r, bottomColor.g, bottomColor.b]);
_colour.push([topColor.r, topColor.g, topColor.b]);
// Reverse winding for the second face
// top-top-bottom
} else {
_colour.push([topColor.r, topColor.g, topColor.b]);
_colour.push([topColor.r, topColor.g, topColor.b]);
_colour.push([bottomColor.r, bottomColor.g, bottomColor.b]);
}
_faces.push(face);
_colours.push(_colour);
});
}
// Skip bottom as there's no point rendering it
// allFaces.push(extruded.faces);
return {
vertices: _vertices,
faces: _faces,
colours: _colours,
flat: allFlat
};
};
var _toEarcut = function(data) {
var dim = data[0][0].length;
var result = {vertices: [], holes: [], dimensions: dim};
var holeIndex = 0;
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < data[i].length; j++) {
for (var d = 0; d < dim; d++) {
result.vertices.push(data[i][j][d]);
}
}
if (i > 0) {
holeIndex += data[i - 1].length;
result.holes.push(holeIndex);
}
}
return result;
};
var _triangulate = function(contour, holes, dim) {
// console.time('earcut');
var faces = earcut(contour, holes, dim);
var result = [];
for (i = 0, il = faces.length; i < il; i += 3) {
result.push(faces.slice(i, i + 3));
}
// console.timeEnd('earcut');
return result;
};
return {
defaultStyle: defaultStyle,
collectFeatures: collectFeatures,
lineStringAttributes: lineStringAttributes,
multiLineStringAttributes: multiLineStringAttributes,
polygonAttributes: polygonAttributes
collectFeatures: collectFeatures
};
})();