UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

156 lines (150 loc) 5.91 kB
import { FEATURE_TYPES } from "../Core/Feature.js"; import { Coordinates } from '@itowns/geographic'; function pointIsOverLine(point, linePoints, epsilon, offset, count, size) { const x0 = point.x; const y0 = point.y; // for each segment of the line (j is i -1) for (let i = offset + size, j = offset; i < offset + count; j = i, i += size) { /* ********************************************************** norm : norm of vector P1P2 distance : distance point P0 to line P1P2 scalar : dot product of P1P0 and P1P2 divide by norm, it represents the projection of P0 on the line Point is over segment P1P2 if : * if the distance, , is inferior to epsilon * and if : -epsilon ≤ scalar ≤ (||P1P2|| + epsilon) + (P0) _ | | | | <---scalar---->| distance | | | v +-------------------------------+ (P1) (P2) *********************************************************** */ const x1 = linePoints[i]; const y1 = linePoints[i + 1]; const x2 = linePoints[j]; const y2 = linePoints[j + 1]; const x21 = x2 - x1; const y21 = y2 - y1; const norm = Math.sqrt(x21 * x21 + y21 * y21); const scalar = ((x0 - x1) * x21 + (y0 - y1) * y21) / norm; if (scalar >= -epsilon && scalar <= norm + epsilon) { const distance = Math.abs(y21 * x0 - x21 * y0 + x2 * y1 - y2 * x1) / norm; if (distance <= epsilon) { return true; } } } return false; } function getClosestPoint(point, points, epsilon, offset, count, size) { const x0 = point.x; const y0 = point.y; let squaredEpsilon = epsilon * epsilon; let closestPoint; for (let i = offset; i < offset + count; i += size) { const x1 = points[i]; const y1 = points[i + 1]; const xP = x0 - x1; const yP = y0 - y1; const n = xP * xP + yP * yP; if (n < squaredEpsilon) { closestPoint = [points[i], points[i + 1]]; squaredEpsilon = n; } } return closestPoint; } function pointIsInsidePolygon(point, polygonPoints, epsilon, offset, count, size) { // ray-casting algorithm based on // http://wrf.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html const x = point.x; const y = point.y; let inside = false; // in first j is last point of polygon // for each segment of the polygon (j is i -1) for (let i = offset, j = offset + count - size; i < offset + count; j = i, i += size) { const xi = polygonPoints[i]; const yi = polygonPoints[i + 1]; const xj = polygonPoints[j]; const yj = polygonPoints[j + 1]; if (pointIsOverLine(point, [xi, yi, xj, yj], epsilon, 0, 4, 2)) { return true; } // isIntersect semi-infinite ray horizontally with polygon's edge if (yi > y != yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) { inside = !inside; } } return inside; } function isFeatureSingleGeometryUnderCoordinate(coordinate, type, coordinates, epsilon, offset, count, size) { if (type == FEATURE_TYPES.LINE && pointIsOverLine(coordinate, coordinates, epsilon, offset, count, size)) { return true; } else if (type == FEATURE_TYPES.POLYGON && pointIsInsidePolygon(coordinate, coordinates, epsilon, offset, count, size)) { return true; } else if (type == FEATURE_TYPES.POINT) { const closestPoint = getClosestPoint(coordinate, coordinates, epsilon, offset, count, size); if (closestPoint) { return { coordinates: closestPoint }; } } } function isFeatureUnderCoordinate(coordinate, feature, epsilon, result) { const featCoord = coordinate.as(feature.crs); for (const geometry of feature.geometries) { if (geometry.extent == undefined || geometry.extent.isPointInside(featCoord, epsilon)) { const offset = geometry.indices[0].offset * feature.size; const count = geometry.indices[0].count * feature.size; const under = isFeatureSingleGeometryUnderCoordinate(featCoord, feature.type, feature.vertices, epsilon, offset, count, feature.size); if (under) { result.push({ type: feature.type, geometry, coordinates: under.coordinates /* || coordinates */, style: feature.style }); } } } } const coord = new Coordinates('EPSG:4326', 0, 0, 0); export default { /** * Filter from a list of features, features that are under a coordinate. * * @param {Coordinates} coordinate - The coordinate for the filter * condition. * @param {Feature|FeatureCollection} collection - A single feature or a * collection of them, to filter given the previous coordinate. * @param {number} [epsilon=0.1] Tolerance around the coordinate (in * coordinate's unit). * * @return {Feature[]} Array of filtered features. */ filterFeaturesUnderCoordinate(coordinate, collection) { let epsilon = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0.1; const result = []; // transform coordinates to collection system projection coordinate.as(collection.crs, coord); // transform coordinates to local system coord.applyMatrix4(collection.matrixWorldInverse); if (collection.extent.isPointInside(coord, epsilon)) { if (collection.isFeatureCollection) { epsilon *= Math.sqrt(collection.scale.x ** 2 + collection.scale.y ** 2); for (const feature of collection.features) { if (feature.extent && !feature.extent.isPointInside(coord, epsilon)) { continue; } isFeatureUnderCoordinate(coord, feature, epsilon, result); } } else if (collection.geometries) { isFeatureUnderCoordinate(coord, collection, epsilon, result); } } return result; } };