@kylebarron/snap-to-tin
Version:
Snap vector features to the faces of a triangulated irregular network (TIN).
177 lines (176 loc) • 7.75 kB
JavaScript
import lineclip from "lineclip";
import { constructRTree } from "./rtree";
import { handlePoint, handleLineString } from "./snap";
export default class SnapFeatures {
constructor(options) {
// Snap arbitrary GeoJSON features
this.snapFeatures = options => {
const { features } = options;
const newFeatures = [];
for (const feature of features) {
const geometryType = feature.geometry.type;
if (geometryType === "Point") {
const coord = feature.geometry.coordinates;
const newCoord = this._handlePoint(coord);
if (!newCoord)
continue;
feature.geometry.coordinates = newCoord;
newFeatures.push(feature);
}
else if (geometryType === "MultiPoint") {
const newCoords = [];
for (const point of feature.geometry.coordinates) {
const newPoint = this._handlePoint(point);
if (newPoint)
newCoords.push(newPoint);
}
feature.geometry.coordinates = newCoords;
newFeatures.push(feature);
}
else if (geometryType === "LineString") {
// An array of one or more LineStrings
const newLines = this._handleLine(feature.geometry.coordinates);
if (!newLines)
continue;
// Single LineString
if (newLines.length === 1) {
feature.geometry.coordinates = newLines[0];
}
else {
feature.geometry.type = "MultiLineString";
feature.geometry.coordinates = newLines;
}
newFeatures.push(feature);
}
else if (geometryType === "MultiLineString") {
const newCoords = [];
for (const line of feature.geometry.coordinates) {
const newLines = this._handleLine(line);
if (!newLines)
continue;
// Single LineString
if (newLines.length === 1) {
newCoords.push(newLines[0]);
}
else {
newCoords.push.apply(newLines);
}
}
feature.geometry.coordinates = newCoords;
newFeatures.push(feature);
}
}
return newFeatures;
};
this._handlePoint = (coord) => {
if (this.bounds && this.bounds.length === 4) {
// Make sure coordinate is within bounds
if (coord[0] < this.bounds[0] ||
coord[0] > this.bounds[2] ||
coord[1] < this.bounds[1] ||
coord[1] > this.bounds[3]) {
return;
}
}
return handlePoint(coord, this.index, this.triangles);
};
this._handleLine = (coords) => {
// Clip line to box
let clippedLine = [coords];
if (this.bounds && this.bounds.length === 4) {
clippedLine = lineclip(coords, this.bounds);
if (clippedLine.length === 0)
return;
}
const newLineSegments = [];
for (const lineSegment of clippedLine) {
newLineSegments.push(handleLineString(lineSegment, this.index, this.triangles));
}
return newLineSegments;
};
// Snap typedArray of points
this.snapPoints = options => {
const { positions, coordLength = 2, featureIds } = options;
const newPoints = new Float32Array((positions.length / coordLength) * 3);
const newFeatureIds = new Uint32Array((featureIds && featureIds.length) || 0);
let pointIndex = 0;
// Iterate over vertex index
for (let i = 0; i < positions.length / coordLength; i++) {
const coord = positions.subarray(i * coordLength, (i + 1) * coordLength);
const newPoint = this._handlePoint(coord);
if (newPoint) {
newPoints.set(newPoint, pointIndex * 3);
if (featureIds) {
newFeatureIds[pointIndex] = featureIds[i];
}
pointIndex++;
}
}
// Filter array to size of filled points
return {
positions: newPoints.subarray(0, pointIndex * 3),
featureIds: featureIds
};
};
// Snap typedArray of lines
this.snapLines = options => {
const { positions, pathIndices, coordLength = 2, featureIds } = options;
const newLines = [];
const newFeatureIds = [];
// Loop over each LineString, as defined by pathIndices
const loopIndices = pathIndices ? pathIndices : [0, positions.length];
for (let i = 0; i < loopIndices.length - 1; i++) {
const positionStartIndex = loopIndices[i];
const positionEndIndex = loopIndices[i + 1];
// Make array of coordinates
const line = [];
for (let j = positionStartIndex; j < positionEndIndex; j++) {
line.push(positions.subarray(j * coordLength, (j + 1) * coordLength));
}
const newLineSegments = this._handleLine(line);
if (!newLineSegments)
continue;
const objectId = featureIds && featureIds[i];
for (const newLineSegment of newLineSegments) {
newLines.push(newLineSegment);
if (objectId)
newFeatureIds.push(objectId);
}
}
// Create binary arrays
const newPositions = [];
const newPathIndices = [];
const newNewFeatureIds = [];
let positionIndex = 0;
for (let i = 0; i < newLines.length; i++) {
const line = newLines[i];
newPositions.push.apply(line);
newPathIndices.push(positionIndex);
if (featureIds) {
for (let j = 0; j < line.length; j++) {
newNewFeatureIds.push(newFeatureIds[i]);
}
}
positionIndex += line.length;
}
// Backfill last index
newPathIndices.push(newPositions.length);
return {
positions: Float32Array.from(newPositions),
pathIndices: Uint32Array.from(newPathIndices),
featureIds: Uint32Array.from(newNewFeatureIds)
};
};
const { indices, positions, bounds = [-Infinity, -Infinity, Infinity, Infinity] } = options;
const { index, triangles } = constructRTree(indices, positions);
this.index = index;
this.triangles = triangles;
// Intersection of provided bounds and rtree bounds
this.bounds = [
Math.max(bounds[0], index.minX),
Math.max(bounds[1], index.minY),
Math.min(bounds[2], index.maxX),
Math.min(bounds[3], index.maxY)
];
}
}