From 794159c73e95d6b46e0867dca43c8b1d041cd60d Mon Sep 17 00:00:00 2001 From: eq-ianecc Date: Thu, 20 May 2021 15:32:24 -0400 Subject: [PATCH] trips map --- src/components/trips-map.js | 189 ++++++++++++++++++++++++++++++++++++ src/index.js | 1 + stories/trips.stories.js | 14 +++ 3 files changed, 204 insertions(+) create mode 100644 src/components/trips-map.js create mode 100644 stories/trips.stories.js diff --git a/src/components/trips-map.js b/src/components/trips-map.js new file mode 100644 index 00000000..a394ced9 --- /dev/null +++ b/src/components/trips-map.js @@ -0,0 +1,189 @@ +import React, { useState, useEffect } from 'react' +import PropTypes from 'prop-types' + +import { + commonProps, + commonDefaultProps, +} from '../shared/map-props' + +import { AmbientLight, PointLight, LightingEffect } from '@deck.gl/core' +import { DeckGL } from '@deck.gl/react' +import { StaticMap } from 'react-map-gl' +import { PolygonLayer } from '@deck.gl/layers' +import { TripsLayer } from '@deck.gl/geo-layers' + + +import { styled, setup } from 'goober' + + +setup(React.createElement) + +const MapContainer = styled('div')` + height: 100%; + width: 100%; + position: absolute; +` + +const propTypes = { + layers: PropTypes.array, + setDimensionsCb: PropTypes.func, + setHighlightObj: PropTypes.func, + getTooltip: PropTypes.func, + getCursor: PropTypes.func, + viewStateOverride: PropTypes.object, + legend: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.bool, + ]), + showTooltip: PropTypes.bool, + renderTooltip: PropTypes.func, + pitch: PropTypes.number, +} + +const DATA_URL = { + BUILDINGS: + 'https://locus-cdn.s3.amazonaws.com/assets/toronto-buildings.json', // eslint-disable-line + TRIPS: 'https://locus-cdn.s3.amazonaws.com/assets/toronto-trips2.json' // eslint-disable-line +} + +const ambientLight = new AmbientLight({ + color: [255, 255, 255], + intensity: 1.0, +}) + +const pointLight = new PointLight({ + color: [255, 255, 255], + intensity: 2.0, + position: [-74.05, 40.7, 8000], +}) + +const lightingEffect = new LightingEffect({ ambientLight, pointLight }) + +const material = { + ambient: 0.1, + diffuse: 0.6, + shininess: 32, + specularColor: [60, 64, 70], +} + +const DEFAULT_THEME = { + buildingColor: [74, 80, 87], + trailColor0: [253, 128, 93], + trailColor1: [23, 184, 190], + material, + effects: [lightingEffect], +} + +const INITIAL_VIEW_STATE = { + longitude: -79.39, + latitude: 43.66, + zoom: 13, + pitch: 45, + bearing: 0, +} + +const landCover = [[[-79.61, 43.53], [-79.18, 43.53], [-79.18, 43.78], [-79.66, 43.78]]] + +const defaultProps = { + layers: [], + setDimensionsCb: () => { }, + setHighlightObj: () => { }, + getTooltip: () => { }, + getCursor: () => { }, + viewStateOverride: {}, + legend: undefined, + showTooltip: false, + renderTooltip: undefined, + pitch: 0, +} + +// DeckGL react component +const TripsMap = ({ + mapboxApiAccessToken, + buildings = DATA_URL.BUILDINGS, + trips = DATA_URL.TRIPS, + trailLength = 180, + initialViewState = INITIAL_VIEW_STATE, + theme = DEFAULT_THEME, + loopLength = 1800, // unit corresponds to the timestamp in source data + animationSpeed = 1, +}) => { + + const [time, setTime] = useState(0) + const [animation] = useState({}) + + const animate = () => { + setTime(t => (t + animationSpeed) % loopLength) + animation.id = window.requestAnimationFrame(animate) + } + + useEffect( + () => { + animation.id = window.requestAnimationFrame(animate) + return () => window.cancelAnimationFrame(animation.id) + }, + [animation], + ) + + const layers = [ + // This is only needed when using shadow effects + new PolygonLayer({ + id: 'ground', + data: landCover, + getPolygon: f => f, + stroked: false, + getFillColor: [0, 0, 0, 0], + }), + new TripsLayer({ + id: 'trips', + data: trips, + getPath: d => d.path, + getTimestamps: d => d.timestamps.map(p => p - 1620403500), + getColor: d => (d.vendor === 0 ? theme.trailColor0 : theme.trailColor1), + opacity: 0.3, + widthMinPixels: 2, + rounded: true, + trailLength, + currentTime: time, + + shadowEnabled: false, + }), + new PolygonLayer({ + id: 'buildings', + data: buildings, + extruded: true, + wireframe: false, + opacity: 0.5, + getPolygon: f => f.polygon, + getElevation: f => f.height, + getFillColor: theme.buildingColor, + material: theme.material, + }), + ] + + return ( + + + + + + ) +} + +TripsMap.propTypes = { + ...propTypes, + ...StaticMap.propTypes, + ...commonProps, +} +TripsMap.defaultProps = { + ...defaultProps, + ...StaticMap.defaultProps, + ...commonDefaultProps, +} + +export default TripsMap diff --git a/src/index.js b/src/index.js index 7be12949..32d6a17f 100644 --- a/src/index.js +++ b/src/index.js @@ -5,3 +5,4 @@ export { default as ReportMap } from './components/report-wi-map' export { default as POIMapActivePOI } from './components/poi-map-active-poi' export { default as QLReportMap } from './components/ql-report-map' export { default as GeoCohortMap } from './components/geo-cohort-map' +export { default as TripsMap } from './components/trips-map' diff --git a/stories/trips.stories.js b/stories/trips.stories.js new file mode 100644 index 00000000..cb1d7ee3 --- /dev/null +++ b/stories/trips.stories.js @@ -0,0 +1,14 @@ +/* eslint-disable react/prop-types */ +import React from 'react' +import { storiesOf } from '@storybook/react' +import { TripsMap } from '../src' + + +const MAPBOX_ACCESS_TOKEN = process.env.MAPBOX_ACCESS_TOKEN + +storiesOf('Trips Map', module) + .add('Trips layer', () => ( + + ))