1
1
import React from 'react' ;
2
2
import { useSelector } from 'react-redux' ;
3
3
import { Layer , Source , Marker } from 'react-map-gl' ;
4
+ import along from '@turf/along' ;
4
5
import lineSliceAlong from '@turf/line-slice-along' ;
5
6
import length from '@turf/length' ;
7
+ import bezierSpline from '@turf/bezier-spline' ;
8
+ import transformTranslate from '@turf/transform-translate' ;
9
+ import { Point , polygon , lineString } from '@turf/helpers' ;
6
10
import { Feature , LineString } from 'geojson' ;
7
11
import cx from 'classnames' ;
8
- import { get } from 'lodash' ;
12
+ import { mapValues , get } from 'lodash' ;
9
13
10
14
import { RootState } from 'reducers' ;
11
15
import { Viewport } from 'reducers/map' ;
12
16
import { AllowancesSetting } from 'reducers/osrdsimulation' ;
13
17
import { datetime2time } from 'utils/timeManipulation' ;
14
18
import { boundedValue } from 'utils/numbers' ;
19
+ import { getCurrentBearing } from 'utils/geometry' ;
15
20
import { TrainPosition } from './types' ;
16
21
17
22
function getFill ( isSelectedTrain : boolean , ecoBlocks : boolean ) {
@@ -46,31 +51,135 @@ function getSpeedAndTimeLabel(isSelectedTrain: boolean, ecoBlocks: boolean, poin
46
51
) ;
47
52
}
48
53
54
+ interface TriangleSideDimensions {
55
+ left : number ;
56
+ right : number ;
57
+ up : number ;
58
+ upWidth : number ;
59
+ down : number ;
60
+ }
61
+
49
62
// When the train is backward, lineSliceAlong will crash. we need to have head and tail in the right order
50
- export function makeDisplayedHeadAndTail ( point : TrainPosition , geojsonPath : Feature < LineString > ) {
63
+ export function makeDisplayedHeadAndTail (
64
+ point : TrainPosition ,
65
+ geojsonPath : Feature < LineString > ,
66
+ sideDimensions : {
67
+ head : TriangleSideDimensions ;
68
+ tail : TriangleSideDimensions ;
69
+ }
70
+ ) {
51
71
const pathLength = length ( geojsonPath ) ;
52
- const trueHead = Math . max ( point . tailDistanceAlong , point . headDistanceAlong ) ;
53
- const trueTail = Math . min ( point . tailDistanceAlong , point . headDistanceAlong ) ;
72
+ const [ trueTail , trueHead ] = [ point . tailDistanceAlong , point . headDistanceAlong ] . sort (
73
+ ( a , b ) => a - b
74
+ ) ;
75
+
76
+ const headMinusTriangle = trueHead - sideDimensions . head . up ;
77
+ const tailPlusTriangle = Math . min ( trueTail + sideDimensions . tail . down , headMinusTriangle ) ;
78
+ const boundedHead = boundedValue ( headMinusTriangle , [ 0 , pathLength ] ) ;
79
+ const boundedTail = boundedValue ( tailPlusTriangle , [ 0 , pathLength ] ) ;
80
+
81
+ const headPosition = along ( geojsonPath , boundedHead ) ;
82
+ const tailPosition = along ( geojsonPath , boundedTail ) ;
54
83
return {
55
- head : boundedValue ( trueHead , 0 , pathLength ) ,
56
- tail : boundedValue ( trueTail , 0 , pathLength ) ,
84
+ headDistance : boundedHead ,
85
+ tailDistance : boundedTail ,
86
+ headPosition,
87
+ tailPosition,
57
88
} ;
58
89
}
59
90
60
- function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming ( viewport : Viewport , threshold = 12 ) {
91
+ function getzoomPowerOf2LengthFactor ( viewport : Viewport , threshold = 12 ) {
61
92
return 2 ** ( threshold - viewport ?. zoom ) ;
62
93
}
63
94
95
+ function getTriangleSideDimensions ( zoomLengthFactor : number , size = 1 ) {
96
+ const scaleNumber = ( x : number ) => x * zoomLengthFactor * size ;
97
+ const head = {
98
+ left : 0.05 ,
99
+ right : 0.05 ,
100
+ up : 0.1 ,
101
+ upWidth : 0.019 ,
102
+ down : 0.02 ,
103
+ } ;
104
+ const tail = {
105
+ left : 0.05 ,
106
+ right : 0.05 ,
107
+ up : 0.05 ,
108
+ upWidth : 0.019 ,
109
+ down : 0.02 ,
110
+ } ;
111
+ return {
112
+ head : mapValues ( head , scaleNumber ) ,
113
+ tail : mapValues ( tail , scaleNumber ) ,
114
+ } ;
115
+ }
116
+
117
+ function getHeadTriangle (
118
+ trainGeoJsonPath : Feature < LineString > ,
119
+ position : Feature < Point > ,
120
+ sideDimensions : Record < string , number >
121
+ ) {
122
+ const bearing = getCurrentBearing ( trainGeoJsonPath ) ;
123
+ const left = transformTranslate ( position , sideDimensions . left , bearing - 90 ) ;
124
+ const right = transformTranslate ( position , sideDimensions . right , bearing + 90 ) ;
125
+ const up = transformTranslate ( position , sideDimensions . up , bearing ) ;
126
+ const down = transformTranslate ( position , sideDimensions . down , bearing + 180 ) ;
127
+ const upLeft = transformTranslate ( up , sideDimensions . upWidth , bearing - 90 ) ;
128
+ const upRight = transformTranslate ( up , sideDimensions . upWidth , bearing + 90 ) ;
129
+ const coordinates = [
130
+ down . geometry . coordinates ,
131
+ left . geometry . coordinates ,
132
+ upLeft . geometry . coordinates ,
133
+ upRight . geometry . coordinates ,
134
+ right . geometry . coordinates ,
135
+ down . geometry . coordinates ,
136
+ ] ;
137
+ const contour = lineString ( coordinates ) ;
138
+ const bezier = bezierSpline ( contour ) ;
139
+ const triangle = polygon ( [ bezier . geometry . coordinates ] ) ;
140
+ return triangle ;
141
+ }
142
+
143
+ function getTrainGeoJsonPath (
144
+ geojsonPath : Feature < LineString > ,
145
+ tailDistance : number ,
146
+ headDistance : number
147
+ ) {
148
+ const threshold = 0.0005 ;
149
+ if ( headDistance - tailDistance > threshold ) {
150
+ return lineSliceAlong ( geojsonPath , tailDistance , headDistance ) ;
151
+ }
152
+ return lineSliceAlong ( geojsonPath , headDistance - threshold , headDistance ) ;
153
+ }
154
+
155
+ function getTrainPieces (
156
+ point : TrainPosition ,
157
+ geojsonPath : Feature < LineString > ,
158
+ zoomLengthFactor : number
159
+ ) {
160
+ const sideDimensions = getTriangleSideDimensions ( zoomLengthFactor , 2 ) ;
161
+ const { tailDistance, headDistance, headPosition, tailPosition } = makeDisplayedHeadAndTail (
162
+ point ,
163
+ geojsonPath ,
164
+ sideDimensions
165
+ ) ;
166
+ const trainGeoJsonPath = getTrainGeoJsonPath ( geojsonPath , tailDistance , headDistance ) ;
167
+ const headTriangle = getHeadTriangle ( trainGeoJsonPath , headPosition , sideDimensions . head ) ;
168
+ const rearTriangle = getHeadTriangle ( trainGeoJsonPath , tailPosition , sideDimensions . tail ) ;
169
+ return [ trainGeoJsonPath , headTriangle , rearTriangle ] ;
170
+ }
171
+
64
172
interface TrainHoverPositionProps {
65
173
point : TrainPosition ;
66
174
isSelectedTrain ?: boolean ;
67
175
geojsonPath : Feature < LineString > ;
68
176
}
69
177
70
- const shiftFactor = {
71
- long : 1 / 450 ,
72
- lat : 1 / 1000 ,
178
+ const labelShiftFactor = {
179
+ long : 0.005 ,
180
+ lat : 0.0011 ,
73
181
} ;
182
+
74
183
function TrainHoverPosition ( props : TrainHoverPositionProps ) {
75
184
const { point, isSelectedTrain = false , geojsonPath } = props ;
76
185
const { selectedTrain, allowancesSettings } = useSelector (
@@ -84,32 +193,51 @@ function TrainHoverPosition(props: TrainHoverPositionProps) {
84
193
const label = getSpeedAndTimeLabel ( isSelectedTrain , ecoBlocks , point ) ;
85
194
86
195
if ( geojsonPath && point . headDistanceAlong && point . tailDistanceAlong ) {
87
- const zoomLengthFactor = getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming ( viewport ) ;
88
- const { tail, head } = makeDisplayedHeadAndTail ( point , geojsonPath ) ;
89
- const trainGeoJsonPath = lineSliceAlong ( geojsonPath , tail , head ) ;
90
-
196
+ const zoomLengthFactor = getzoomPowerOf2LengthFactor ( viewport ) ;
197
+ const [ trainGeoJsonPath , headTriangle , rearTriangle ] = getTrainPieces (
198
+ point ,
199
+ geojsonPath ,
200
+ zoomLengthFactor
201
+ ) ;
91
202
return (
92
203
< >
93
204
< Marker
94
205
className = "map-search-marker"
95
206
longitude = {
96
- point . headPosition . geometry . coordinates [ 0 ] + zoomLengthFactor * shiftFactor . long
207
+ point . headPosition . geometry . coordinates [ 0 ] + zoomLengthFactor * labelShiftFactor . long
208
+ }
209
+ latitude = {
210
+ point . headPosition . geometry . coordinates [ 1 ] + zoomLengthFactor * labelShiftFactor . lat
97
211
}
98
- latitude = { point . headPosition . geometry . coordinates [ 1 ] + zoomLengthFactor * shiftFactor . lat }
99
212
>
100
213
{ label }
101
214
</ Marker >
215
+ < Source type = "geojson" data = { headTriangle } >
216
+ < Layer
217
+ id = { `${ point . id } -head` }
218
+ type = "fill"
219
+ paint = { {
220
+ 'fill-color' : fill ,
221
+ } }
222
+ />
223
+ </ Source >
224
+ < Source type = "geojson" data = { rearTriangle } >
225
+ < Layer
226
+ id = { `${ point . id } -rear` }
227
+ type = "fill"
228
+ paint = { {
229
+ 'fill-color' : fill ,
230
+ } }
231
+ />
232
+ </ Source >
102
233
< Source type = "geojson" data = { trainGeoJsonPath } >
103
234
< Layer
104
235
id = { `${ point . id } -path` }
105
236
type = "line"
106
237
paint = { {
107
- 'line-width' : 8 ,
238
+ 'line-width' : 16 ,
108
239
'line-color' : fill ,
109
240
} }
110
- layout = { {
111
- 'line-cap' : 'round' ,
112
- } }
113
241
/>
114
242
</ Source >
115
243
</ >
0 commit comments