Skip to content

Commit 6399b56

Browse files
evansirokylandonreed
authored andcommitted
fix(editor): improve pattern shape editing
refactor some other things to make them easier to read
1 parent 512734f commit 6399b56

26 files changed

+1639
-1699
lines changed

lib/common/util/util.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export function retrievalMethodString (method) {
2323
}
2424

2525
export function generateUID () {
26+
// TODO: replace with better UID generator if possible
27+
// this has a 1 in 1.7 million chance of a collision
2628
return ('0000' + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4)
2729
}
2830

lib/editor/actions/map/index.js

Lines changed: 142 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1+
import ll from '@conveyal/lonlat'
2+
import uniqueId from 'lodash.uniqueid'
13
import shp from 'shpjs'
4+
import lineDistance from 'turf-line-distance'
25
import lineSlice from 'turf-line-slice'
36
import point from 'turf-point'
4-
import lineDistance from 'turf-line-distance'
5-
import ll from '@conveyal/lonlat'
67

78
import {updateActiveGtfsEntity, saveActiveGtfsEntity} from '../active'
8-
import {getControlPointSnap, recalculatePatternCoordinates, newControlPoint} from '../../util/map'
9+
import {setErrorMessage} from '../../../manager/actions/status'
910
import {polyline as getPolyline} from '../../../scenario-editor/utils/valhalla'
10-
11-
export function updateMapSetting (props) {
12-
return {
13-
type: 'UPDATE_MAP_SETTING',
14-
props
15-
}
16-
}
11+
import {recalculateShape, newControlPoint} from '../../util/map'
1712

