Skip to content
Open
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
95 changes: 49 additions & 46 deletions lib/editor/components/pattern/EditShapePanel.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
// @flow

import Icon from '@conveyal/woonerf/components/icon'
import React, {Component} from 'react'
import {Alert, Button, ButtonGroup, ButtonToolbar, OverlayTrigger, Tooltip} from 'react-bootstrap'
import React, { Component } from 'react'
import { Alert, Button, ButtonGroup, ButtonToolbar, OverlayTrigger, Tooltip } from 'react-bootstrap'
import ll from '@conveyal/lonlat'
import numeral from 'numeral'
import lineDistance from 'turf-line-distance'
import lineString from 'turf-linestring'

import * as activeActions from '../../actions/active'
import * as mapActions from '../../actions/map'
import {ARROW_MAGENTA, PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS} from '../../constants'
import { ARROW_MAGENTA, PATTERN_TO_STOP_DISTANCE_THRESHOLD_METERS } from '../../constants'
import * as tripPatternActions from '../../actions/tripPattern'
import OptionButton from '../../../common/components/OptionButton'
import * as statusActions from '../../../manager/actions/status'
import {polyline as getPolyline} from '../../../scenario-editor/utils/valhalla'
import { polyline as getPolyline } from '../../../scenario-editor/utils/valhalla'
import {
controlPointsFromSegments,
generateControlPointsFromPatternStops,
getPatternDistance,
isValidStopControlPoint
} from '../../util/map'
import type {ControlPoint, GtfsRoute, LatLng, Pattern, GtfsStop} from '../../../types'
import type {EditSettingsUndoState} from '../../../types/reducers'
import type { ControlPoint, GtfsRoute, LatLng, Pattern, GtfsStop } from '../../../types'
import type { EditSettingsUndoState } from '../../../types/reducers'

import EditSettings from './EditSettings'

