kopia lustrzana https://github.com/robhawkes/vizicities
265 wiersze
7.4 KiB
JavaScript
Executable File
265 wiersze
7.4 KiB
JavaScript
Executable File
/*
|
|
* GeoJSON helpers for handling data and generating objects
|
|
*/
|
|
|
|
import * as THREE from 'three';
|
|
import * as 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)
|
|
//
|
|
// This isn't really valid GeoJSON so perhaps something best left to an external
|
|
// component for now, until a better approach can be considered
|
|
//
|
|
// See: http://lists.geojson.org/pipermail/geojson-geojson.org/2009-June/000489.html
|
|
|
|
// Light and dark colours used for poor-mans AO gradient on object sides
|
|
var light = new THREE.Color(0xffffff);
|
|
var shadow = new THREE.Color(0x666666);
|
|
|
|
var GeoJSON = (function() {
|
|
var defaultStyle = {
|
|
color: '#ffffff',
|
|
outline: false,
|
|
outlineColor: '#000000',
|
|
transparent: false,
|
|
opacity: 1,
|
|
blending: THREE.NormalBlending,
|
|
height: 0,
|
|
lineOpacity: 1,
|
|
lineTransparent: false,
|
|
lineColor: '#ffffff',
|
|
lineWidth: 1,
|
|
lineBlending: THREE.NormalBlending
|
|
};
|
|
|
|
// Attempts to merge together multiple GeoJSON Features or FeatureCollections
|
|
// into a single FeatureCollection
|
|
var collectFeatures = function(data, layers, _topojson) {
|
|
var collections = [];
|
|
|
|
if (_topojson) {
|
|
// If not overridden, merge all features from all objects
|
|
for (var tk in data.objects) {
|
|
if (layers.length > 0) {
|
|
if (!layers.includes(tk)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
collections.push(topojson.feature(data, data.objects[tk]));
|
|
}
|
|
|
|
return geojsonMerge(collections);
|
|
} else {
|
|
// If root doesn't have a type then let's see if there are features in the
|
|
// next step down
|
|
if (!data.type) {
|
|
// TODO: Allow GeoJSON objects to be overridden as an option
|
|
|
|
// If not overridden, merge all features from all objects
|
|
for (var gk in data) {
|
|
if (layers.length > 0) {
|
|
if (!layers.includes(gk)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!data[gk].type) {
|
|
continue;
|
|
}
|
|
|
|
collections.push(data[gk]);
|
|
}
|
|
|
|
return geojsonMerge(collections);
|
|
} else if (Array.isArray(data)) {
|
|
return geojsonMerge(data);
|
|
} else {
|
|
return data;
|
|
}
|
|
}
|
|
};
|
|
|
|
// TODO: This is only used by GeoJSONTile so either roll it into that or
|
|
// update GeoJSONTile to use the new GeoJSONLayer or geometry layers
|
|
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
|
|
};
|
|
};
|
|
|
|
// TODO: This is only used by GeoJSONTile so either roll it into that or
|
|
// update GeoJSONTile to use the new GeoJSONLayer or geometry layers
|
|
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
|
|
};
|
|
};
|
|
|
|
// TODO: This is only used by GeoJSONTile so either roll it into that or
|
|
// update GeoJSONTile to use the new GeoJSONLayer or geometry layers
|
|
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
|
|
};
|
|
};
|
|
|
|
// TODO: This is only used by GeoJSONTile so either roll it into that or
|
|
// update GeoJSONTile to use the new GeoJSONLayer or geometry layers
|
|
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;
|
|
};
|
|
|
|
// TODO: This is only used by GeoJSONTile so either roll it into that or
|
|
// update GeoJSONTile to use the new GeoJSONLayer or geometry layers
|
|
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
|
|
};
|
|
})();
|
|
|
|
export default GeoJSON;
|