UNPKG

sharedstreets

Version:

SharedStreets, a 'digital commons' for the street

369 lines (368 loc) 19 kB
'use strict'; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PointMatcher = exports.PointCandidate = exports.ReferenceSideOfStreet = exports.ReferenceDirection = void 0; const turfHelpers = __importStar(require("@turf/helpers")); const along_1 = __importDefault(require("@turf/along")); const bearing_1 = __importDefault(require("@turf/bearing")); const distance_1 = __importDefault(require("@turf/distance")); const nearest_point_on_line_1 = __importDefault(require("@turf/nearest-point-on-line")); const tile_index_1 = require("./tile_index"); const tiles_1 = require("./tiles"); // import { start } from 'repl'; // import { normalize } from 'path'; const DEFAULT_SEARCH_RADIUS = 25; const DEFAULT_LENGTH_TOLERANCE = 0.1; const DEFUALT_CANDIDATES = 10; const DEFAULT_BEARING_TOLERANCE = 15; // 360 +/- tolerance const MAX_FEATURE_LENGTH = 15000; // 1km const MAX_SEARCH_RADIUS = 100; const MAX_LENGTH_TOLERANCE = 0.5; const MAX_CANDIDATES = 10; const MAX_BEARING_TOLERANCE = 180; // 360 +/- tolerance const REFERNECE_GEOMETRY_OFFSET = 2; const MAX_ROUTE_QUERIES = 16; // TODO need to pull this from PBF enum defintion // @property {number} Motorway=0 Motorway value // * @property {number} Trunk=1 Trunk value // * @property {number} Primary=2 Primary value // * @property {number} Secondary=3 Secondary value // * @property {number} Tertiary=4 Tertiary value // * @property {number} Residential=5 Residential value // * @property {number} Unclassified=6 Unclassified value // * @property {number} Service=7 Service value // * @property {number} Other=8 Other value function roadClassConverter(roadClass) { if (roadClass === 'Motorway') return 0; else if (roadClass === 'Trunk') return 1; else if (roadClass === 'Primary') return 2; else if (roadClass === 'Secondary') return 3; else if (roadClass === 'Tertiary') return 4; else if (roadClass === 'Residential') return 5; else if (roadClass === 'Unclassified') return 6; else if (roadClass === 'Service') return 7; else return null; } function angleDelta(a1, a2) { var delta = 180 - Math.abs(Math.abs(a1 - a2) - 180); return delta; } function normalizeAngle(a) { if (a < 0) return a + 360; return a; } var ReferenceDirection; (function (ReferenceDirection) { ReferenceDirection["FORWARD"] = "forward"; ReferenceDirection["BACKWARD"] = "backward"; })(ReferenceDirection = exports.ReferenceDirection || (exports.ReferenceDirection = {})); var ReferenceSideOfStreet; (function (ReferenceSideOfStreet) { ReferenceSideOfStreet["RIGHT"] = "right"; ReferenceSideOfStreet["LEFT"] = "left"; ReferenceSideOfStreet["UNKNOWN"] = "unknown"; })(ReferenceSideOfStreet = exports.ReferenceSideOfStreet || (exports.ReferenceSideOfStreet = {})); class PointCandidate { calcScore() { if (!this.score) { // score for snapped points are average of distance to point on line distance and distance to snapped ponit if (this.snappedPoint) this.score = (this.pointOnLine.properties.dist + distance_1.default(this.searchPoint, this.snappedPoint, { units: 'meters' })) / 2; else this.score = this.pointOnLine.properties.dist; } return this.score; } toFeature() { this.calcScore(); var feature = turfHelpers.feature(this.pointOnLine.geometry, { score: this.score, location: this.location, referenceLength: this.referenceLength, geometryId: this.geometryId, referenceId: this.referenceId, direction: this.direction, bearing: this.bearing, sideOfStreet: this.sideOfStreet, interceptAngle: this.interceptAngle }); return feature; } } exports.PointCandidate = PointCandidate; class PointMatcher { constructor(extent = null, params, existingTileIndex = null) { this.searchRadius = DEFAULT_SEARCH_RADIUS; this.bearingTolerance = DEFAULT_BEARING_TOLERANCE; this.lengthTolerance = DEFAULT_LENGTH_TOLERANCE; this.includeIntersections = false; this.includeStreetnames = false; this.ignoreDirection = false; this.snapToIntersections = false; this.snapTopology = false; this.snapSideOfStreet = ReferenceSideOfStreet.UNKNOWN; this.tileParams = params; if (existingTileIndex) this.tileIndex = existingTileIndex; else this.tileIndex = new tile_index_1.TileIndex(); } directionForRefId(refId) { var ref = this.tileIndex.objectIndex.get(refId); if (ref) { var geom = this.tileIndex.objectIndex.get(ref['geometryId']); if (geom) { if (geom['forwardReferenceId'] === ref['id']) return ReferenceDirection.FORWARD; else if (geom['backReferenceId'] === ref['id']) return ReferenceDirection.BACKWARD; } } return null; } toIntersectionIdForRefId(refId) { var ref = this.tileIndex.objectIndex.get(refId); return ref.locationReferences[ref.locationReferences.length - 1].intersectionId; } fromIntersectionIdForRefId(refId) { var ref = this.tileIndex.objectIndex.get(refId); return ref.locationReferences[0].intersectionId; } getPointCandidateFromRefId(searchPoint, refId, searchBearing) { return __awaiter(this, void 0, void 0, function* () { var reference = this.tileIndex.objectIndex.get(refId); var geometry = this.tileIndex.objectIndex.get(reference.geometryId); var geometryFeature = this.tileIndex.featureIndex.get(reference.geometryId); var direction = ReferenceDirection.FORWARD; if (geometry.backReferenceId && geometry.backReferenceId === refId) direction = ReferenceDirection.BACKWARD; var pointOnLine = nearest_point_on_line_1.default(geometryFeature, searchPoint, { units: 'meters' }); if (pointOnLine.properties.dist < this.searchRadius) { var refLength = 0; for (var lr of reference.locationReferences) { if (lr.distanceToNextRef) refLength = refLength + (lr.distanceToNextRef / 100); } var interceptBearing = normalizeAngle(bearing_1.default(pointOnLine, searchPoint)); var i = pointOnLine.properties.index; if (geometryFeature.geometry.coordinates.length <= i + 1) i = i - 1; var lineBearing = bearing_1.default(geometryFeature.geometry.coordinates[i], geometryFeature.geometry.coordinates[i + 1]); if (direction === ReferenceDirection.BACKWARD) lineBearing += 180; lineBearing = normalizeAngle(lineBearing); var pointCandidate = new PointCandidate(); pointCandidate.searchPoint = searchPoint; pointCandidate.pointOnLine = pointOnLine; pointCandidate.geometryId = geometryFeature.properties.id; pointCandidate.referenceId = reference.id; pointCandidate.roadClass = geometry.roadClass; // if(this.includeStreetnames) { // var metadata = await this.cache.metadataById(pointCandidate.geometryId); // pointCandidate.streetname = metadata.name; // } pointCandidate.direction = direction; pointCandidate.referenceLength = refLength; if (direction === ReferenceDirection.FORWARD) pointCandidate.location = pointOnLine.properties.location; else pointCandidate.location = refLength - pointOnLine.properties.location; pointCandidate.bearing = normalizeAngle(lineBearing); pointCandidate.interceptAngle = normalizeAngle(interceptBearing - lineBearing); pointCandidate.sideOfStreet = ReferenceSideOfStreet.UNKNOWN; if (pointCandidate.interceptAngle < 180) { pointCandidate.sideOfStreet = ReferenceSideOfStreet.RIGHT; } if (pointCandidate.interceptAngle > 180) { pointCandidate.sideOfStreet = ReferenceSideOfStreet.LEFT; } if (geometry.backReferenceId) pointCandidate.oneway = false; else pointCandidate.oneway = true; // check bearing and add to candidate list if (!searchBearing || angleDelta(searchBearing, lineBearing) < this.bearingTolerance) return pointCandidate; } return null; }); } getPointCandidateFromGeom(searchPoint, pointOnLine, candidateGeom, candidateGeomFeature, searchBearing, direction) { if (pointOnLine.properties.dist < this.searchRadius) { var reference; if (direction === ReferenceDirection.FORWARD) { reference = this.tileIndex.objectIndex.get(candidateGeom.forwardReferenceId); } else { if (candidateGeom.backReferenceId) reference = this.tileIndex.objectIndex.get(candidateGeom.backReferenceId); else return null; // no back-reference } var refLength = 0; for (var lr of reference.locationReferences) { if (lr.distanceToNextRef) refLength = refLength + (lr.distanceToNextRef / 100); } var interceptBearing = normalizeAngle(bearing_1.default(pointOnLine, searchPoint)); var i = pointOnLine.properties.index; if (candidateGeomFeature.geometry.coordinates.length <= i + 1) i = i - 1; var lineBearing = bearing_1.default(candidateGeomFeature.geometry.coordinates[i], candidateGeomFeature.geometry.coordinates[i + 1]); if (direction === ReferenceDirection.BACKWARD) lineBearing += 180; lineBearing = normalizeAngle(lineBearing); var pointCandidate = new PointCandidate(); pointCandidate.searchPoint = searchPoint; pointCandidate.pointOnLine = pointOnLine; pointCandidate.geometryId = candidateGeomFeature.properties.id; pointCandidate.referenceId = reference.id; pointCandidate.roadClass = candidateGeom.roadClass; // if(this.includeStreetnames) { // var metadata = await this.cache.metadataById(pointCandidate.geometryId); // pointCandidate.streetname = metadata.name; // } pointCandidate.direction = direction; pointCandidate.referenceLength = refLength; if (direction === ReferenceDirection.FORWARD) pointCandidate.location = pointOnLine.properties.location; else pointCandidate.location = refLength - pointOnLine.properties.location; pointCandidate.bearing = normalizeAngle(lineBearing); pointCandidate.interceptAngle = normalizeAngle(interceptBearing - lineBearing); pointCandidate.sideOfStreet = ReferenceSideOfStreet.UNKNOWN; if (pointCandidate.interceptAngle < 180) { pointCandidate.sideOfStreet = ReferenceSideOfStreet.RIGHT; } if (pointCandidate.interceptAngle > 180) { pointCandidate.sideOfStreet = ReferenceSideOfStreet.LEFT; } if (candidateGeom.backReferenceId) pointCandidate.oneway = false; else pointCandidate.oneway = true; // check bearing and add to candidate list if (!searchBearing || angleDelta(searchBearing, lineBearing) < this.bearingTolerance) return pointCandidate; } return null; } getPointCandidates(searchPoint, searchBearing, maxCandidates) { return __awaiter(this, void 0, void 0, function* () { this.tileIndex.addTileType(tiles_1.TileType.REFERENCE); var candidateFeatures = yield this.tileIndex.nearby(searchPoint, tiles_1.TileType.GEOMETRY, this.searchRadius, this.tileParams); var candidates = new Array(); if (candidateFeatures && candidateFeatures.features) { for (var candidateFeature of candidateFeatures.features) { var candidateGeom = this.tileIndex.objectIndex.get(candidateFeature.properties.id); var candidateGeomFeature = this.tileIndex.featureIndex.get(candidateFeature.properties.id); var pointOnLine = nearest_point_on_line_1.default(candidateGeomFeature, searchPoint, { units: 'meters' }); var forwardCandidate = yield this.getPointCandidateFromGeom(searchPoint, pointOnLine, candidateGeom, candidateGeomFeature, searchBearing, ReferenceDirection.FORWARD); var backwardCandidate = yield this.getPointCandidateFromGeom(searchPoint, pointOnLine, candidateGeom, candidateGeomFeature, searchBearing, ReferenceDirection.BACKWARD); if (forwardCandidate != null) { var snapped = false; if (this.snapToIntersections) { if (forwardCandidate.location < this.searchRadius) { var snappedForwardCandidate1 = Object.assign(new PointCandidate, forwardCandidate); snappedForwardCandidate1.location = 0; snappedForwardCandidate1.snappedPoint = along_1.default(candidateGeomFeature, 0, { "units": "meters" }); candidates.push(snappedForwardCandidate1); snapped = true; } if (forwardCandidate.referenceLength - forwardCandidate.location < this.searchRadius) { var snappedForwardCandidate2 = Object.assign(new PointCandidate, forwardCandidate); snappedForwardCandidate2.location = snappedForwardCandidate2.referenceLength; snappedForwardCandidate2.snappedPoint = along_1.default(candidateGeomFeature, snappedForwardCandidate2.referenceLength, { "units": "meters" }); candidates.push(snappedForwardCandidate2); snapped = true; } } if (!snapped) { candidates.push(forwardCandidate); } } if (backwardCandidate != null) { var snapped = false; if (this.snapToIntersections) { if (backwardCandidate.location < this.searchRadius) { var snappedBackwardCandidate1 = Object.assign(new PointCandidate, backwardCandidate); snappedBackwardCandidate1.location = 0; // not reversing the geom so snap to end on backRefs snappedBackwardCandidate1.snappedPoint = along_1.default(candidateGeomFeature, snappedBackwardCandidate1.referenceLength, { "units": "meters" }); candidates.push(snappedBackwardCandidate1); snapped = true; } if (backwardCandidate.referenceLength - backwardCandidate.location < this.searchRadius) { var snappedBackwardCandidate2 = Object.assign(new PointCandidate, backwardCandidate); snappedBackwardCandidate2.location = snappedBackwardCandidate2.referenceLength; // not reversing the geom so snap to start on backRefs snappedBackwardCandidate2.snappedPoint = along_1.default(candidateGeomFeature, 0, { "units": "meters" }); candidates.push(snappedBackwardCandidate2); snapped = true; } } if (!snapped) { candidates.push(backwardCandidate); } } } } var sortedCandidates = candidates.sort((p1, p2) => { p1.calcScore(); p2.calcScore(); if (p1.score > p2.score) { return 1; } if (p1.score < p2.score) { return -1; } return 0; }); if (sortedCandidates.length > maxCandidates) { sortedCandidates = sortedCandidates.slice(0, maxCandidates); } return sortedCandidates; }); } } exports.PointMatcher = PointMatcher;