UNPKG

@kylebarron/snap-to-tin

Version:

Snap vector features to the faces of a triangulated irregular network (TIN).

177 lines (176 loc) 7.75 kB
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) ]; } }