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

Add Mapillary links in itinerary #521

Merged
merged 8 commits into from
Feb 23, 2022
5 changes: 5 additions & 0 deletions example-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,8 @@ dateTime:
# # Whether to render routes within flex zones of a route's patterns. If set to true,
# # routes will not be rendered within flex zones.
# hideRouteShapesWithinFlexZones: true

# API key to make Mapillary API calls. These are used to show street imagery.
# Mapillary calls these "Client Tokens". They can be created at https://www.mapillary.com/dashboard/developers
# mapillary:
# key: <Mapillary Key>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing an API key provided from Mapillary on the dev portal. They provide a client token and a client secret, at least with the way I registered the app. What do I put in this field? Can we call this something else? Or clarify in the comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've made a clearer comment in f68f925!

1 change: 1 addition & 0 deletions lib/actions/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export function switchLocations() {
}

export const setLegDiagram = createAction('SET_LEG_DIAGRAM')
export const setMapillaryId = createAction('SET_MAPILLARY_ID')

export const setElevationPoint = createAction('SET_ELEVATION_POINT')

Expand Down
22 changes: 19 additions & 3 deletions lib/components/map/map.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
}

.otp .link-button:focus {
outline:0;
outline: 0;
}

/* leg diagram */
Expand All @@ -29,8 +29,8 @@
z-index: 1000;
background-color: white;
background-clip: padding-box;
border: 2px solid rgba(127, 127, 127, .5);
border-Radius: 4px;
border: 2px solid rgba(127, 127, 127, 0.5);
border-radius: 4px;
cursor: crosshair;
}

Expand Down Expand Up @@ -64,6 +64,22 @@
background: none;
}

.otp .leg-diagram .mapillary-close-button {
background: rgba(255, 255, 255, 0.85);
border-radius: 0 0 1em;
display: block;
left: 0;
max-width: 40px;
padding: 0.5em;
position: absolute;
top: 0;
z-index: 10000;
}
.otp .leg-diagram .mapillary-close-button:hover {
background: rgba(200, 200, 200, 0.85);
color: #333;
}

/*** Car Rental Map Icons ***/

