Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PB-1377 : fix bug with line crossing Switzerland #1221

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,982 changes: 1,866 additions & 116 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 1 addition & 15 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,7 @@
"@ivanv/vue-collapse-transition": "^1.0.2",
"@mapbox/togeojson": "^0.16.2",
"@popperjs/core": "^2.11.8",
"@turf/area": "^7.2.0",
"@turf/bbox": "^7.2.0",
"@turf/boolean-contains": "^7.2.0",
"@turf/boolean-intersects": "^7.2.0",
"@turf/boolean-point-in-polygon": "^7.2.0",
"@turf/buffer": "^7.2.0",
"@turf/centroid": "^7.2.0",
"@turf/circle": "^7.2.0",
"@turf/distance": "^7.2.0",
"@turf/explode": "^7.2.0",
"@turf/helpers": "^7.2.0",
"@turf/nearest-point": "^7.2.0",
"@turf/point-to-line-distance": "^7.2.0",
"@turf/simplify": "^7.2.0",
"@turf/turf": "^7.2.0",
"animate.css": "^4.1.1",
"axios": "^1.7.9",
"bootstrap": "^5.3.3",
Expand All @@ -78,7 +65,6 @@
"hammerjs": "^2.0.8",
"jquery": "^3.7.1",
"jszip": "^3.10.1",
"liang-barsky": "^1.0.12",
"lodash": "^4.17.21",
"maplibre-gl": "^5.0.1",
"ol": "^10.3.1",
Expand Down
4 changes: 1 addition & 3 deletions src/api/search.api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import bbox from '@turf/bbox'
import center from '@turf/center'
import { points } from '@turf/helpers'
import { bbox, center, points } from '@turf/turf'
import axios from 'axios'
import proj4 from 'proj4'

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

import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import explode from '@turf/explode'
import { point } from '@turf/helpers'
import nearestPoint from '@turf/nearest-point'
import { explode, nearestPoint, point } from '@turf/turf'
import { Feature } from 'ol'
import GeoJSON from 'ol/format/GeoJSON'
import proj4 from 'proj4'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { booleanIntersects } from '@turf/boolean-intersects'
import { circle } from '@turf/circle'
import {
booleanIntersects,
circle,
geometryCollection,
lineString,
multiLineString,
multiPoint,
multiPolygon,
point,
polygon,
} from '@turf/helpers'
} from '@turf/turf'
import { platformModifierKeyOnly } from 'ol/events/condition'
import GeoJSON from 'ol/format/GeoJSON'
import { DragBox } from 'ol/interaction'
Expand Down
3 changes: 1 addition & 2 deletions src/modules/menu/components/LayerCatalogueItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
// importing directly the vue component, see https://github.com/ivanvermeyen/vue-collapse-transition/issues/5
import CollapseTransition from '@ivanv/vue-collapse-transition/src/CollapseTransition.vue'
import booleanContains from '@turf/boolean-contains'
import { polygon } from '@turf/helpers'
import { booleanContains, polygon } from '@turf/turf'
import { computed, onMounted, ref, toRefs, watch } from 'vue'
import { useStore } from 'vuex'

Expand Down
2 changes: 1 addition & 1 deletion src/store/modules/features.store.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import simplify from '@turf/simplify'
import { simplify } from '@turf/turf'
import { containsCoordinate } from 'ol/extent'
import { toRaw } from 'vue'

Expand Down
71 changes: 10 additions & 61 deletions src/utils/coordinates/CoordinateSystemBounds.class.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clip } from 'liang-barsky'
import { bboxPolygon, booleanPointInPolygon, lineSplit, lineString, points } from '@turf/turf'

