Skip to content

Commit efeae58

Browse files
authored
Merge pull request #630 from ibi-group/refactor-route-layer
Handle new gtfs-lib response for encoded polylines
2 parents 64a4d20 + 893b789 commit efeae58

File tree

13 files changed

+129
-109
lines changed

13 files changed

+129
-109
lines changed

i18n/english.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ components:
676676
ShowAllRoutesOnMapFilter:
677677
fetching: Fetching
678678
showAllRoutesOnMap: Show all routes
679-
tooManyShapeRecords: Too many shapes to display
679+
tooManyShapeRecords: large shapes.txt may impact performance
680680
Sidebar:
681681
unknown: Unknown
682682
SnapshotItem:

lib/common/util/gtfs.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// @flow
22

33
import moment from 'moment'
4+
import {decode as decodePolyline} from 'polyline'
5+
6+
type GraphQLShape = {polyline: string, shape_id: string}
47

58
/**
69
* @param {number} seconds Seconds after midnight
@@ -36,3 +39,16 @@ export function secondsAfterMidnightToHHMM (seconds: ?(number | string)): string
3639
export function humanizeSeconds (seconds: number): string {
3740
return moment.duration(seconds, 'seconds').humanize()
3841
}
42+
43+
/**
44+
* Array map function to decode a GraphQL encoded shape polyline.
45+
*/
46+
export function decodeShapePolylines (shape: GraphQLShape) {
47+
return {
48+
id: shape.shape_id,
49+
// Decode polyline and coords divide by ten (gtfs-lib
50+
// simplification level requires this).
51+
latLngs: decodePolyline(shape.polyline)
52+
.map(coords => ([coords[0] / 10, coords[1] / 10]))
53+
}
54+
}

lib/editor/actions/editor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const setEditorCheckIn = createAction(
4040
'SET_EDITOR_CHECK_IN',
4141
(payload: LockState) => payload
4242
)
43-
const showEditorModal = createVoidPayloadAction('SHOW_EDITOR_MODAL')
43+
export const showEditorModal = createVoidPayloadAction('SHOW_EDITOR_MODAL')
4444

4545
export type EditorActions = ActionType<typeof createGtfsEntity> |
4646
ActionType<typeof receiveBaseGtfs> |

lib/editor/actions/tripPattern.js

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import {ActionCreators} from 'redux-undo'
55
import {toast} from 'react-toastify'
66

77
import {resetActiveGtfsEntity, savedGtfsEntity, updateActiveGtfsEntity, updateEditSetting} from './active'
8-
import {createVoidPayloadAction, secureFetch} from '../../common/actions'
8+
import {createVoidPayloadAction, fetchGraphQL, secureFetch} from '../../common/actions'
99
import {snakeCaseKeys} from '../../common/util/map-keys'
1010
import {generateUID} from '../../common/util/util'
11-
import {fetchGTFSEntities} from '../../manager/actions/versions'
11+
import {showEditorModal} from './editor'
12+
import {shapes} from '../../gtfs/util/graphql'
13+
import {fetchGTFSEntities, receiveGTFSEntities} from '../../manager/actions/versions'
1214
import {fetchTripCounts} from './trip'
1315
import {getEditorNamespace} from '../util/gtfs'
1416
import {resequenceStops, resequenceShapePoints} from '../util/map'
@@ -17,6 +19,7 @@ import {entityIsNew} from '../util/objects'
1719
import type {ControlPoint, Pattern, PatternStop} from '../../types'
1820
import type {dispatchFn, getStateFn} from '../../types/reducers'
1921

