Skip to content

Commit c79c699

Browse files
committed
trips map
1 parent 60533df commit c79c699

File tree

3 files changed

+204
-0
lines changed

3 files changed

+204
-0
lines changed

src/components/trips-map.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import React, { useState, useEffect } from 'react'
2+
import PropTypes from 'prop-types'
3+
4+
import {
5+
commonProps,
6+
commonDefaultProps,
7+
} from '../shared/map-props'
8+
9+
import { AmbientLight, PointLight, LightingEffect } from '@deck.gl/core'
10+
import { DeckGL } from '@deck.gl/react'
11+
import { StaticMap } from 'react-map-gl'
12+
import { PolygonLayer } from '@deck.gl/layers'
13+
import { TripsLayer } from '@deck.gl/geo-layers'
14+
15+
16+
import { styled, setup } from 'goober'
17+
18+
19+
setup(React.createElement)
20+
21+
const MapContainer = styled('div')`
22+
height: 100%;
23+
width: 100%;
24+
position: absolute;
25+
`
26+
27+
const propTypes = {
28+
layers: PropTypes.array,
29+
setDimensionsCb: PropTypes.func,
30+
setHighlightObj: PropTypes.func,
31+
getTooltip: PropTypes.func,
32+
getCursor: PropTypes.func,
33+
viewStateOverride: PropTypes.object,
34+
legend: PropTypes.oneOfType([
35+
PropTypes.node,
36+
PropTypes.bool,
37+
]),
38+
showTooltip: PropTypes.bool,
39+
renderTooltip: PropTypes.func,
40+
pitch: PropTypes.number,
41+
}
42+
43+
const DATA_URL = {
44+
BUILDINGS:
45+
'https://locus-cdn.s3.amazonaws.com/assets/toronto-buildings.json', // eslint-disable-line
46+
TRIPS: 'https://locus-cdn.s3.amazonaws.com/assets/toronto-trips.json' // eslint-disable-line
47+
}
48+
49+
const ambientLight = new AmbientLight({
50+
color: [255, 255, 255],
51+
intensity: 1.0,
52+
})
53+
54+
const pointLight = new PointLight({
55+
color: [255, 255, 255],
56+
intensity: 2.0,
57+
position: [-74.05, 40.7, 8000],
58+
})
59+
60+
const lightingEffect = new LightingEffect({ ambientLight, pointLight })
61+
62+
const material = {
63+
ambient: 0.1,
64+
diffuse: 0.6,
65+
shininess: 32,
66+
specularColor: [60, 64, 70],
67+
}
68+
69+
const DEFAULT_THEME = {
70+
buildingColor: [74, 80, 87],
71+
trailColor0: [253, 128, 93],
72+
trailColor1: [23, 184, 190],
73+
material,
74+
effects: [lightingEffect],
75+
}
76+
77+
const INITIAL_VIEW_STATE = {
78+
longitude: -79.39,
79+
latitude: 43.66,
80+
zoom: 13,
81+
pitch: 45,
82+
bearing: 0,
83+
}
84+
85+
const landCover = [[[-79.61, 43.53], [-79.18, 43.53], [-79.18, 43.78], [-79.66, 43.78]]]
86+
87+
const defaultProps = {
88+
layers: [],
89+
setDimensionsCb: () => { },
90+
setHighlightObj: () => { },
91+
getTooltip: () => { },
92+
getCursor: () => { },
93+
viewStateOverride: {},
94+
legend: undefined,
95+
showTooltip: false,
96+
renderTooltip: undefined,
97+
pitch: 0,
98+
}
99+
100+
// DeckGL react component
101+
const TripsMap = ({
102+
mapboxApiAccessToken,
103+
buildings = DATA_URL.BUILDINGS,
104+
trips = DATA_URL.TRIPS,
105+
trailLength = 180,
106+
initialViewState = INITIAL_VIEW_STATE,
107+
theme = DEFAULT_THEME,
108+
loopLength = 1800, // unit corresponds to the timestamp in source data
109+
animationSpeed = 1,
110+
}) => {
111+
112+
const [time, setTime] = useState(0)
113+
const [animation] = useState({})
114+
115+
const animate = () => {
116+
setTime(t => (t + animationSpeed) % loopLength)
117+
animation.id = window.requestAnimationFrame(animate)
118+
}
119+
120+
useEffect(
121+
() => {
122+
animation.id = window.requestAnimationFrame(animate)
123+
return () => window.cancelAnimationFrame(animation.id)
124+
},
125+
[animation],
126+
)
127+
128+
const layers = [
129+
// This is only needed when using shadow effects
130+
new PolygonLayer({
131+
id: 'ground',
132+
data: landCover,
133+
getPolygon: f => f,
134+
stroked: false,
135+
getFillColor: [0, 0, 0, 0],
136+
}),
137+
new TripsLayer({
138+
id: 'trips',
139+
data: trips,
140+
getPath: d => d.path,
141+
getTimestamps: d => d.timestamps.map(p => p - 1620403200),
142+
getColor: d => (d.vendor === 0 ? theme.trailColor0 : theme.trailColor1),
143+
opacity: 0.3,
144+
widthMinPixels: 2,
145+
rounded: true,
146+
trailLength,
147+
currentTime: time,
148+
149+
shadowEnabled: false,
150+
}),
151+
new PolygonLayer({
152+
id: 'buildings',
153+
data: buildings,
154+
extruded: true,
155+
wireframe: false,
156+
opacity: 0.5,
157+
getPolygon: f => f.polygon,
158+
getElevation: f => f.height,
159+
getFillColor: theme.buildingColor,
160+
material: theme.material,
161+
}),
162+
]
163+
164+
return (
165+
<MapContainer>
166+
<DeckGL
167+
layers={layers}
168+
effects={theme.effects}
169+
initialViewState={initialViewState}
170+
controller={true}
171+
>
172+
<StaticMap reuseMaps mapStyle="mapbox://styles/dilshaneq/ckowvrtv00vol17nvjc2zk30y" preventStyleDiffing={true} mapboxApiAccessToken={mapboxApiAccessToken} />
173+
</DeckGL>
174+
</MapContainer>
175+
)
176+
}
177+
178+
TripsMap.propTypes = {
179+
...propTypes,
180+
...StaticMap.propTypes,
181+
...commonProps,
182+
}
183+
TripsMap.defaultProps = {
184+
...defaultProps,
185+
...StaticMap.defaultProps,
186+
...commonDefaultProps,
187+
}
188+
189+
export default TripsMap

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export { default as ReportMap } from './components/report-wi-map'
55
export { default as POIMapActivePOI } from './components/poi-map-active-poi'
66
export { default as QLReportMap } from './components/ql-report-map'
77
export { default as GeoCohortMap } from './components/geo-cohort-map'
8+
export { default as TripsMap } from './components/trips-map'

stories/trips.stories.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* eslint-disable react/prop-types */
2+
import React from 'react'
3+
import { storiesOf } from '@storybook/react'
4+
import { TripsMap } from '../src'
5+
6+
7+
const MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN
8+
9+
storiesOf('Trips Map', module)
10+
.add('Trips layer', () => (
11+
<TripsMap
12+
mapboxApiAccessToken={ MAPBOX_ACCESS_TOKEN }
13+
/>
14+
))

0 commit comments

Comments
 (0)