/**
* Representation of boundaries of a coordinate system (also sometime called extent)
Expand Down Expand Up @@ -85,7 +85,15 @@ export default class CoordinateSystemBounds {
}
// checking if we require splitting
if (coordinates.find((coordinate) => !this.isInBounds(coordinate[0], coordinate[1]))) {
return splitIfOutOfBoundsRecurse(coordinates, this)
const boundsAsPolygon = bboxPolygon(this.flatten)
return lineSplit(lineString(coordinates), boundsAsPolygon).features.map((chunk) => {
return {
coordinates: chunk.geometry.coordinates,
isWithinBounds: points(chunk.geometry.coordinates).features.every((point) =>
booleanPointInPolygon(point, boundsAsPolygon)
),
}
})
}
// no splitting needed, we return the coordinates as they were given
return [
Expand All @@ -96,62 +104,3 @@ export default class CoordinateSystemBounds {
]
}
}

/**
* @param {[Number, Number][]} coordinates
* @param {CoordinateSystemBounds} bounds
* @param {[CoordinatesChunk]} previousChunks
* @param {Boolean} isFirstChunk
* @returns {[CoordinatesChunk]}
*/
function splitIfOutOfBoundsRecurse(coordinates, bounds, previousChunks = [], isFirstChunk = true) {
// for the first chunk, we take the very first coordinate, after that as we add the junction
// to the coordinates, we need to take the second to check if it is in bound
const firstCoordinate = coordinates[isFirstChunk ? 0 : 1]
const isFirstCoordinateInBounds = bounds.isInBounds(firstCoordinate[0], firstCoordinate[1])
// searching for the next coordinates where the split will happen (omitting the first coordinate)
const nextCoordinateWithoutSameBounds = coordinates
.slice(1)
.find(
(coordinate) =>
isFirstCoordinateInBounds !== bounds.isInBounds(coordinate[0], coordinate[1])
)
if (!nextCoordinateWithoutSameBounds) {
// end of the recurse loop, nothing more to split, we add the last segment/chunk
return [
...previousChunks,
{
coordinates,
isWithinBounds: isFirstCoordinateInBounds,
},
]
}
const indexOfNextCoord = coordinates.indexOf(nextCoordinateWithoutSameBounds)
const lastCoordinateWithSameBounds = coordinates[indexOfNextCoord - 1]
// adding the coordinate where the boundaries are crossed
let crossing1 = lastCoordinateWithSameBounds.slice(),
crossing2 = nextCoordinateWithoutSameBounds.slice()
clip(
lastCoordinateWithSameBounds,
nextCoordinateWithoutSameBounds,
bounds.flatten,
crossing1,
crossing2
)
// if first coordinate was in bound we have to use crossing2 as our intersection (crossing 1 will be a copy of firstCoordinate)
// it's the opposite if firstCoordinate was out of bounds, we have to use crossing1 as the intersection
const intersection = isFirstCoordinateInBounds ? crossing2 : crossing1
const coordinateLeftToProcess = [intersection, ...coordinates.slice(indexOfNextCoord)]
return splitIfOutOfBoundsRecurse(
coordinateLeftToProcess,
bounds,
[
...previousChunks,
{
coordinates: [...coordinates.slice(0, indexOfNextCoord), intersection],
isWithinBounds: isFirstCoordinateInBounds,
},
],
false
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ describe('CoordinateSystemBounds', () => {
// checking that the split happened on the bounds
const intersectingCoordinate = outOfBoundChunk.coordinates[1]
expect(intersectingCoordinate).to.be.an('Array').of.length(2)
expect(intersectingCoordinate).to.eql([bounds.lowerX - 1, yValue])
expect(intersectingCoordinate).to.eql([bounds.lowerX, yValue])
// next chunk must start by the intersecting coordinate
expect(inBoundChunk).to.haveOwnProperty('isWithinBounds')
expect(inBoundChunk.isWithinBounds).to.be.true
expect(inBoundChunk.coordinates).to.be.an('Array').of.length(4)
const [firstInBoundCoordinate] = inBoundChunk.coordinates
expect(firstInBoundCoordinate).to.be.an('Array').of.length(2)
expect(firstInBoundCoordinate).to.eql([bounds.lowerX - 1, yValue])
expect(firstInBoundCoordinate).to.eql([bounds.lowerX, yValue])
// checking that further coordinates have been correctly copied
coordinatesOverlappingBounds.slice(1).forEach((coordinate, index) => {
expect(inBoundChunk.coordinates[index + 1][0]).to.eq(coordinate[0])
Expand All @@ -88,27 +88,58 @@ describe('CoordinateSystemBounds', () => {
expect(result).to.be.an('Array').of.length(4)
const [firstChunk, secondChunk, thirdChunk, fourthChunk] = result
// first chunk should have two coordinates, the first from the list and the first intersection
expect(firstChunk.isWithinBounds).to.be.false
expect(firstChunk.coordinates).to.be.an('Array').of.length(2)
expect(firstChunk.coordinates[0]).to.eql(coordinatesGoingBackAndForth[0])
expect(firstChunk.coordinates[1]).to.eql(expectedFirstIntersection)
// second chunk should start with the first intersection, then include the second coord
// and finish with the second intersection
expect(secondChunk.coordinates).to.be.an('Array').of.length(3)
expect(secondChunk.isWithinBounds).to.be.true
expect(secondChunk.coordinates[0]).to.eql(expectedFirstIntersection)
expect(secondChunk.coordinates[1]).to.eql(coordinatesGoingBackAndForth[1])
expect(secondChunk.coordinates[2]).to.eql(expectedSecondIntersection)
// third chunk should be : intersection2, coord3, coord4, intersection3
expect(thirdChunk.coordinates).to.be.an('Array').of.length(4)
expect(thirdChunk.isWithinBounds).to.be.false
expect(thirdChunk.coordinates[0]).to.eql(expectedSecondIntersection)
expect(thirdChunk.coordinates[1]).to.eql(coordinatesGoingBackAndForth[2])
expect(thirdChunk.coordinates[2]).to.eql(coordinatesGoingBackAndForth[3])
expect(thirdChunk.coordinates[3]).to.eql(expectedThirdIntersection)
// last chunk should be : intersection3, coord5, coord6
expect(fourthChunk.coordinates).to.be.an('Array').of.length(3)
expect(fourthChunk.isWithinBounds).to.be.true
expect(fourthChunk.coordinates[0]).to.eql(expectedThirdIntersection)
expect(fourthChunk.coordinates[1]).to.eql(coordinatesGoingBackAndForth[4])
expect(fourthChunk.coordinates[2]).to.eql(coordinatesGoingBackAndForth[5])
})
it('splits correctly a line crossing bounds two times in a straight line (no stop inside)', () => {
const coordinatesGoingThrough = [
[-1, 50], // outside
[101, 50], // outside
]
const expectedFirstIntersection = [bounds.lowerX, 50]
const expectedSecondIntersection = [bounds.upperX, 50]

const result = bounds.splitIfOutOfBounds(coordinatesGoingThrough)
expect(result).to.be.an('Array').of.length(3)
const [firstChunk, secondChunk, thirdChunk] = result

expect(firstChunk.isWithinBounds).to.be.false
expect(firstChunk.coordinates).to.be.an('Array').of.length(2)
expect(firstChunk.coordinates[0]).to.eql(coordinatesGoingThrough[0])
expect(firstChunk.coordinates[1]).to.eql(expectedFirstIntersection)

expect(secondChunk.isWithinBounds).to.be.true
expect(secondChunk.coordinates).to.be.an('Array').of.length(2)
expect(secondChunk.coordinates[0]).to.eql(expectedFirstIntersection)
expect(secondChunk.coordinates[1]).to.eql(expectedSecondIntersection)

expect(thirdChunk.isWithinBounds).to.be.false
expect(thirdChunk.coordinates).to.be.an('Array').of.length(2)
expect(thirdChunk.coordinates[0]).to.eql(expectedSecondIntersection)
expect(thirdChunk.coordinates[1]).to.eql(coordinatesGoingThrough[1])
})
})
describe('isInBounds(x, y)', () => {
const testInstance = new CoordinateSystemBounds(-1, 1, -1, 1)
Expand Down
4 changes: 1 addition & 3 deletions src/utils/extentUtils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import bbox from '@turf/bbox'
import buffer from '@turf/buffer'
import { point } from '@turf/helpers'
import { bbox, buffer, point } from '@turf/turf'
import { getIntersection as getExtentIntersection } from 'ol/extent'
import proj4 from 'proj4'

Expand Down
6 changes: 3 additions & 3 deletions src/utils/geoJsonUtils.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import bbox from '@turf/bbox'
import centroid from '@turf/centroid'
import {
bbox,
centroid,
featureCollection,
lineString,
multiLineString,
multiPoint,
multiPolygon,
point,
polygon,
} from '@turf/helpers'
} from '@turf/turf'
import proj4 from 'proj4'
import { reproject } from 'reproject'

Expand Down
2 changes: 1 addition & 1 deletion src/utils/gpxUtils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { gpx as gpxToGeoJSON } from '@mapbox/togeojson'
import bbox from '@turf/bbox'
import { bbox } from '@turf/turf'
import { isEmpty as isExtentEmpty } from 'ol/extent'
import GPX from 'ol/format/GPX'

Expand Down
5 changes: 1 addition & 4 deletions src/utils/identifyOnVectorLayer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import booleanPointInPolygon from '@turf/boolean-point-in-polygon'
import distance from '@turf/distance'
import { point } from '@turf/helpers'
import pointToLineDistance from '@turf/point-to-line-distance'
import { booleanPointInPolygon, distance, point, pointToLineDistance } from '@turf/turf'
import proj4 from 'proj4'
import { reproject } from 'reproject'

Expand Down
Loading