sharedstreets
Version:
SharedStreets, a 'digital commons' for the street
765 lines (764 loc) • 52.3 kB
JavaScript
"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 });
const command_1 = require("@oclif/command");
const fs_1 = require("fs");
const index_1 = require("../index");
const index_2 = require("../index");
const turfHelpers = __importStar(require("@turf/helpers"));
const geom_1 = require("../geom");
const envelope_1 = __importDefault(require("@turf/envelope"));
const index_3 = require("../index");
const line_offset_1 = __importDefault(require("@turf/line-offset"));
const data_1 = require("../data");
const chalk = require('chalk');
const cliProgress = require('cli-progress');
function mapOgProperties(og_props, new_props) {
for (var prop of Object.keys(og_props)) {
new_props['pp_' + prop] = og_props[prop];
//console.log(new_props)
}
}
let Match = /** @class */ (() => {
class Match extends command_1.Command {
run() {
return __awaiter(this, void 0, void 0, function* () {
const { args, flags } = this.parse(Match);
this.log(chalk.bold.keyword('green')(' 🌏 Loading geojson data...'));
var inFile = args.file;
var outFile = flags.out;
if (!inFile || !fs_1.existsSync(inFile)) {
this.log(chalk.bold.keyword('orange')(' 💾 Input file not found...'));
return;
}
if (!outFile)
outFile = inFile;
if (outFile.toLocaleLowerCase().endsWith(".geojson"))
outFile = outFile.split(".").slice(0, -1).join(".");
if (flags['direction-field'])
console.log(chalk.bold.keyword('green')(' Filtering one-way and two-way streets using field "' + flags['direction-field'] + '" with values: ' + ' "' + flags['one-way-with-direction-value'] + '", "' + flags['one-way-against-direction-value'] + '", "' + flags['two-way-value'] + '"'));
if (flags['match-bike'] || flags['match-pedestrian']) {
if (flags['match-bike']) {
console.log(chalk.bold.keyword('green')(' Matching using bike routing rules'));
}
if (flags['match-pedestrian']) {
console.log(chalk.bold.keyword('green')(' Matching using pedestrian routing rules'));
}
if (flags['match-motorway-only'])
console.log(chalk.bold.keyword('orange')(' Ignoring motorway-only setting'));
}
else if (flags['match-car']) {
if (flags['match-motorway-only'])
console.log(chalk.bold.keyword('green')(' Matching using car routing rules on motorways only'));
else if (flags['match-surface-only'])
console.log(chalk.bold.keyword('green')(' Matching using car routing rules on surface streets only'));
else
console.log(chalk.bold.keyword('green')(' Matching using car routing rules on all streets'));
}
var content = fs_1.readFileSync(inFile);
var data = JSON.parse(content.toLocaleString());
var params = new index_1.TilePathParams();
params.source = flags['tile-source'];
params.tileHierarchy = flags['tile-hierarchy'];
if (data.features[0].geometry.type === 'LineString' || data.features[0].geometry.type === 'MultiLineString') {
yield matchLines(outFile, params, data, flags);
}
else if (data.features[0].geometry.type === 'Point') {
yield matchPoints(outFile, params, data, flags);
}
});
}
}
Match.description = 'matches point and line features to sharedstreets refs';
Match.examples = [
`$ shst match points.geojson --out=matched_points.geojson --port-properties
🌏 Loading points...
✨ Matching 3 points...
🎉 Matched 2 points... (1 unmached)
`,
];
Match.flags = {
help: command_1.flags.help({ char: 'h' }),
// flag with a value (-o, --out=FILE)
out: command_1.flags.string({ char: 'o', description: 'file output name creates files [file-output-name].matched.geojson and [file-output-name].unmatched.geojson' }),
'tile-source': command_1.flags.string({ description: 'SharedStreets tile source', default: 'osm/planet-181224' }),
'tile-hierarchy': command_1.flags.integer({ description: 'SharedStreets tile hierarchy', default: 6 }),
'skip-port-properties': command_1.flags.boolean({ char: 'p', description: 'skip porting existing feature properties preceeded by "pp_"', default: false }),
'follow-line-direction': command_1.flags.boolean({ description: 'only match using line direction', default: false }),
'best-direction': command_1.flags.boolean({ description: 'only match one direction based on best score', default: false }),
'direction-field': command_1.flags.string({ description: 'name of optional line properity describing segment directionality, use the related "one-way-*-value" and "two-way-value" properties' }),
'one-way-with-direction-value': command_1.flags.string({ description: 'name of optional value of "direction-field" indicating a one-way street with line direction' }),
'one-way-against-direction-value': command_1.flags.string({ description: 'name of optional value of "direction-field" indicating a one-way street against line direction' }),
'two-way-value': command_1.flags.string({ description: 'name of optional value of "direction-field" indicating a two-way street' }),
'bearing-field': command_1.flags.string({ description: 'name of optional point property containing bearing in decimal degrees', default: 'bearing' }),
'search-radius': command_1.flags.integer({ description: 'search radius for for snapping points, lines and traces (in meters)', default: 10 }),
'snap-intersections': command_1.flags.boolean({ description: 'snap line end-points to nearest intersection if closer than distance defined by snap-intersections-radius ', default: false }),
'snap-intersections-radius': command_1.flags.integer({ description: 'snap radius for intersections (in meters) used when snap-intersections is set', default: 10 }),
'snap-side-of-street': command_1.flags.boolean({ description: 'snap line to side of street', default: false }),
'side-of-street-field': command_1.flags.string({ description: 'name of optional property defining side of street relative to direction of travel' }),
'right-side-of-street-value': command_1.flags.string({ description: 'value of "side-of-street-field" for right side features', default: 'right' }),
'left-side-of-street-value': command_1.flags.string({ description: 'value of "side-of-street-field" for left side features', default: 'left' }),
'center-of-street-value': command_1.flags.string({ description: 'value of "side-of-street-field" for center features', default: 'center' }),
'left-side-driving': command_1.flags.boolean({ description: 'snap line to side of street using left-side driving rules', default: false }),
'match-car': command_1.flags.boolean({ description: 'match using car routing rules', default: true }),
'match-bike': command_1.flags.boolean({ description: 'match using bike routing rules', default: false }),
'match-pedestrian': command_1.flags.boolean({ description: 'match using pedestrian routing rules', default: false }),
'match-motorway-only': command_1.flags.boolean({ description: 'only match against motorway segments', default: false }),
'match-surface-streets-only': command_1.flags.boolean({ description: 'only match against surface street segments', default: false }),
'offset-line': command_1.flags.integer({ description: 'offset geometry based on direction of matched line (in meters)' }),
'cluster-points': command_1.flags.integer({ description: 'aproximate sub-segment length for clustering points (in meters)' }),
'buffer-points': command_1.flags.boolean({ description: 'buffer points into segment-snapped line segments' }),
'buffer-points-length': command_1.flags.integer({ description: 'length of buffered point (in meters)', default: 5 }),
'buffer-points-length-field': command_1.flags.string({ description: 'name of property containing buffered points (in meters)', default: 'length' }),
'buffer-merge': command_1.flags.boolean({ description: 'merge buffered points -- requires related buffer-merge-match-fields to be defined', default: false }),
'buffer-merge-match-fields': command_1.flags.string({ description: 'comma seperated list of fields to match values when merging buffered points', default: '' }),
'buffer-merge-group-fields': command_1.flags.string({ description: 'comma seperated list of fields to group values when merging buffered points', default: '' }),
'join-points': command_1.flags.boolean({ description: 'joins points into segment-snapped line segments -- requires related join-points-match-fields to be defined' }),
'join-points-match-fields': command_1.flags.string({ description: 'comma seperated list of fields to match values when joining points', default: '' }),
'join-point-sequence-field': command_1.flags.string({ description: 'name of field containing point sequence (e.g. 1=start, 2=middle, 3=terminus)', default: 'point_sequence' }),
'trim-intersections-radius': command_1.flags.integer({ description: 'buffer and clip radius for intersections in point buffer and point join operations (in meters)', default: 0 })
};
Match.args = [{ name: 'file' }];
return Match;
})();
exports.default = Match;
function matchPoints(outFile, params, points, flags) {
return __awaiter(this, void 0, void 0, function* () {
console.log(chalk.bold.keyword('green')(' ✨ Matching ' + points.features.length + ' points...'));
// test matcher point candidates
var cleanPoints = new geom_1.CleanedPoints(points);
var graph = new index_2.Graph(null, params);
if (flags['snap-intersections'])
graph.tileIndex.addTileType(index_1.TileType.INTERSECTION);
graph.searchRadius = flags['search-radius'];
var unmatchedPoints = [];
const bar2 = new cliProgress.Bar({}, {
format: chalk.keyword('blue')(' {bar}') + ' {percentage}% | {value}/{total} ',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591'
});
class MatchedPointType {
}
;
var matchedPoints = [];
bar2.start(points.features.length, 0);
for (var searchPoint of cleanPoints.clean) {
var bearing = null;
if (searchPoint.properties && searchPoint.properties[flags['bearing-field']])
bearing = parseFloat(searchPoint.properties[flags['bearing-field']]);
var matches = yield graph.matchPoint(searchPoint, bearing, 3, flags['left-side-driving']);
if (matches.length > 0) {
var matchedPoint = new MatchedPointType();
matchedPoint.matchedPoint = matches[0];
matchedPoint.originalFeature = searchPoint;
matchedPoints.push(matchedPoint);
}
else {
unmatchedPoints.push(searchPoint);
}
bar2.increment();
}
bar2.stop();
var clusteredPoints = [];
var bufferedPoints = [];
var joinedPoints = [];
var bufferedMergedPoints = [];
var intersectionClusteredPoints = [];
var mergedPoints = [];
if (flags['cluster-points']) {
var clusteredPointMap = {};
var intersectionClusteredPointMap = {};
const mergePointIntoCluster = (matchedPoint) => {
var pointGeom = null;
if (flags['snap-intersections'] &&
(matchedPoint.matchedPoint.location <= flags['snap-intersections-radius'] ||
matchedPoint.matchedPoint.referenceLength - matchedPoint.matchedPoint.location <= flags['snap-intersections-radius'])) {
var reference = graph.tileIndex.objectIndex.get(matchedPoint.matchedPoint.referenceId);
var intersectionId;
if (matchedPoint.matchedPoint.location <= flags['snap-intersections-radius']) {
intersectionId = reference.locationReferences[0].intersectionId;
}
else if (matchedPoint.matchedPoint.referenceLength - matchedPoint.matchedPoint.location <= flags['snap-intersections-radius']) {
intersectionId = reference.locationReferences[reference.locationReferences.length - 1].intersectionId;
}
if (intersectionClusteredPointMap[intersectionId]) {
pointGeom = intersectionClusteredPointMap[intersectionId];
pointGeom.properties['count'] += 1;
}
else {
pointGeom = JSON.parse(JSON.stringify(graph.tileIndex.featureIndex.get(intersectionId)));
var intersection = graph.tileIndex.objectIndex.get(intersectionId);
delete pointGeom.properties["id"];
pointGeom.properties["intersectionId"] = intersectionId;
var inboundCount = 1;
for (var inboundRefId of intersection.inboundReferenceIds) {
pointGeom.properties["inboundReferenceId_" + inboundCount] = inboundRefId;
inboundCount++;
}
var outboundCount = 1;
for (var outboundRefId of intersection.outboundReferenceIds) {
pointGeom.properties["outboundReferenceId_" + outboundCount] = outboundRefId;
outboundCount++;
}
pointGeom.properties['count'] = 1;
intersectionClusteredPointMap[intersectionId] = pointGeom;
}
}
else {
var binCount = data_1.getBinCountFromLength(matchedPoint.matchedPoint.referenceLength, flags['cluster-points']);
var binPosition = data_1.getBinPositionFromLocation(matchedPoint.matchedPoint.referenceLength, flags['cluster-points'], matchedPoint.matchedPoint.location);
var binId = data_1.generateBinId(matchedPoint.matchedPoint.referenceId, binCount, binPosition);
var binLength = data_1.getBinLength(matchedPoint.matchedPoint.referenceLength, flags['cluster-points']);
if (clusteredPointMap[binId]) {
clusteredPointMap[binId].properties['count'] += 1;
}
else {
var bins = graph.tileIndex.referenceToBins(matchedPoint.matchedPoint.referenceId, binCount, 2, index_2.ReferenceSideOfStreet.RIGHT);
var binPoint = turfHelpers.point(bins.geometry.coordinates[binPosition - 0]);
binPoint.properties['id'] = binId;
binPoint.properties['referenceId'] = matchedPoint.matchedPoint.referenceId;
binPoint.properties['binPosition'] = binPosition;
binPoint.properties['binCount'] = binCount;
binPoint.properties['binLength'] = binLength;
binPoint.properties['count'] = 1;
clusteredPointMap[binId] = binPoint;
}
pointGeom = clusteredPointMap[binId];
}
for (var property of Object.keys(matchedPoint.originalFeature.properties)) {
if (property.startsWith('pp_')) {
if (!isNaN(matchedPoint.originalFeature.properties[property])) {
var sumPropertyName = 'sum_' + property;
if (!pointGeom.properties[sumPropertyName]) {
pointGeom.properties[sumPropertyName] = 0;
}
pointGeom.properties[sumPropertyName] += matchedPoint.originalFeature.properties[property];
}
}
}
};
for (var matchedPoint of matchedPoints) {
mergePointIntoCluster(matchedPoint);
}
intersectionClusteredPoints = Object.keys(intersectionClusteredPointMap).map(key => intersectionClusteredPointMap[key]);
clusteredPoints = Object.keys(clusteredPointMap).map(key => clusteredPointMap[key]);
}
var offsetLine = flags['offset-line'];
if (flags['buffer-points']) {
class MergeBufferedPointsType {
}
;
console.log(chalk.bold.keyword('green')(' ✨ Buffering ' + matchedPoints.length + ' matched points...'));
var bufferLength = flags['buffer-points-length'];
console.log(chalk.bold.keyword('green')(' default buffer length: ' + bufferLength));
var bufferLengthFieldName = null;
if (flags['buffer-points-length-field']) {
bufferLengthFieldName = flags['buffer-points-length-field'].toLocaleLowerCase().trim().replace(/ /g, "_");
console.log(chalk.bold.keyword('green')(' buffer length fieldname: ' + bufferLengthFieldName));
}
bufferLengthFieldName = flags['buffer-points-length-field'].toLocaleLowerCase().trim().replace(/ /g, "_");
for (var matchedPoint of matchedPoints) {
var leftSideDriving = flags['left-side-driving'];
if (offsetLine) {
if (leftSideDriving) {
if (matchedPoint.matchedPoint.sideOfStreet === index_2.ReferenceSideOfStreet.LEFT) {
offsetLine = offsetLine;
}
else if (matchedPoint.matchedPoint.sideOfStreet === index_2.ReferenceSideOfStreet.RIGHT) {
offsetLine = 0 - offsetLine;
}
}
else {
if (matchedPoint.matchedPoint.sideOfStreet === index_2.ReferenceSideOfStreet.RIGHT) {
offsetLine = offsetLine;
}
else if (matchedPoint.matchedPoint.sideOfStreet === index_2.ReferenceSideOfStreet.LEFT) {
offsetLine = 0 - offsetLine;
}
}
}
var pointBufferLength = bufferLength;
if (bufferLengthFieldName && matchedPoint.originalFeature.properties.hasOwnProperty(bufferLengthFieldName))
pointBufferLength = matchedPoint.originalFeature.properties[bufferLengthFieldName];
matchedPoint.bufferedPoint = yield graph.bufferPoint(matchedPoint.matchedPoint, pointBufferLength, offsetLine);
var bufferedFeature = matchedPoint.bufferedPoint.toFeature();
mapOgProperties(matchedPoint.originalFeature.properties, bufferedFeature.properties);
bufferedPoints.push(bufferedFeature);
}
if (flags['buffer-merge']) {
const bufferIntersectionRaidus = flags['trim-intersections-radius'];
console.log(chalk.bold.keyword('green')(' ✨ Merging ' + bufferedPoints.length + ' buffered points...'));
let bufferedPreMergedPoints = new Map();
let mergeFields = [];
if (flags['buffer-merge-match-fields']) {
// split and clean property fields
mergeFields = flags['buffer-merge-match-fields'].split(",").map((f) => { return f.toLocaleLowerCase().replace(/ /g, "_"); });
mergeFields.sort();
console.log(chalk.bold.keyword('green')(' merging on fields: ' + mergeFields.join(', ')));
}
let groupFields = [];
if (flags['buffer-merge-group-fields']) {
// split and clean property fields
groupFields = flags['buffer-merge-group-fields'].split(",").map((f) => { return f.toLocaleLowerCase().replace(/ /g, "_"); });
groupFields.sort();
console.log(chalk.bold.keyword('green')(' grouping on field values: ' + groupFields.join(', ')));
}
for (let matchedPoint of matchedPoints) {
let fieldValues = [];
for (let mergeField of mergeFields) {
if (matchedPoint.originalFeature.properties.hasOwnProperty(mergeField)) {
fieldValues.push((mergeField + ':' + matchedPoint.originalFeature.properties[mergeField]).toLocaleLowerCase().trim().replace(/ /g, "_"));
}
}
let fieldValuesString = fieldValues.join(':');
let refSideHash = matchedPoint.bufferedPoint.referenceId + ':' + matchedPoint.bufferedPoint.sideOfStreet + ':' + fieldValuesString;
if (!bufferedPreMergedPoints.has(refSideHash)) {
bufferedPreMergedPoints.set(refSideHash, new Array());
}
;
bufferedPreMergedPoints.get(refSideHash).push(matchedPoint);
}
let mergeSegments = (bufferedSegments) => __awaiter(this, void 0, void 0, function* () {
let mergedSegment = new MergeBufferedPointsType();
let mergedSegments = [];
bufferedSegments;
bufferedSegments.sort((a, b) => (a.bufferedPoint.section[0] > b.bufferedPoint.section[0]) ? 11 : -1);
let segment1 = bufferedSegments.pop();
mergedSegment.mergedPathSegments = segment1.bufferedPoint;
mergedSegment.matchedPoints = [segment1];
while (segment1 && bufferedSegments.length > 0) {
let segment2 = bufferedSegments.pop();
if (segment2 && mergedSegment.mergedPathSegments.isIntersecting(segment2.bufferedPoint)) {
let offsetLine = flags['offset-line'];
let leftSideDriving = flags['left-side-driving'];
if (offsetLine) {
if (leftSideDriving) {
if (mergedSegment.mergedPathSegments.sideOfStreet === index_2.ReferenceSideOfStreet.LEFT) {
offsetLine = offsetLine;
}
else if (mergedSegment.mergedPathSegments.sideOfStreet === index_2.ReferenceSideOfStreet.RIGHT) {
offsetLine = 0 - offsetLine;
}
}
else {
if (mergedSegment.mergedPathSegments.sideOfStreet === index_2.ReferenceSideOfStreet.RIGHT) {
offsetLine = offsetLine;
}
else if (mergedSegment.mergedPathSegments.sideOfStreet === index_2.ReferenceSideOfStreet.LEFT) {
offsetLine = 0 - offsetLine;
}
}
}
mergedSegment.mergedPathSegments = yield graph.union(mergedSegment.mergedPathSegments, segment2.bufferedPoint, bufferIntersectionRaidus, offsetLine);
mergedSegment.matchedPoints.push(segment2);
}
else {
mergedSegments.push(mergedSegment);
if (segment2) {
segment1 = segment2;
mergedSegment = new MergeBufferedPointsType();
mergedSegment.mergedPathSegments = segment1.bufferedPoint;
mergedSegment.matchedPoints = [segment1];
}
else {
mergedSegment = null;
}
}
}
if (mergedSegment)
mergedSegments.push(mergedSegment);
return mergedSegments;
});
for (let refSide of bufferedPreMergedPoints.keys()) {
if (bufferedPreMergedPoints.get(refSide).length > 0) {
let mergedBuffers = yield mergeSegments(bufferedPreMergedPoints.get(refSide));
for (let mergedBuffer of mergedBuffers) {
let outputBufferedFeature = mergedBuffer.mergedPathSegments.toFeature();
for (let mergeField of mergeFields) {
if (mergedBuffer.matchedPoints[0].originalFeature.properties.hasOwnProperty(mergeField)) {
outputBufferedFeature.properties['pp_' + mergeField] = mergedBuffer.matchedPoints[0].originalFeature.properties[mergeField];
}
}
for (let groupField of groupFields) {
let groupedFieldValues = [];
for (let point of mergedBuffer.matchedPoints) {
if (point.originalFeature.properties.hasOwnProperty(groupField)) {
groupedFieldValues.push(point.originalFeature.properties[groupField]);
}
}
outputBufferedFeature.properties['pp_' + groupField] = groupedFieldValues;
}
outputBufferedFeature.properties['shst_merged_point_count'] = mergedBuffer.matchedPoints.length;
let mergedBufferLength = 0;
for (let point of mergedBuffer.matchedPoints) {
mergedBufferLength += point.bufferedPoint.section[1] - point.bufferedPoint.section[0];
}
outputBufferedFeature.properties['shst_merged_buffer_length'] = mergedBufferLength;
bufferedMergedPoints.push(outputBufferedFeature);
}
}
}
}
}
if (flags['join-points']) {
class JoinedPointsType {
}
;
console.log(chalk.bold.keyword('green')(' ✨ Joining ' + matchedPoints.length + ' matched points...'));
var preMergedPoints = new Map();
var mergeFields = [];
if (flags['join-points-match-fields']) {
// split and clean property fields
mergeFields = flags['join-points-match-fields'].split(",").map((f) => { return f.toLocaleLowerCase().replace(/ /g, "_"); });
mergeFields.sort();
console.log(chalk.bold.keyword('green')(' merging on fields: ' + mergeFields.join(', ')));
}
for (var matchedPoint of matchedPoints) {
let fieldValues = [];
for (let mergeField of mergeFields) {
if (matchedPoint.originalFeature.properties.hasOwnProperty(mergeField)) {
fieldValues.push((mergeField + ':' + matchedPoint.originalFeature.properties[mergeField]).toLocaleLowerCase().trim().replace(/ /g, "_"));
}
}
let fieldValuesString = fieldValues.join(':');
let refSideHash = matchedPoint.matchedPoint.referenceId + ':' + matchedPoint.matchedPoint.sideOfStreet + ':' + fieldValuesString;
if (!preMergedPoints.has(refSideHash)) {
preMergedPoints.set(refSideHash, new Array());
}
;
preMergedPoints.get(refSideHash).push(matchedPoint);
}
const bufferIntersectionRaidus = flags['trim-intersections-radius'];
const mergePoints = (matchedPoints) => __awaiter(this, void 0, void 0, function* () {
// sort matched points along line
matchedPoints.sort((a, b) => {
return a.matchedPoint.location - b.matchedPoint.location;
});
let joinedSegments = [];
let currSegment = null;
for (let matchedPoint of matchedPoints) {
if (!currSegment) {
currSegment = new JoinedPointsType();
currSegment.matchedPoints = [];
if (parseInt(matchedPoint.originalFeature.properties[flags['join-point-sequence-field']]) === 1) {
currSegment.matchedPoints.push(matchedPoint);
}
else if (parseInt(matchedPoint.originalFeature.properties[flags['join-point-sequence-field']]) > 1) {
let startPoint = JSON.parse(JSON.stringify(matchedPoint));
startPoint.matchedPoint.location = 0;
currSegment.matchedPoints.push(startPoint);
currSegment.matchedPoints.push(matchedPoint);
if (parseInt(matchedPoint.originalFeature.properties[flags['join-point-sequence-field']]) === 3) {
currSegment.joinedPath = yield graph.joinPoints(currSegment.matchedPoints[0].matchedPoint, currSegment.matchedPoints[currSegment.matchedPoints.length - 1].matchedPoint, bufferIntersectionRaidus, offsetLine);
joinedSegments.push(currSegment);
currSegment = null;
}
}
}
else if (parseInt(matchedPoint.originalFeature.properties[flags['join-point-sequence-field']]) > 1) {
currSegment.matchedPoints.push(matchedPoint);
if (parseInt(matchedPoint.originalFeature.properties[flags['join-point-sequence-field']]) === 3) {
currSegment.joinedPath = yield graph.joinPoints(currSegment.matchedPoints[0].matchedPoint, currSegment.matchedPoints[currSegment.matchedPoints.length - 1].matchedPoint, bufferIntersectionRaidus, offsetLine);
joinedSegments.push(currSegment);
currSegment = null;
}
}
}
if (currSegment && currSegment.matchedPoints.length > 0) {
let endPoint = JSON.parse(JSON.stringify(currSegment.matchedPoints[currSegment.matchedPoints.length - 1]));
endPoint.matchedPoint.location = endPoint.matchedPoint.referenceLength;
currSegment.matchedPoints.push(endPoint);
currSegment.joinedPath = yield graph.joinPoints(currSegment.matchedPoints[0].matchedPoint, currSegment.matchedPoints[currSegment.matchedPoints.length - 1].matchedPoint, bufferIntersectionRaidus, offsetLine);
joinedSegments.push(currSegment);
currSegment = null;
}
return joinedSegments;
});
for (let refSide of preMergedPoints.keys()) {
if (preMergedPoints.get(refSide).length > 0) {
let mergedPointSegments = yield mergePoints(preMergedPoints.get(refSide));
for (let mergedPointSegment of mergedPointSegments) {
let outputJoinedFeature = mergedPointSegment.joinedPath.toFeature();
for (let mergeField of mergeFields) {
if (mergedPointSegment.matchedPoints[0].originalFeature.properties.hasOwnProperty(mergeField)) {
outputJoinedFeature.properties['pp_' + mergeField] = mergedPointSegment.matchedPoints[0].originalFeature.properties[mergeField];
}
}
outputJoinedFeature.properties['shst_joined_point_count'] = mergedPointSegment.matchedPoints.length;
joinedPoints.push(outputJoinedFeature);
}
}
}
}
if (matchedPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + matchedPoints.length + ' matched points: ' + outFile + ".matched.geojson"));
var featureArray = [];
for (var matchedPoint of matchedPoints) {
featureArray.push(matchedPoint.matchedPoint.toFeature());
}
var matchedFeatureCollection = turfHelpers.featureCollection(featureArray);
var matchedJsonOut = JSON.stringify(matchedFeatureCollection);
fs_1.writeFileSync(outFile + ".matched.geojson", matchedJsonOut);
}
if (clusteredPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + clusteredPoints.length + ' clustered points: ' + outFile + ".clustered.geojson"));
var clusteredPointsFeatureCollection = turfHelpers.featureCollection(clusteredPoints);
var clusteredJsonOut = JSON.stringify(clusteredPointsFeatureCollection);
fs_1.writeFileSync(outFile + ".clustered.geojson", clusteredJsonOut);
}
if (bufferedPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + bufferedPoints.length + ' buffered points: ' + outFile + ".buffered.geojson"));
var bufferedPointsFeatureCollection = turfHelpers.featureCollection(bufferedPoints);
var bufferedJsonOut = JSON.stringify(bufferedPointsFeatureCollection);
fs_1.writeFileSync(outFile + ".buffered.geojson", bufferedJsonOut);
}
if (bufferedMergedPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + bufferedMergedPoints.length + ' buffered and merged points: ' + outFile + ".buffered.merged.geojson"));
var bufferedMergedPointsFeatureCollection = turfHelpers.featureCollection(bufferedMergedPoints);
var bufferedMergedJsonOut = JSON.stringify(bufferedMergedPointsFeatureCollection);
fs_1.writeFileSync(outFile + ".buffered.merged.geojson", bufferedMergedJsonOut);
}
if (intersectionClusteredPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + intersectionClusteredPoints.length + ' intersection clustered points: ' + outFile + ".intersection_clustered.geojson"));
var intersectionClusteredPointsFeatureCollection = turfHelpers.featureCollection(intersectionClusteredPoints);
var intersectionClusteredJsonOut = JSON.stringify(intersectionClusteredPointsFeatureCollection);
fs_1.writeFileSync(outFile + ".intersection_clustered.geojson", intersectionClusteredJsonOut);
}
if (unmatchedPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + unmatchedPoints.length + ' unmatched points: ' + outFile + ".unmatched.geojson"));
var unmatchedFeatureCollection = turfHelpers.featureCollection(unmatchedPoints);
var unmatchedJsonOut = JSON.stringify(unmatchedFeatureCollection);
fs_1.writeFileSync(outFile + ".unmatched.geojson", unmatchedJsonOut);
}
if (joinedPoints.length) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + joinedPoints.length + ' joined points: ' + outFile + ".joined.geojson"));
var joinedPointFeatureCollection = turfHelpers.featureCollection(joinedPoints);
var joinedPointJson = JSON.stringify(joinedPointFeatureCollection);
fs_1.writeFileSync(outFile + ".joined.geojson", joinedPointJson);
}
if (cleanPoints.invalid && cleanPoints.invalid.length > 0) {
console.log(chalk.bold.keyword('blue')(' ✏️ Writing ' + cleanPoints.invalid.length + ' invalid points: ' + outFile + ".invalid.geojson"));
var invalidJsonOut = JSON.stringify(cleanPoints.invalid);
fs_1.writeFileSync(outFile + ".unmatched.geojson", invalidJsonOut);
}
});
}
var MatchDirection;
(function (MatchDirection) {
MatchDirection[MatchDirection["FORWARD"] = 0] = "FORWARD";
MatchDirection[MatchDirection["BACKWARD"] = 1] = "BACKWARD";
MatchDirection[MatchDirection["BOTH"] = 2] = "BOTH";
MatchDirection[MatchDirection["BEST"] = 3] = "BEST";
})(MatchDirection || (MatchDirection = {}));
function matchLines(outFile, params, lines, flags) {
return __awaiter(this, void 0, void 0, function* () {
var cleanedlines = new geom_1.CleanedLines(lines);
console.log(chalk.bold.keyword('green')(' ✨ Matching ' + cleanedlines.clean.length + ' lines...'));
const getMatchedPath = (path) => {
path.matchedPath.properties['segments'] = path.segments;
path.matchedPath.properties['score'] = path.score;
path.matchedPath.properties['matchType'] = path.matchType;
if (!flags['skip-port-properties'])
mapOgProperties(path.originalFeature.properties, path.matchedPath.properties);
return path.matchedPath;
};
const getMatchedSegments = (path, ref) => {
var segmentIndex = 1;
var segmentGeoms = [];
for (var segment of path.segments) {
var segmentGeom = segment.geometry;
segmentGeom.properties = {};
segmentGeom.properties['shstReferenceId'] = segment.referenceId;
segmentGeom.properties['shstGeometryId'] = segment.geometryId;
segmentGeom.properties['shstFromIntersectionId'] = segment.fromIntersectionId;
segmentGeom.properties['shstToIntersectionId'] = segment.toIntersectionId;
segmentGeom.properties['referenceLength'] = segment.referenceLength;
segmentGeom.properties['section'] = segment.section;
segmentGeom.properties['gisReferenceId'] = ref.id;
segmentGeom.properties['gisGeometryId'] = ref.geometryId;
segmentGeom.properties['gisTotalSegments'] = path.segments.length;
segmentGeom.properties['gisSegmentIndex'] = segmentIndex;
segmentGeom.properties['gisFromIntersectionId'] = ref.locationReferences[0].intersectionId;
segmentGeom.properties['gisToIntersectionId'] = ref.locationReferences[ref.locationReferences.length - 1].intersectionId;
segmentGeom.properties['startSideOfStreet'] = path.startPoint.sideOfStreet;
segmentGeom.properties['endSideOfStreet'] = path.endPoint.sideOfStreet;
if (flags['side-of-street-field'] && path.originalFeature.properties[flags['side-of-street-field']]) {
var sideOfStreetValue = path.originalFeature.properties[flags['side-of-street-field']].toLocaleLowerCase();
if (flags['left-side-of-street-value'].toLocaleLowerCase() === sideOfStreetValue) {
path.sideOfStreet = index_2.ReferenceSideOfStreet.LEFT;
}
else if (flags['right-side-of-street-value'].toLocaleLowerCase() === sideOfStreetValue) {
path.sideOfStreet = index_2.ReferenceSideOfStreet.RIGHT;
}
else if (flags['center-of-street-value'].toLocaleLowerCase() === sideOfStreetValue) {
path.sideOfStreet = index_2.ReferenceSideOfStreet.CENTER;
}
else {
path.sideOfStreet = index_2.ReferenceSideOfStreet.UNKNOWN;
}
}
if (flags['offset-line']) {
if (flags['snap-side-of-street']) {
if (flags['left-side-driving']) {
if (path.sideOfStreet == index_2.ReferenceSideOfStreet.RIGHT)
segmentGeom = line_offset_1.default(segmentGeom, 0 - flags['offset-line'], { "units": "meters" });
else if (path.sideOfStreet == index_2.ReferenceSideOfStreet.LEFT)
segmentGeom = line_offset_1.default(segmentGeom, flags['offset-line'], { "units": "meters" });
}
else {
if (path.sideOfStreet == index_2.ReferenceSideOfStreet.RIGHT)
segmentGeom = line_offset_1.default(segmentGeom, flags['offset-line'], { "units": "meters" });
else if (path.sideOfStreet == index_2.ReferenceSideOfStreet.LEFT)
segmentGeom = line_offset_1.default(segmentGeom, 0 - flags['offset-line'], { "units": "meters" });
}
}
else {
if (flags['left-side-driving']) {
segmentGeom = line_offset_1.default(segmentGeom, 0 - flags['offset-line'], { "units": "meters" });
}
else {
segmentGeom = line_offset_1.default(segmentGeom, flags['offset-line'], { "units": "meters" });
}
}
}
segmentGeom.properties['sideOfStreet'] = path.sideOfStreet;
segmentGeom.properties['score'] = path.score;
segmentGeom.properties['matchType'] = path.matchType;
mapOgProperties(path.originalFeature.properties, segmentGeom.properties);
segmentGeoms.push(segmentGeom);
segmentIndex++;
}
return segmentGeoms;
};
var extent = envelope_1.default(lines);
var graphMode;
if (flags['match-bike'])
graphMode = index_2.GraphMode.BIKE;
else if (flags['match-pedestrian'])
graphMode = index_2.GraphMode.PEDESTRIAN;
else if (flags['match-car']) {
if (flags['match-motorway-only'])
graphMode = index_2.GraphMode.CAR_MOTORWAY_ONLY;
else if (flags['match-surface-only'])
graphMode = index_2.GraphMode.CAR_SURFACE_ONLY;
else
graphMode = index_2.GraphMode.CAR_ALL;
}
else
graphMode = index_2.GraphMode.CAR_ALL;
var matcher = new index_2.Graph(extent, params, graphMode);
yield matcher.buildGraph();
if (flags['search-radius'])
matcher.searchRadius = flags['search-radius'];
matcher.snapIntersections = flags['snap-intersections'];
var matchedLines = [];
var unmatchedLines = [];
const bar1 = new cliProgress.Bar({}, {
format: chalk.keyword('blue')(' {bar}') + ' {percentage}% | {value}/{total} ',
barCompleteChar: '\u2588',
barIncompleteChar: '\u2591'
});
bar1.start(cleanedlines.clean.length, 0);
for (var line of cleanedlines.clean) {
if (line.properties['geo_id'] == 30107269)
console.log('30107269');
var matchDirection;
if (flags['direction-field'] && line.properties[flags['direction-field'].toLocaleLowerCase()] != undefined) {
var lineDirectionValue = '' + line.properties[flags['direction-field'].toLocaleLowerCase()];
if (lineDirectionValue == '' + flags['one-way-with-direction-value']) {
matchDirection = MatchDirection.FORWARD;
}
else if (lineDirectionValue == '' + flags['one-way-against-direction-value']) {
matchDirection = MatchDirection.BACKWARD;
}
else if (lineDirectionValue == '' + flags['two-way-value']) {
matchDirection = MatchDirection.BOTH;
}
else {
// TODO handle lines that don't match rules
matchDirection = MatchDirection.BOTH;
}
}
else if (flags['follow-line-direction']) {
matchDirection = MatchDirection.FORWARD;
}
else if (flags['best-direction']) {
matchDirection = MatchDirection.BEST;
}
else {
matchDirection = MatchDirection.BOTH;
}
var matchForward = null;
var matchForwardSegments = null;
if (matchDirection == MatchDirection.FORWARD || matchDirection == MatchDirection.BOTH || matchDirection == MatchDirection.BEST) {
var gisRef = index_3.forwardReference(line);
matchForward = yield matcher.matchGeom(line);
if (matchForward && matchForward.score < matcher.searchRadius * 2) {
matchForwardSegments = getMatchedSegments(matchForward, gisRef);
}
}
var matchBackward = null;
var matchBackwardSegments = null;
if (matchDirection == MatchDirection.BACKWARD || matchDirection == MatchDirection.BOTH || matchDirection == MatchDirection.BEST) {
var gisRef = index_3.backReference(line);
var reversedLine = geom_1.reverseLineString(line);
matchBackward = yield matcher.matchGeom(reversedLine);
if (matchBackward && matchBackward.score < matcher.searchRadius * 2) {
matchBackwardSegments = getMatchedSegments(matchBackward, gisRef);
}
}
var matchedLine = false;
if ((matchDirection == MatchDirection.FORWARD || matchDirection == MatchDirection.BOTH) && matchForwardSegments) {
matchedLines = matchedLines.concat(matchForwardSegments);
matchedLine = true;
}
if ((matchDirection == MatchDirection.BACKWARD || matchDirection == MatchDirection.BOTH) && matchBackwardSegments) {
matchedLines = matchedLines.concat(matchBackwardSegments);
matchedLine = true;
}
if (matchDirection == MatchDirection.BEST) {
if (matchForward && matchBackward) {
if (matchForward.score > matchBackward.score) {
matchedLines = matchedLines.concat(matchForwardSegments);
matchedLine = true;
}
else if (matchForward.score == matchBackward.score) {
if (flags['left-side-driving']) {
if (matchForward.sideOfStreet == index_2.ReferenceSideOfStreet.LEFT)
matchedLines = matchedLines.concat(matchForwardSegments);
else
matchedLines = matchedLines.concat(matchBackwardSegments);
}
else {
if (matchForward.sideOfStreet == index_2.ReferenceSideOfStreet.RIGHT)
matchedLines = matchedLines.concat(matchForwardSegments);