Skip to content

Commit

Permalink
upgrade turf to v7
Browse files Browse the repository at this point in the history
needed to add NodeGeographyUtils.findTouchingPoint
because new version of turf.lineIntersect does not return the touching point for
lines that are touching at extremities only, which occurs in real life data
(for instance where the streets are cut at the intersection in osm data), or
at corners where street name changes.

Also had to update some functions types to match the new turf types.
Turf now uses GeoJSON types instead of custom js types.

Areas were updated since there were some issues with its calculation
in previous versions of turf. See Turfjs/turf#2166
  • Loading branch information
kaligrafy committed Nov 12, 2024
1 parent 9028a52 commit afc0edc
Show file tree
Hide file tree
Showing 21 changed files with 1,634 additions and 1,343 deletions.
2 changes: 1 addition & 1 deletion packages/chaire-lib-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"@opentelemetry/resources": "^1.27.0",
"@opentelemetry/sdk-trace-node": "^1.27.0",
"@opentelemetry/sdk-trace-base": "^1.27.0",
"@turf/turf": "^6.3.0",
"@turf/turf": "^7.1.0",
"@zeit/fetch-retry": "^5.0.1",
"bcryptjs": "^2.4.3",
"bytes": "^3.1.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/chaire-lib-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"dependencies": {
"@casl/ability": "^5.4.4",
"@mapbox/polyline": "^1.1.1",
"@turf/turf": "^6.3.0",
"@turf/turf": "^7.1.0",
"@types/node": "^17.0.38",
"JSONStream": "^1.3.5",
"child_process": "^1.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const findNearestFromPolygon = <P extends GeoJSON.GeoJsonProperties>(
}
let nearest: GeoJSON.Feature<GeoJSON.Point, P> | undefined = undefined;
let shortestDistance = Number.MAX_VALUE;
const polygonLines = turf.polygonToLineString(feature);
const polygonLines = turf.polygonToLine(feature);
const lines = polygonLines.type === 'FeatureCollection' ? polygonLines.features : [polygonLines];
for (let i = 0; i < features.length; i++) {
for (let lineI = 0; lineI < lines.length; lineI++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ const getOverlappingOnBuffer = (
diff = diff / 2;
buffer = lastCount < options.expectedApproximateCount ? buffer + diff : buffer - diff;
const bufferedFeature = turf.buffer(originalFeature, buffer / 1000);
if (bufferedFeature.geometry === null) {
if (!bufferedFeature || bufferedFeature.geometry === null) {
break;
}
lastIndices = getOverlappingIndices(bufferedFeature, featuresToSplit);
Expand Down Expand Up @@ -211,7 +211,7 @@ const getMaxBufferSize = (feature: GeoJSON.Feature, featuresToSplit: GeoJSON.Fea
// feature on a buffer of the size of the shape, then calculate the smallest
// distance between features in that area as the buffer size.
const bufferedShape = turf.buffer(feature, defaultBufferSizeInMeters / 1000);
if (bufferedShape.geometry === null) {
if (!bufferedShape || bufferedShape.geometry === null) {
return defaultBufferSizeInMeters;
}
const splitSubset = getOverlappingIndices(bufferedShape, featuresToSplit);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* License text available at https://opensource.org/licenses/MIT
*/

import { lineString, nearestPointOnLine, booleanPointInPolygon, Position, distance } from '@turf/turf';
import { LineString, Point, Feature, Polygon } from 'geojson';
import { lineString, nearestPointOnLine, booleanPointInPolygon, distance } from '@turf/turf';
import { LineString, Point, Feature, Polygon, Position } from 'geojson';

const MAX_NODE_RELOCATION_DISTANCE = 50; // in meters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { offsetOverlappingLines, OFFSET_WIDTH, getLinesInView } from '../ManageOverlappingLines';
import { lineOffset, inside, circle, union, bboxPolygon } from '@turf/turf';
import { lineOffset, booleanPointInPolygon, circle, union, bboxPolygon, featureCollection } from '@turf/turf';
import GeoJSON, { LineString } from 'geojson';
import _cloneDeep from 'lodash/cloneDeep';

Expand Down Expand Up @@ -163,10 +163,10 @@ test('Test overlaps between multiple segments of the same line with another line
let polygon = _cloneDeep(featureSkeleton) as GeoJSON.Feature<GeoJSON.Polygon | GeoJSON.MultiPolygon>;
polygon.geometry.type = 'Polygon';
collection.features[0].geometry.coordinates.forEach((coord) => {
polygon = union(polygon, (circle(coord, OFFSET_WIDTH + 1, { units: 'meters' })));
polygon = union(featureCollection([polygon, circle(coord, OFFSET_WIDTH + 1, { units: 'meters' }) as any])) as any;
});
offsetCollection.features[1].geometry.coordinates.forEach((coord) => {
expect(inside(coord, polygon)).toBeTruthy();
expect(booleanPointInPolygon(coord, polygon as any)).toBeTruthy();
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
*/

import { manageRelocatingNodes } from '../RelocateNodes';
import { LineString, Point } from '@turf/turf';
import _cloneDeep from 'lodash/cloneDeep';

const transitPaths : GeoJSON.FeatureCollection<LineString> = {
const transitPaths : GeoJSON.FeatureCollection<GeoJSON.LineString> = {
type: 'FeatureCollection',
features: [
{
Expand Down Expand Up @@ -69,7 +68,7 @@ const transitPaths : GeoJSON.FeatureCollection<LineString> = {
}]
};

const transitNodes : GeoJSON.FeatureCollection<Point> = {
const transitNodes : GeoJSON.FeatureCollection<GeoJSON.Point> = {
type: 'FeatureCollection',
features: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ export default class OsmDataPreparationNonResidential {
!isPolygon(poiBuildings[index].geojson) ||
(isPolygon(poi) &&
isPolygon(poiBuildings[index].geojson) &&
turf.intersect(poi, poiBuildings[index].geojson as any) !== null))
turf.booleanIntersects(poi, poiBuildings[index].geojson as any)))
);
if (educationBuildingIndexes.length > 0) {
const educationPois = educationBuildingIndexes.flatMap((educationBuildingIndex) => {
Expand Down Expand Up @@ -505,7 +505,7 @@ export default class OsmDataPreparationNonResidential {
if (
isPolygon(poi) &&
isPolygon(poiBuilding.geojson) &&
turf.intersect(poi, poiBuilding.geojson) === null
turf.booleanIntersects(poi, poiBuilding.geojson) === null
) {
remainingPoiTags.push(poi);
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import each from 'jest-each';
import fs from 'fs';
import fs from 'fs';
import { DataFileOsmRaw, DataOsmRaw } from '../data/dataOsmRaw';
import { DataFileGeojson, DataGeojson } from '../data/dataGeojson';
import OsmDataPreparationResidential from '../OsmDataPreparationResidential';
Expand All @@ -24,7 +24,7 @@ const geojsonCompare = (element1: GeoJSON.Feature, element2: GeoJSON.Feature) =>
return coord1[0] === coord2[0] ? numberCompare(coord1[1], coord2[1]) : numberCompare(coord1[0], coord2[0]);
}
return 0;
}
};

const getData = (dir: string): { osmRawData: DataOsmRaw, osmGeojsonData: DataGeojson } => {
return {
Expand All @@ -36,8 +36,8 @@ const getData = (dir: string): { osmRawData: DataOsmRaw, osmGeojsonData: DataGeo
'',
{ readFileAbsolute: () => fs.readFileSync(`${__dirname}/imports/${dir}/osmData.geojson`) }
)
}
}
};
};

each([
['residential-zone-without-building-count-matches'],
Expand All @@ -55,14 +55,14 @@ each([
let fileContent = fs.readFileSync(`${__dirname}/imports/${testDir}/residentialEntrances.geojson`);
expect(fileContent).not.toBeNull();
let expectedFeatures = JSON.parse(fileContent.toString() as string).features;
expectedFeatures = expectedFeatures.map(f => {return {...f , id: expect.any(String)}});
expectedFeatures = expectedFeatures.map((f) => { return { ...f, id: expect.any(String) }; });
expect(residentialEntrances.sort(geojsonCompare)).toEqual(expectedFeatures.sort(geojsonCompare));

// Validte the zones data
fileContent = fs.readFileSync(`${__dirname}/imports/${testDir}/residentialZones.geojson`);
expect(fileContent).not.toBeNull();
expectedFeatures = JSON.parse(fileContent.toString() as string).features;
expectedFeatures = expectedFeatures.map(f => {return {...f , id: expect.any(String)}});
expectedFeatures = expectedFeatures.map((f) => { return { ...f, id: expect.any(String) }; });
expect(zonesWithResidences.sort(geojsonCompare)).toEqual(expectedFeatures.sort(geojsonCompare));

});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"entrance_type": "entrance",
"retirement_home": false,
"from_landrole": false,
"area": 242.66835859963368
"area": 242.12624982520123
},
"geometry": {
"type": "Point",
Expand All @@ -27,7 +27,7 @@
"entrance_type": "entrance",
"retirement_home": false,
"from_landrole": false,
"area": 242.66835859963368
"area": 242.12624982520123
},
"geometry": {
"type": "Point",
Expand All @@ -44,7 +44,7 @@
"entrance_type": "entrance",
"retirement_home": true,
"from_landrole": false,
"area": 276.0667297772118
"area": 275.450010732325
},
"geometry": {
"type": "Point",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"entrance_type": "entrance",
"retirement_home": false,
"from_landrole": false,
"area": 242.66835859963368
"area": 242.12624982520123
},
"geometry": {
"type": "Point",
Expand All @@ -27,7 +27,7 @@
"entrance_type": "entrance",
"retirement_home": false,
"from_landrole": false,
"area": 242.66835859963368
"area": 242.12624982520123
},
"geometry": {
"type": "Point",
Expand All @@ -44,7 +44,7 @@
"entrance_type": "entrance",
"retirement_home": true,
"from_landrole": false,
"area": 276.0667297772118
"area": 275.450010732325
},
"geometry": {
"type": "Point",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"entrance_type": "entrance",
"retirement_home": false,
"from_landrole": false,
"area": 242.66835859963368
"area": 242.12624982520123
},
"geometry": {
"type": "Point",
Expand All @@ -27,7 +27,7 @@
"entrance_type": "entrance",
"retirement_home": false,
"from_landrole": false,
"area": 242.66835859963368
"area": 242.12624982520123
},
"geometry": {
"type": "Point",
Expand All @@ -44,7 +44,7 @@
"entrance_type": "entrance",
"retirement_home": true,
"from_landrole": false,
"area": 276.0667297772118
"area": 275.450010732325
},
"geometry": {
"type": "Point",
Expand Down
4 changes: 1 addition & 3 deletions packages/chaire-lib-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@mapbox/mapbox-gl-draw": "^1.3.0",
"@turf/helpers": "^6.1.4",
"@turf/length": "^6.0.2",
"@turf/turf": "^6.3.0",
"@turf/turf": "^7.1.0",
"@types/node": "^17.0.38",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/transition-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"format": "prettier-eslint $PWD/'src/**/*.{ts,tsx}' --write"
},
"dependencies": {
"@turf/turf": "^6.3.0",
"@turf/turf": "^7.1.0",
"capnp-ts": "^0.4.0",
"chaire-lib-backend": "^0.2.2",
"chaire-lib-common": "^0.2.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ErrorMessage } from 'chaire-lib-common/lib/utils/TrError';
import {
length as turfLength,
cleanCoords as turfCleanCoords,
pointOnLine as turfNearestPointOnLine,
nearestPointOnLine as turfNearestPointOnLine,
lineSliceAlong as turfLineSliceAlong,
helpers as turfHelpers
} from '@turf/turf';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import { GenericAttributes } from 'chaire-lib-common/lib/utils/objects/GenericOb
import * as Status from 'chaire-lib-common/lib/utils/Status';
import TrError from 'chaire-lib-common/lib/utils/TrError';
import { isSocketIo } from '../../api/socketUtils';
import { FeatureCollection } from '@turf/turf';

interface TransitObjectDataHandler {
lowerCaseName: string;
Expand All @@ -46,7 +45,9 @@ interface TransitObjectDataHandler {
delete: (socket: EventEmitter, id: string, customCachePath: string | undefined) => Promise<Record<string, any>>;
geojsonCollection?: (
params?
) => Promise<Status.Status<{ type: 'geojson'; geojson: FeatureCollection } | { type: 'geobuf'; geobuf: Buffer }>>;
) => Promise<
Status.Status<{ type: 'geojson'; geojson: GeoJSON.FeatureCollection } | { type: 'geobuf'; geobuf: Buffer }>
>;
collection?: (dataSourceId) => Promise<Record<string, any>>;
saveCache?: (attributes) => Promise<Record<string, any>>;
deleteCache?: (id: string, customCachePath: string | undefined) => Promise<Record<string, any>>;
Expand Down
3 changes: 1 addition & 2 deletions packages/transition-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"dependencies": {
"@mapbox/polyline": "^1.1.1",
"@turf/turf": "^6.3.0",
"@turf/turf": "^7.1.0",
"@types/node": "^17.0.38",
"chaire-lib-common": "^0.2.2",
"child_process": "^1.0.2",
Expand All @@ -45,7 +45,6 @@
"yargs": "^16.1.1"
},
"devDependencies": {
"@turf/transform-translate": "^5.1.5",
"@types/geojson": "^7946.0.7",
"@types/geokdbush": "^1.1.2",
"@types/jest": "^29.5.12",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* License text available at https://opensource.org/licenses/MIT
*/
import geokdbush from 'geokdbush';
import { lineIntersect as turfLineIntersect, distance as turfDistance } from '@turf/turf';
import { lineIntersect as turfLineIntersect, distance as turfDistance, point as turfPoint } from '@turf/turf';
import { EventEmitter } from 'events';

import TrError from 'chaire-lib-common/lib/utils/TrError';
Expand Down Expand Up @@ -35,6 +35,49 @@ const placesInBirdRadiusMeters = (node: Node, placeCollection: PlaceCollection,
return placesInBirdRadius;
};

/**
* Find the touching point between two lines.
* It will return point only if the first line as a common point
* with second line and both are extremities.
* @param line1: a GeoJSON.Feature<GeoJSON.LineString>
* @param line2: a GeoJSON.Feature<GeoJSON.LineString>
* @returns a GeoJSON.Feature<GeoJSON.Point> or undefined
*/
const findTouchingPoint = (
line1: GeoJSON.Feature<GeoJSON.LineString>,
line2: GeoJSON.Feature<GeoJSON.LineString>
): GeoJSON.Feature<GeoJSON.Point> | undefined => {
// Extract start and end points of both lines
const line1StartCoordinates = line1.geometry.coordinates[0] as GeoJSON.Position;
const line1EndCoordinates = line1.geometry.coordinates[line1.geometry.coordinates.length - 1] as GeoJSON.Position;
const line2StartCoordinates = line2.geometry.coordinates[0] as GeoJSON.Position;
const line2EndCoordinates = line2.geometry.coordinates[line2.geometry.coordinates.length - 1] as GeoJSON.Position;

// Helper function to check if two points are equal (within a small epsilon)
const arePointsEqual = (p1: GeoJSON.Position, p2: GeoJSON.Position): boolean => {
return p1[0] === p2[0] && p1[1] === p2[1];
};

// Check all possible endpoint combinations
if (arePointsEqual(line1StartCoordinates, line2StartCoordinates)) {
return turfPoint(line1StartCoordinates) as GeoJSON.Feature<GeoJSON.Point>;
}

if (arePointsEqual(line1StartCoordinates, line2EndCoordinates)) {
return turfPoint(line1StartCoordinates) as GeoJSON.Feature<GeoJSON.Point>;
}

if (arePointsEqual(line1EndCoordinates, line2StartCoordinates)) {
return turfPoint(line1EndCoordinates) as GeoJSON.Feature<GeoJSON.Point>;
}

if (arePointsEqual(line1EndCoordinates, line2EndCoordinates)) {
return turfPoint(line1EndCoordinates) as GeoJSON.Feature<GeoJSON.Point>;
}

return undefined;
};

/**
* Get the places that are in walking distance of the node.
* This function does not change the node.
Expand Down Expand Up @@ -167,7 +210,16 @@ export const proposeNames = async (
street1Name !== street2Name &&
!distanceFromNodeByStreetIntersectionName[reversedIntersectionName]
) {
/*
new version of turf.lineIntersect (since v7) does not return the touching point for
lines that are touching at extremities, so we need to find the touching point manually:
See https://github.com/Turfjs/turf/issues/2667
*/
const intersectionPoints = turfLineIntersect(street1, street2);
const touchingPoint = findTouchingPoint(street1, street2);
if (touchingPoint) {
intersectionPoints.features.push(touchingPoint);
}
if (intersectionPoints.features.length > 0) {
for (let k = 0, countK = intersectionPoints.features.length; k < countK; k++) {
const intersectionPoint = intersectionPoints.features[k];
Expand Down
Loading

0 comments on commit afc0edc

Please sign in to comment.