1813
export function addControlPoint (controlPoint, index) {
1914
return {
@@ -23,48 +18,6 @@ export function addControlPoint (controlPoint, index) {
2318
}
2419
}
2520

26-
export function handleControlPointDragEnd (index, controlPoint, evt, pattern) {
27-
return function (dispatch, getState) {
28-
const coordinates = getState().editor.editSettings.patternCoordinates
29-
const controlPointGeoJson = evt.target.toGeoJSON()
30-
const { snap, distTraveled } = getControlPointSnap(controlPointGeoJson, coordinates)
31-
dispatch(updateActiveGtfsEntity(pattern, 'trippattern', {shape: {type: 'LineString', coordinates}}))
32-
dispatch(updateControlPoint(index, snap, distTraveled))
33-
}
34-
}
35-
36-
export function handleControlPointDrag (controlPoints, index, latlng, patternShape) {
37-
return function (dispatch, getState) {
38-
const {followStreets} = getState().editor.editSettings
39-
recalculatePatternCoordinates(
40-
controlPoints,
41-
'update',
42-
index,
43-
followStreets,
44-
latlng,
45-
patternShape,
46-
false
47-
)
48-
.then(coords => {
49-
if (coords) {
50-
dispatch(updatePatternCoordinates(coords))
51-
// const coord = ll.toCoordinates(latlng)
52-
// const cpPoint = point(coord)
53-
// const { snap, distTraveled } = getControlPointSnap(cpPoint, coords)
54-
// dispatch(updateControlPoint(index, snap, distTraveled, false))
55-
}
56-
})
57-
}
58-
}
59-
60-
export function removingControlPoint (pattern, index) {
61-
return {
62-
type: 'REMOVE_CONTROL_POINT',
63-
pattern,
64-
index
65-
}
66-
}
67-
6821
export function constructControlPoint (pattern, latlng, controlPoints) {
6922
return function (dispatch, getState) {
7023
// slice line
@@ -91,50 +44,10 @@ export function constructControlPoint (pattern, latlng, controlPoints) {
9144
}
9245
}
9346

94-
export function updatePatternCoordinates (coordinates) {
95-
return {
96-
type: 'UPDATE_PATTERN_COORDINATES',
97-
coordinates
98-
}
99-
}
100-
101-
export function removeControlPoint (controlPoints, index, pattern) {
102-
return async function (dispatch, getState) {
103-
const {followStreets} = getState().editor.editSettings
104-
const coordinates = await recalculatePatternCoordinates(
105-
controlPoints,
106-
'delete',
107-
index,
108-
followStreets,
109-
null,
110-
pattern.shape
111-
)
112-
// update pattern
113-
dispatch(
114-
updateActiveGtfsEntity(pattern, 'trippattern', {
115-
shape: {type: 'LineString', coordinates}
116-
})
117-
)
118-
// remove controlPoint
119-
dispatch(removingControlPoint(pattern, index))
120-
}
121-
}
122-
123-
export function updateControlPoint (index, point, distance, hardUpdate = true) {
124-
return {
125-
type: 'UPDATE_CONTROL_POINT',
126-
index,
127-
point,
128-
distance,
129-
hardUpdate
130-
}
131-
}
132-
133-
function receivedRoutesShapefile (feedSource, geojson) {
47+
function controlPointDragOrEnd (dragId) {
13448
return {
135-
type: 'RECEIVED_ROUTES_SHAPEFILE',
136-
feedSource,
137-
geojson
49+
type: 'CONTROL_POINT_DRAG_START_OR_END',
50+
dragId
13851
}
13952
}
14053

@@ -171,3 +84,137 @@ export function extendPatternToPoint (pattern, endPoint, newEndPoint) {
17184
return shape
17285
}
17386
}
87+
88+
/**
89+
* Calculate a new shape according to newly dragged position of control point
90+
*
91+
* @param {ControlPoint[]} controlPoints
92+
* @param {Number} index Index of the control point being dragged
93+
* @param {Position} latlng Position of drag point
94+
* @param {Object} patternShape
95+
*/
96+
export function handleControlPointDrag (
97+
controlPoints,
98+
index,
99+
latlng,
100+
patternShape
101+
) {
102+
return function (dispatch, getState) {
103+
const {currentDragId, followStreets} = getState().editor.editSettings
104+
recalculateShape({
105+
controlPoints,
106+
defaultToStraightLine: false,
107+
dragId: currentDragId,
108+
editType: 'update',
109+
followStreets,
110+
index,
111+
newPoint: latlng,
112+
patternShape
113+
}).then(result => {
114+
// make sure dragging hasn't already stopped and that coordinates are returned
115+
if (
116+
result.dragId === getState().editor.editSettings.currentDragId &&
117+
result.coordinates
118+
) {
119+
dispatch(updatePatternCoordinates(result.coordinates))
120+
}
121+
})
122+
}
123+
}
124+
125+
export function handleControlPointDragEnd (controlPoints, index, latlng, pattern) {
126+
return function (dispatch, getState) {
127+
// proclaim end of dragging
128+
dispatch(controlPointDragOrEnd())
129+
130+
// recalculate shape for final position
131+
const {followStreets} = getState().editor.editSettings
132+
recalculateShape({
133+
controlPoints,
134+
defaultToStraightLine: false,
135+
editType: 'update',
136+
index,
137+
followStreets,
138+
newPoint: latlng,
139+
patternShape: pattern.shape,
140+
snapControlPointToNewSegment: true
141+
}).then(result => {
142+
if (!result.coordinates) {
143+
dispatch(setErrorMessage(
144+
'An error occurred while trying to recalculate the shape. Please try again.'
145+
))
146+
} else {
147+
dispatch(
148+
updateActiveGtfsEntity(pattern, 'trippattern', {
149+
shape: {type: 'LineString', coordinates: result.coordinates}
150+
})
151+
)
152+
dispatch(updateControlPoints(result.updatedControlPoints))
153+
}
154+
})
155+
}
156+
}
157+
158+
/**
159+
* Save data to store that dragging is in progress
160+
*
161+
* @param {ControlPoint} controlPoint
162+
* @return {Action}
163+
*/
164+
export function handleControlPointDragStart (controlPoint) {
165+
return controlPointDragOrEnd(uniqueId(controlPoint.id))
166+
}
167+
168+
function receivedRoutesShapefile (feedSource, geojson) {
169+
return {
170+
type: 'RECEIVED_ROUTES_SHAPEFILE',
171+
feedSource,
172+
geojson
173+
}
174+
}
175+
176+
export function removeControlPoint (controlPoints, index, pattern) {
177+
return async function (dispatch, getState) {
178+
const {followStreets} = getState().editor.editSettings
179+
const {
180+
coordinates,
181+
updatedControlPoints
182+
} = await recalculateShape({
183+
controlPoints,
184+
editType: 'delete',
185+
index,
186+
followStreets,
187+
patternShape: pattern.shape
188+
})
189+
// TODO-CONTROL-POINT-FIX: update distance of other control points
190+
// update pattern
191+
dispatch(
192+
updateActiveGtfsEntity(pattern, 'trippattern', {
193+
shape: {type: 'LineString', coordinates}
194+
})
195+
)
196+
// update controlPoints
197+
dispatch(updateControlPoints(updatedControlPoints))
198+
}
199+
}
200+
201+
export function updateControlPoints (newControlPoints) {
202+
return {
203+
type: 'UPDATE_CONTROL_POINTS',
204+
newControlPoints
205+
}
206+
}
207+
208+
export function updateMapSetting (props) {
209+
return {
210+
type: 'UPDATE_MAP_SETTING',
211+
props
212+
}
213+
}
214+
215+
export function updatePatternCoordinates (coordinates) {
216+
return {
217+
type: 'UPDATE_PATTERN_COORDINATES',
218+
coordinates
219+
}
220+
}

lib/editor/actions/map/stopStrategies.js

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {getSegment} from '../../../scenario-editor/utils/valhalla'
1111
import {
1212
constructStop,
1313
stopToStopTime,
14-
recalculatePatternCoordinates,
14+
recalculateShape,
1515
getPatternEndPoint,
1616
street,
1717
constructPoint
@@ -56,6 +56,8 @@ export function addStopAtIntersection (latlng, activePattern) {
5656
const last = json.data.features[json.data.features.length - 1]
5757
const start = constructPoint(endPoint)
5858
const end = constructPoint(last.geometry.coordinates[last.geometry.coordinates.length - 1])
59+
// TODO-lineSlice: refactor below code to not use lineSlice
60+
// the current code may have undesired results in cases where the shape overlaps itself
5961
const trimmed = lineSlice(start, end, {type: 'Feature', geometry: {type: 'LineString', coordinates: extension}})
6062
const { afterIntersection, intersectionStep, distanceFromIntersection } = getState().editor.editSettings
6163
const shape = {type: 'LineString', coordinates: [...activePattern.shape.coordinates, ...trimmed.geometry.coordinates]}
@@ -83,6 +85,8 @@ export function addStopAtIntersection (latlng, activePattern) {
8385
const end = afterIntersection
8486
? constructPoint(shape.coordinates[shape.coordinates.length - 1])
8587
: constructPoint(feature.geometry.coordinates[feature.geometry.coordinates.length - 1])
88+
// TODO-lineSlice: refactor below code to use only relevant section of shape with lineSlice
89+
// the current code may have undesired results in cases where the shape overlaps itself
8690
const lineFromPoint = lineSlice(start, end, {type: 'Feature', geometry: shape})
8791
const stopLocation = along(lineFromPoint, distanceFromIntersection / 1000, 'kilometers')
8892
const latlng = ll.toLeaflet(stopLocation.geometry.coordinates)
@@ -199,20 +203,8 @@ export function addStopToPattern (pattern, stop, index) {
199203
}
200204
}
201205

202-
function removingStopFromPattern (pattern, stop, index, controlPoints) {
203-
return {
204-
type: 'REMOVING_STOP_FROM_PATTERN',
205-
pattern,
206-
stop,
207-
index,
208-
controlPoints
209-
}
210-
}
211-
212206
export function removeStopFromPattern (pattern, stop, index, controlPoints) {
213207
return async function (dispatch, getState) {
214-
dispatch(removingStopFromPattern(pattern, stop, index, controlPoints))
215-
216208
if (!controlPoints) {
217209
const controlPointsFromState = getState().editor.editSettings
218210
.controlPoints
@@ -243,26 +235,33 @@ export function removeStopFromPattern (pattern, stop, index, controlPoints) {
243235

244236
let coordinates
245237
try {
246-
coordinates = await recalculatePatternCoordinates(
238+
const result = await recalculateShape({
247239
controlPoints,
248-
'delete',
249-
cpIndex,
240+
editType: 'delete',
241+
index: cpIndex,
250242
followStreets,
251-
null,
252-
pattern.shape
253-
)
243+
patternShape: pattern.shape
244+
})
245+
coordinates = result.coordinates
254246
} catch (err) {
255247
console.log(err)
256248
dispatch(setErrorMessage(`Could not remove pattern from stop: ${err}`))
257249
return
258250
}
251+
if (!coordinates) {
252+
dispatch(setErrorMessage(
253+
'An error occurred while trying to recalculate the shape. Please try again.'
254+
))
255+
return
256+
}
259257
shape = {type: 'LineString', coordinates}
260258
}
261259
const patternStops = [...pattern.patternStops]
262260
patternStops.splice(index, 1)
263261
dispatch(
264262
updateActiveGtfsEntity(pattern, 'trippattern', {patternStops, shape})
265263
)
264+
// saving the trip pattern will recalculate the control points
266265
dispatch(saveActiveGtfsEntity('trippattern'))
267266
}
268267
}

lib/editor/actions/tripPattern.js

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,22 @@ export function undoingActiveTripPatternEdits (lastActionIndex, lastActionType,
7676

7777
export function undoActiveTripPatternEdits () {
7878
return function (dispatch, getState) {
79-
const lastActionIndex = getState().editor.editSettings.actions.length - 1
80-
const lastActionType = getState().editor.editSettings.actions[lastActionIndex]
81-
const lastCoordinatesIndex = getState().editor.editSettings.coordinatesHistory.length - 1
82-
const lastControlPointsIndex = getState().editor.editSettings.controlPoints.length - 1
83-
const lastCoordinates = getState().editor.editSettings.coordinatesHistory[lastCoordinatesIndex]
84-
dispatch(undoingActiveTripPatternEdits(lastActionIndex, lastActionType, lastCoordinatesIndex, lastControlPointsIndex, lastCoordinates))
79+
const editSettings = getState().editor.editSettings
80+
const lastActionIndex = editSettings.actions.length - 1
81+
const lastActionType = editSettings.actions[lastActionIndex]
82+
const lastCoordinatesIndex = editSettings.coordinatesHistory.length - 1
83+
const lastControlPointsIndex = editSettings.controlPoints.length - 1
84+
const lastCoordinates =
85+
editSettings.coordinatesHistory[lastCoordinatesIndex]
86+
dispatch(
87+
undoingActiveTripPatternEdits(
88+
lastActionIndex,
89+
lastActionType,
90+
lastCoordinatesIndex,
91+
lastControlPointsIndex,
92+
lastCoordinates
93+
)
94+
)
8595
}
8696
}
8797

0 commit comments

Comments
 (0)