.otp .car-rental-icon {
Expand Down
67 changes: 51 additions & 16 deletions lib/components/map/map.js
Original file line number Diff line number Diff line change
@@ -1,43 +1,56 @@
import React, { Component } from 'react'
/* eslint-disable react/prop-types */
// TODO: Typescript (config object)
import { Button, ButtonGroup } from 'react-bootstrap'
import { connect } from 'react-redux'
import { ButtonGroup, Button } from 'react-bootstrap'
import React, { Component } from 'react'

import { setMapillaryId } from '../../actions/map'

import DefaultMap from './default-map'
import LegDiagram from './leg-diagram'
import MapillaryFrame from './mapillary-frame'
import StylizedMap from './stylized-map'

class Map extends Component {
constructor () {
constructor() {
super()
this.state = {
activeViewIndex: 0
}
}

getComponentForView (view) {
getComponentForView(view) {
// TODO: allow a 'CUSTOM' type
switch (view.type) {
case 'DEFAULT': return <DefaultMap />
case 'STYLIZED': return <StylizedMap />
case 'DEFAULT':
return <DefaultMap />
case 'STYLIZED':
return <StylizedMap />
}
}

render () {
const { diagramLeg, mapConfig } = this.props
render() {
const { activeMapillaryImage, diagramLeg, mapConfig, setMapillaryId } =
this.props

const showDiagram = diagramLeg
const showMapillary = activeMapillaryImage

// Use the views defined in the config; if none defined, just show the default map
const views = mapConfig.views || [{ type: 'DEFAULT' }]

return (
<div className='map-container'>
<div className="map-container">
{/* The map views -- only one is visible at a time */}
{views.map((view, i) => {
return (
<div className='map-container'
<div
className="map-container"
key={i}
style={{ visibility: i === this.state.activeViewIndex ? 'visible' : 'hidden' }}
style={{
visibility:
i === this.state.activeViewIndex ? 'visible' : 'hidden'
}}
>
{this.getComponentForView(view)}
</div>
Expand All @@ -46,15 +59,26 @@ class Map extends Component {

{/* The toggle buttons -- only show if multiple views */}
{views.length > 1 && (
<div style={{ bottom: 12 + (showDiagram ? 192 : 0), left: 12, position: 'absolute', zIndex: 100000 }}>
<div
style={{
bottom: 12 + (showDiagram ? 192 : 0),
left: 12,
position: 'absolute',
zIndex: 100000
}}
>
<ButtonGroup>
{views.map((view, i) => {
return (
<Button
bsSize='xsmall'
bsStyle={i === this.state.activeViewIndex ? 'success' : 'default'}
bsSize="xsmall"
bsStyle={
i === this.state.activeViewIndex ? 'success' : 'default'
}
key={i}
onClick={() => { this.setState({ activeViewIndex: i }) }}
onClick={() => {
this.setState({ activeViewIndex: i })
}}
style={{ padding: '3px 6px' }}
>
{view.text || view.type}
Expand All @@ -67,6 +91,12 @@ class Map extends Component {

{/* The leg diagram overlay, if active */}
{showDiagram && <LegDiagram leg={diagramLeg} />}
{showMapillary && (
<MapillaryFrame
id={activeMapillaryImage}
onClose={() => setMapillaryId(null)}
/>
)}
</div>
)
}
Expand All @@ -76,9 +106,14 @@ class Map extends Component {

const mapStateToProps = (state, ownProps) => {
return {
activeMapillaryImage: state.otp.ui.mapillaryId,
diagramLeg: state.otp.ui.diagramLeg,
mapConfig: state.otp.config.map
}
}

export default connect(mapStateToProps)(Map)
const mapDispatchToProps = {
setMapillaryId
}

export default connect(mapStateToProps, mapDispatchToProps)(Map)
55 changes: 55 additions & 0 deletions lib/components/map/mapillary-frame.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Button } from 'react-bootstrap'
import React, { useEffect, useState } from 'react'

// eslint-disable-next-line sort-imports-es6-autofix/sort-imports-es6
import Icon from '../util/icon'

const MapillaryFrame = ({
id,
onClose
}: {
id: string
onClose?: () => void
}): React.ReactElement => {
const [fakeLoad, setFakeLoad] = useState(false)
useEffect(() => {
// If the ID changed, show a "fake" loading screen to indicate to the user
// something is happening
setFakeLoad(true)
setTimeout(() => setFakeLoad(false), 750)
}, [id])

return (
<div className="leg-diagram" style={{ height: '50vh', zIndex: 999 }}>
<div
style={{
alignItems: 'center',
display: fakeLoad ? 'flex' : 'none',
height: '100%',
justifyContent: 'center'
}}
>
<Icon className="fa-spin" type="spinner" />
</div>
<iframe
frameBorder="0"
src={`https://www.mapillary.com/embed?image_key=${id}&style=photo`}
style={{
display: fakeLoad ? 'none' : 'block',
height: '100%',
width: '100%'
}}
title="Imagery of the street"
/>
<Button
aria-label="Close"
className="mapillary-close-button close-button clear-button-formatting"
onClick={onClose}
>
<i className="fa fa-close" />
</Button>
</div>
)
}

export default MapillaryFrame
61 changes: 35 additions & 26 deletions lib/components/narrative/default/access-leg.js
Original file line number Diff line number Diff line change
@@ -1,83 +1,92 @@
import coreUtils from '@opentripplanner/core-utils'
import { FormattedMessage } from 'react-intl'
import { humanizeDistanceString } from '@opentripplanner/humanize-distance'
import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, {Component} from 'react'
import { FormattedMessage } from 'react-intl'
import React, { Component } from 'react'

import Icon from '../../util/icon'
import Strong from '../../util/strong-text'
import FormattedMode from '../../util/formatted-mode'
import FormattedDuration from '../../util/formatted-duration'
import FormattedMode from '../../util/formatted-mode'
import Icon from '../../util/icon'
import LegDiagramPreview from '../leg-diagram-preview'
import Strong from '../../util/strong-text'

/**
* Default access leg component for narrative itinerary.
*/
export default class AccessLeg extends Component {
static propTypes = {
active: PropTypes.bool,
activeStep: PropTypes.number,
index: PropTypes.number,
leg: PropTypes.object,
setActiveLeg: PropTypes.func,
setActiveStep: PropTypes.func
}

_onLegClick = (e) => {
const {active, index, leg, setActiveLeg} = this.props
const { active, index, leg, setActiveLeg } = this.props
if (active) {
setActiveLeg(null)
} else {
setActiveLeg(index, leg)
}
}

_onStepClick (e, step, index) {
_onStepClick(e, step, index) {
if (index === this.props.activeStep) {
this.props.setActiveStep(null)
} else {
this.props.setActiveStep(index, step)
}
}

render () {
render() {
const { active, activeStep, index, leg } = this.props
return (
<div
className={`leg${active ? ' active' : ''} access-leg`}
key={index}>
<button
className='header'
onClick={this._onLegClick}>
<span><Icon type={`caret-${active ? 'down' : 'right'}`} /></span>
<div className={`leg${active ? ' active' : ''} access-leg`} key={index}>
<button className="header" onClick={this._onLegClick}>
<span>
<Icon type={`caret-${active ? 'down' : 'right'}`} />
</span>
<FormattedMessage
id='components.AccessLeg.summary'
id="components.AccessLeg.summary"
values={{
distance: humanizeDistanceString(leg.distance),
distanceSpan: contents => <span className='leg-distance'>{contents}</span>,
durationSpan: contents => <span className='leg-duration'>{contents}</span>,
distanceSpan: (contents) => (
<span className="leg-distance">{contents}</span>
),
durationSpan: (contents) => (
<span className="leg-duration">{contents}</span>
),
formattedDuration: <FormattedDuration duration={leg.duration} />,
mode: <FormattedMode mode={leg.mode.toLowerCase()} />,
strong: Strong
}}
/>
</button>
{active &&
<div className='step-by-step'>
<div className='access-leg'>
{active && (
<div className="step-by-step">
<div className="access-leg">
{leg.steps.map((step, stepIndex) => {
const stepIsActive = activeStep === stepIndex
return (
<button
className={`step ${stepIsActive ? 'active' : ''}`}
key={stepIndex}
onClick={(e) => this._onStepClick(e, step, stepIndex)}>
<span className='step-distance'>{humanizeDistanceString(step.distance)}</span>
<span className='step-text'>{coreUtils.itinerary.getStepInstructions(step)}</span>
onClick={(e) => this._onStepClick(e, step, stepIndex)}
>
<span className="step-distance">
{humanizeDistanceString(step.distance)}
</span>
<span className="step-text">
{coreUtils.itinerary.getStepInstructions(step)}
</span>
</button>
)
})}
</div>
</div>
}
)}
<LegDiagramPreview leg={leg} />
</div>
)
Expand Down
Loading