Expand Down Expand Up @@ -50,19 +50,22 @@ export default class EditShapePanel extends Component<Props> {
* Construct new pattern geometry from the pattern stop locations.
*/
async drawPatternFromStops (pattern: Pattern, stopsCoordinates: Array<LatLng>, followStreets: boolean): Promise<any> {
const {editSettings, saveActiveGtfsEntity, setErrorMessage, updatePatternGeometry} = this.props
const { editSettings, saveActiveGtfsEntity, setErrorMessage, updatePatternGeometry } = this.props
const { avoidMotorways, followRail } = editSettings.present
let patternSegments = []
if (followStreets) {
patternSegments = await getPolyline(stopsCoordinates, true, editSettings.present.avoidMotorways)
} else {
// Construct straight-line segments using stop coordinates
stopsCoordinates
.forEach((stop, i) => {
if (i < stopsCoordinates.length - 1) {
const segment = [ll.toCoordinates(stop), ll.toCoordinates(stopsCoordinates[i + 1])]
patternSegments.push(segment)
}
})
if (followStreets || followRail) {
patternSegments = await getPolyline(stopsCoordinates, true, avoidMotorways, followRail)
}

// Fallback to straight-line segments if routing failed or was not requested
if (!patternSegments || patternSegments.length === 0) {
patternSegments = []
stopsCoordinates.forEach((stop, i) => {
if (i < stopsCoordinates.length - 1) {
const segment = [ll.toCoordinates(stop), ll.toCoordinates(stopsCoordinates[i + 1])]
patternSegments.push(segment)
}
})
}
if (patternSegments && patternSegments.length > 0) {
const controlPoints = controlPointsFromSegments(pattern.patternStops, patternSegments)
Expand All @@ -73,13 +76,13 @@ export default class EditShapePanel extends Component<Props> {
saveActiveGtfsEntity('trippattern')
return true
} else {
setErrorMessage({message: 'Error drawing pattern from stops! Some stops may be unreachable by streets.'})
setErrorMessage({ message: 'Error drawing pattern from stops! Some stops may be unreachable by streets.' })
return false
}
}

_cancelEdits = () => {
const {activePattern, resetActiveGtfsEntity, togglePatternEditing} = this.props
const { activePattern, resetActiveGtfsEntity, togglePatternEditing } = this.props
if (this._hasEdits()) {
if (!window.confirm('You have unsaved shape edits. Are you sure you want to cancel and revert these changes?')) {
return
Expand All @@ -93,16 +96,16 @@ export default class EditShapePanel extends Component<Props> {
}

_generateShapeFromStops = () => {
const {activePattern, editSettings, stops} = this.props
const { activePattern, editSettings, stops } = this.props
const stopLocations = stops && activePattern.patternStops && activePattern.patternStops.length
? activePattern.patternStops
.map((s, index) => {
const stop = stops.find(st => st.stop_id === s.stopId)
if (!stop) {
console.warn(`Could not locate stop with stop_id=${s.stopId}`)
return {lng: 0, lat: 0}
return { lng: 0, lat: 0 }
}
return {lng: stop.stop_lon, lat: stop.stop_lat}
return { lng: stop.stop_lon, lat: stop.stop_lat }
})
: []
this.drawPatternFromStops(activePattern, stopLocations, editSettings.present.followStreets)
Expand All @@ -114,7 +117,7 @@ export default class EditShapePanel extends Component<Props> {
const body = this._hasShapePoints()
? 'Are you sure you want to overwrite the existing shape for this trip pattern?'
: 'Are you sure you want to create an auto-generated shape for this trip pattern?'
this.props.showConfirmModal({title, body, onConfirm})
this.props.showConfirmModal({ title, body, onConfirm })
}

_deleteShape = () => {
Expand All @@ -136,7 +139,7 @@ export default class EditShapePanel extends Component<Props> {
updateActiveGtfsEntity({
component: 'trippattern',
entity: activePattern,
props: {shapePoints: [], shapeId: null}
props: { shapePoints: [], shapeId: null }
})
saveActiveGtfsEntity('trippattern')
}
Expand All @@ -149,20 +152,20 @@ export default class EditShapePanel extends Component<Props> {
* user on resolving the issue.
*/
_getPatternStopsWithShapeIssues = () => {
const {controlPoints, stops} = this.props
const { controlPoints, stops } = this.props
return controlPoints
// $FlowFixMe: can't tell flow all elements are defined in the map.
.filter(isValidStopControlPoint)
.map((controlPoint, index) => {
const {point, stopId} = controlPoint
const { point, stopId } = controlPoint
let exceedsThreshold = false
const {coordinates: cpCoord} = point.geometry
const { coordinates: cpCoord } = point.geometry
// Find stop entity for control point.
const stop = stops.find(s => s.stop_id === stopId)
if (!stop) {
// If no stop entity found, do not attempt to draw a line to the
// missing stop.
return {controlPoint, index, stop: null, distance: 0, exceedsThreshold}
return { controlPoint, index, stop: null, distance: 0, exceedsThreshold }
}
const coordinates = [[cpCoord[1], cpCoord[0]], [stop.stop_lat, stop.stop_lon]]
const distance: number = lineDistance(lineString(coordinates), 'meters')
Expand All @@ -181,15 +184,15 @@ export default class EditShapePanel extends Component<Props> {
}

_beginEditing = () => {
const {togglePatternEditing} = this.props
const { togglePatternEditing } = this.props
togglePatternEditing()
}

_hasShapePoints = () => this.props.activePattern.shapePoints &&
this.props.activePattern.shapePoints.length > 0

save = () => {
const {editSettings, saveActiveGtfsEntity, updateEditSetting} = this.props
const { editSettings, saveActiveGtfsEntity, updateEditSetting } = this.props
saveActiveGtfsEntity('trippattern')
// $FlowFixMe action is actually wrapped in promise when connected
.then(() => updateEditSetting({
Expand All @@ -211,7 +214,7 @@ export default class EditShapePanel extends Component<Props> {
updateEditSetting,
undoActiveTripPatternEdits
} = this.props
const {present: editSettings} = editSettingsState
const { present: editSettings } = editSettingsState
const hasEdits = this._hasEdits()
const fromStopsButton = <OverlayTrigger
placement='bottom'
Expand All @@ -221,7 +224,7 @@ export default class EditShapePanel extends Component<Props> {
<Button
onClick={this._confirmCreateFromStops}
bsSize='small'
style={{width: '102px'}}>
style={{ width: '102px' }}>
<span><Icon type='map-marker' /> From stops</span>
</Button>
</OverlayTrigger>
Expand All @@ -238,21 +241,21 @@ export default class EditShapePanel extends Component<Props> {
{' '}
({formattedShapeDistance} miles)
</h4>
<div style={{margin: '5px 0'}}>
<div style={{ margin: '5px 0' }}>
{!activePattern.shapeId
? <small className='text-warning'>
<Icon type='exclamation-triangle' />{' '}
No shape associated with this pattern.
</small>
: <small>
<span className='overflow' style={{width: '250px'}}>
<span className='overflow' style={{ width: '250px' }}>
shape_id:{' '}
<span title={activePattern.shapeId}>{activePattern.shapeId}</span>
</span>
<Button
bsStyle='link'
bsSize='small'
style={{padding: '0 2px 10px 2px'}}
style={{ padding: '0 2px 10px 2px' }}
title='Delete shape for pattern'
onClick={this._deleteShape}>
<span className='text-danger'><Icon type='trash' /></span>
Expand All @@ -261,18 +264,18 @@ export default class EditShapePanel extends Component<Props> {
}
</div>
{patternStopsWithShapeIssues.length > 0
? <Alert bsStyle='warning' style={{fontSize: 'small'}}>
? <Alert bsStyle='warning' style={{ fontSize: 'small' }}>
<h4><Icon type='exclamation-triangle' /> Pattern stop snapping issue</h4>
<ul className='list-unstyled' style={{marginBottom: '5px'}}>
<ul className='list-unstyled' style={{ marginBottom: '5px' }}>
{patternStopsWithShapeIssues
.map(item => {
const {distance, index, stop} = item
const { distance, index, stop } = item
if (!stop) return null
const roundedDist = Math.round(distance * 100) / 100
return (
<li key={index}>
#{index + 1} {stop.stop_name}{' '}
<span style={{color: 'red'}}>
<span style={{ color: 'red' }}>
{roundedDist} m
</span>
</li>
Expand Down Expand Up @@ -313,24 +316,24 @@ export default class EditShapePanel extends Component<Props> {
<ButtonToolbar>
<Button
block
style={{width: '167px'}}
style={{ width: '167px' }}
onClick={this._cancelEdits}
bsSize='small'>
<Icon type='ban' /> Cancel shape editing
</Button>
{fromStopsButton}
</ButtonToolbar>
<ButtonGroup style={{margin: '5px 0'}} block>
<ButtonGroup style={{ margin: '5px 0' }} block>
<OptionButton
onClick={setActivePatternSegment}
value={patternSegment - 1}
disabled={!patternSegment || patternSegment < 1}
bsSize='xsmall'>
<Icon type='caret-left' style={{color: 'blue'}} /> Prev
<Icon type='caret-left' style={{ color: 'blue' }} /> Prev
</OptionButton>
<OptionButton
onClick={setActivePatternSegment}
style={{minWidth: '165px', fontSize: '80%', padding: '2px 0'}}
style={{ minWidth: '165px', fontSize: '80%', padding: '2px 0' }}
disabled={patternSegment >= controlPoints.length - 1}
value={nextSegment}
bsSize='xsmall'>
Expand All @@ -345,7 +348,7 @@ export default class EditShapePanel extends Component<Props> {
value={nextSegment}
disabled={patternSegment >= controlPoints.length - 1}
bsSize='xsmall'>
Next <Icon type='caret-right' style={{color: ARROW_MAGENTA}} />
Next <Icon type='caret-right' style={{ color: ARROW_MAGENTA }} />
</OptionButton>
</ButtonGroup>
<ButtonToolbar>
Expand All @@ -372,7 +375,7 @@ export default class EditShapePanel extends Component<Props> {
<Button
onClick={this._beginEditing}
bsSize='small'
style={{width: '167px'}}
style={{ width: '167px' }}
bsStyle='warning'>
<span><Icon type='pencil' /> Edit pattern geometry</span>
</Button>
Expand Down