22+
const fetchingTripPatterns = createVoidPayloadAction('FETCHING_TRIP_PATTERNS')
2023
const savedTripPattern = createVoidPayloadAction('SAVED_TRIP_PATTERN')
2124
export const setActivePatternSegment = createAction(
2225
'SET_ACTIVE_PATTERN_SEGMENT',
@@ -118,8 +121,25 @@ export function togglePatternEditing () {
118121
*/
119122
export function fetchTripPatterns (feedId: string) {
120123
return function (dispatch: dispatchFn, getState: getStateFn) {
124+
dispatch(fetchingTripPatterns())
121125
const namespace = getEditorNamespace(feedId, getState())
122-
return dispatch(fetchGTFSEntities({namespace, type: 'pattern', editor: true}))
126+
if (!namespace) {
127+
console.error('Cannot fetch GTFS for undefined or null namespace')
128+
dispatch(showEditorModal())
129+
return
130+
}
131+
const variables = {namespace}
132+
return dispatch(fetchGraphQL({query: shapes, variables}))
133+
.then(data => dispatch(
134+
receiveGTFSEntities({
135+
namespace,
136+
component: 'pattern',
137+
data,
138+
editor: true,
139+
id: null,
140+
replaceNew: true
141+
})
142+
))
123143
}
124144
}
125145

lib/editor/components/map/EditorMapLayersControl.js

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// @flow
22

3-
import React, {Component} from 'react'
4-
import {Browser} from 'leaflet'
3+
import React, {PureComponent} from 'react'
4+
import L from 'leaflet'
55
import {
6+
FeatureGroup,
67
LayersControl,
8+
Polyline,
79
TileLayer
810
} from 'react-leaflet'
911

@@ -23,37 +25,34 @@ type Props = {
2325

2426
type OverlayItem = {component: any, name: string}
2527

26-
export default class EditorMapLayersControl extends Component<Props> {
28+
export default class EditorMapLayersControl extends PureComponent<Props> {
2729
render () {
28-
const { stops, user } = this.props
30+
const canvas = L.canvas()
31+
const { tripPatterns, stops, user } = this.props
2932
const { BaseLayer, Overlay } = LayersControl
3033
const OVERLAYS: Array<OverlayItem> = [
31-
// FIXME: Do not show trip pattern overlay because it can cause out of
32-
// memory issues for very large GTFS feeds.
33-
// {
34-
// name: 'Route alignments',
35-
// component: (
36-
// <FeatureGroup>
37-
// {tripPatterns
38-
// ? tripPatterns.map((tp) => {
39-
// if (!tp.latLngs) return null
40-
// return (
41-
// <Polyline
42-
// key={tp.id}
43-
// positions={tp.latLngs}
44-
// weight={2}
45-
// color='#888'>
46-
// <Tooltip sticky>
47-
// <span>{tp.name} ({tp.route_id})</span>
48-
// </Tooltip>
49-
// </Polyline>
50-
// )
51-
// })
52-
// : null
53-
// }
54-
// </FeatureGroup>
55-
// )
56-
// },
34+
{
35+
name: 'Route alignments',
36+
component: (
37+
<FeatureGroup>
38+
{tripPatterns
39+
? tripPatterns.map((tp) => {
40+
if (!tp.latLngs) return null
41+
return (
42+
<Polyline
43+
color='#888'
44+
interactive={false}
45+
key={tp.id}
46+
positions={tp.latLngs}
47+
renderer={canvas}
48+
weight={2} />
49+
)
50+
})
51+
: null
52+
}
53+
</FeatureGroup>
54+
)
55+
},
5756
{
5857
name: 'Stop locations',
5958
component: <StopLayer key='stop-layer' stops={stops} />
@@ -77,7 +76,7 @@ export default class EditorMapLayersControl extends Component<Props> {
7776
name='Route layer'
7877
key='route-layer'>
7978
<TileLayer
80-
url={`https://s3.amazonaws.com/datatools-nysdot/tiles/{z}_{x}_{y}${Browser.retina ? '@2x' : ''}.png`} />
79+
url={`https://s3.amazonaws.com/datatools-nysdot/tiles/{z}_{x}_{y}${L.Browser.retina ? '@2x' : ''}.png`} />
8180
</Overlay>
8281
}
8382
{OVERLAYS.map((overlay: OverlayItem, i: number) => (

lib/editor/containers/ActiveGtfsEditor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const mapStateToProps = (state: AppState, ownProps: Props) => {
6565
const {activeEntityId, subEntityId} = getIdsFromParams(ownProps.routeParams)
6666
const {data, editSettings: editSettingsState, mapState} = state.editor
6767
const {present: editSettings} = editSettingsState
68-
const {active, tables, status} = data
68+
const {active, tables, tripPatterns, status} = data
6969
// FIXME: entityId is now a non-string line number and somewhere the number is
7070
// being cast to a string.
7171
const activeEntity = active.entity && active.entity.id === activeEntityId
@@ -130,6 +130,7 @@ const mapStateToProps = (state: AppState, ownProps: Props) => {
130130
tableData: tables,
131131
tableView,
132132
timetableStatus,
133+
tripPatterns,
133134
user,
134135
validationErrors
135136
}

lib/editor/reducers/data.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import update from 'react-addons-update'
44
import clone from 'lodash/cloneDeep'
55
import SortDirection from 'react-virtualized/dist/commonjs/Table/SortDirection'
66

7-
import {ENTITY} from '../constants'
7+
import {decodeShapePolylines} from '../../common/util/gtfs'
88
import {defaultSorter} from '../../common/util/util'
9+
import {ENTITY} from '../constants'
910
import {generateNullProps, getTableById, getKeyForId} from '../util/gtfs'
1011
import {getMapToGtfsStrategy, entityIsNew} from '../util/objects'
1112
import {assignDistancesToPatternStops, constructShapePoints} from '../util/map'
@@ -22,6 +23,11 @@ const getUpdateEntityKeys = component => component === 'trippattern'
2223
export const defaultState = {
2324
active: {},
2425
lock: {},
26+
sort: {
27+
key: 'name',
28+
direction: SortDirection.ASC
29+
},
30+
status: {},
2531
tables: {
2632
agency: [],
2733
calendar: [],
@@ -36,11 +42,7 @@ export const defaultState = {
3642
service_id: []
3743
}
3844
},
39-
sort: {
40-
key: 'name',
41-
direction: SortDirection.ASC
42-
},
43-
status: {}
45+
tripPatterns: null
4446
}
4547

4648
/* eslint-disable complexity */
@@ -51,6 +53,10 @@ const data = (state: DataState = defaultState, action: Action): DataState => {
5153
return update(state, {active: {$merge: {feedSourceId}}})
5254
case 'RECEIVE_BASE_GTFS': {
5355
const feed = clone(action.payload.feed)
56+
if (!feed) {
57+
console.warn('Could not fetch base GTFS!')
58+
return state
59+
}
5460
if (feed.feed_info.length === 0) {
5561
console.warn(`No feed info found. Adding feed info with null values.`)
5662
feed.feed_info.push(generateNullProps('feedinfo'))
@@ -223,6 +229,11 @@ const data = (state: DataState = defaultState, action: Action): DataState => {
223229
if (!tableName) return state
224230
return update(state, {tables: {[tableName]: {$push: [entity]}}})
225231
}
232+
case 'FETCHING_TRIP_PATTERNS': {
233+
// Set value to an empty array to prevent the user from accidentally
234+
// triggering multiple fetches.
235+
return update(state, {tripPatterns: {$set: []}})
236+
}
226237
case 'RECEIVE_GTFS_ENTITIES': {
227238
let activePattern
228239
const {data, editor, component} = action.payload
@@ -235,17 +246,7 @@ const data = (state: DataState = defaultState, action: Action): DataState => {
235246
// Handle trip patterns to be drawn as overlay layer in editor
236247
return update(state, {
237248
tripPatterns: {$set:
238-
data.feed.patterns.map(pattern => {
239-
return {
240-
id: pattern.id,
241-
name: pattern.name,
242-
route_id: pattern.route_id,
243-
latLngs: pattern.shape_points
244-
? (pattern.shape_points.map(sp =>
245-
({lon: sp.shape_pt_lon, lat: sp.shape_pt_lat})))
246-
: null
247-
}
248-
})
249+
data.feed.shapes_as_polylines.map(decodeShapePolylines)
249250
}
250251
})
251252
}

lib/gtfs/actions/shapes.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {createAction, type ActionType} from 'redux-actions'
44

55
import {createVoidPayloadAction, fetchGraphQL} from '../../common/actions'
6+
import {decodeShapePolylines} from '../../common/util/gtfs'
67
import {shapes} from '../../gtfs/util/graphql'
78
import {updateRoutesOnMapDisplay} from './filter'
89

@@ -34,13 +35,8 @@ export function toggleShowAllRoutesOnMap (namespace: string) {
3435
dispatch(fetchingShapes())
3536
return dispatch(fetchGraphQL({query: shapes, variables: {namespace}}))
3637
.then(data => {
37-
const {patterns} = data.feed
38-
const shapes = patterns.map(pattern => {
39-
return pattern.shape.map(point => [
40-
point.shape_pt_lat,
41-
point.shape_pt_lon
42-
])
43-
})
38+
const {shapes_as_polylines: encodedShapes} = data.feed
39+
const shapes = encodedShapes.map(decodeShapePolylines)
4440
dispatch(receiveShapes(shapes))
4541
})
4642
}

0 commit comments

Comments
 (0)