Skip to content

Commit

Permalink
Mask Extension example for website (#7681)
Browse files Browse the repository at this point in the history
Co-authored-by: Xiaoji Chen <cxiaoji@gmail.com>
  • Loading branch information
felixpalmer and Pessimistress authored Feb 27, 2023
1 parent d1188b0 commit bed6396
Show file tree
Hide file tree
Showing 14 changed files with 87,115 additions and 1 deletion.
20 changes: 20 additions & 0 deletions examples/website/mask-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
This is a standalone version of the MaskExtension example on [deck.gl](http://deck.gl) website.

### Usage

Copy the content of this folder to your project.

```bash
# install dependencies
npm install
# or
yarn
# bundle and serve the app with vite
npm start
```

### Data format

Sample data is stored in [deck.gl Example Data](https://github.com/visgl/deck.gl-data/tree/master/examples/globe), showing air traffic data on selected dates collected by [The OpenSky Network](https://opensky-network.org). [Source](https://zenodo.org/record/3974209) under the Creative Commons CC-BY license.

For more information about using the MaskExtension, check out the [documentation](../../../docs/api-reference/extensions/mask-extension.md).
59 changes: 59 additions & 0 deletions examples/website/mask-extension/animated-arc-group-layer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {CompositeLayer} from '@deck.gl/core';
import AnimatedArcLayer from './animated-arc-layer';

const MAX_ARCS_PER_LAYER = 2500;

/** Same effect as the AnimatedArcLayer, but perf optimized.
* Data is divided into smaller groups, and one sub layer is rendered for each group.
* This allows us to cheaply cull invisible arcs by turning layers off and on.
*/
export default class AnimatedArcGroupLayer extends CompositeLayer {
updateState({props, changeFlags}) {
if (changeFlags.dataChanged) {
// Sort and group data
const {data, getSourceTimestamp, getTargetTimestamp} = props;
const groups = sortAndGroup(data, getSourceTimestamp, getTargetTimestamp, MAX_ARCS_PER_LAYER);
this.setState({groups});
}
}

renderLayers() {
const {timeRange} = this.props;
const {groups = []} = this.state;

return groups.map(
(group, index) =>
new AnimatedArcLayer(this.props, this.getSubLayerProps({
id: index.toString(),
data: group.data,
visible: group.startTime < timeRange[1] && group.endTime > timeRange[0],
timeRange
}))
);
}
}

AnimatedArcGroupLayer.layerName = 'AnimatedArcGroupLayer';
AnimatedArcGroupLayer.defaultProps = AnimatedArcLayer.defaultProps;

function sortAndGroup(data, getStartTime, getEndTime, groupSize) {
const groups = [];
let group = null;

data.sort((d1, d2) => getStartTime(d1) - getStartTime(d2));

for (const d of data) {
if (!group || group.data.length >= groupSize) {
group = {
startTime: Infinity,
endTime: -Infinity,
data: []
};
groups.push(group);
}
group.data.push(d);
group.startTime = Math.min(group.startTime, getStartTime(d));
group.endTime = Math.max(group.endTime, getEndTime(d));
}
return groups;
}
64 changes: 64 additions & 0 deletions examples/website/mask-extension/animated-arc-layer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {ArcLayer} from '@deck.gl/layers';

export default class AnimatedArcLayer extends ArcLayer {
getShaders() {
const shaders = super.getShaders();
shaders.inject = {
'vs:#decl': `\
uniform vec2 timeRange;
attribute float instanceSourceTimestamp;
attribute float instanceTargetTimestamp;
varying float vTimestamp;
`,
'vs:#main-end': `\
vTimestamp = mix(instanceSourceTimestamp, instanceTargetTimestamp, segmentRatio);
`,
'fs:#decl': `\
uniform vec2 timeRange;
varying float vTimestamp;
`,
'fs:#main-start': `\
if (vTimestamp < timeRange.x || vTimestamp > timeRange.y) {
discard;
}
`,
// Shape trail into teardrop
'fs:DECKGL_FILTER_COLOR': `\
float f = (vTimestamp - timeRange.x) / (timeRange.y - timeRange.x);
color.a *= pow(f, 5.0);
float cap = 10.0 * (f - 0.9);
float w = pow(f, 4.0) - smoothstep(0.89, 0.91, f) * pow(cap, 4.0);
color.a *= smoothstep(1.1 * w, w, abs(geometry.uv.y));
`
};
return shaders;
}

initializeState() {
super.initializeState();
this.getAttributeManager().addInstanced({
instanceSourceTimestamp: {
size: 1,
accessor: 'getSourceTimestamp'
},
instanceTargetTimestamp: {
size: 1,
accessor: 'getTargetTimestamp'
}
});
}

draw(params) {
params.uniforms = Object.assign({}, params.uniforms, {
timeRange: this.props.timeRange
});
super.draw(params);
}
}

AnimatedArcLayer.layerName = 'AnimatedArcLayer';
AnimatedArcLayer.defaultProps = {
getSourceTimestamp: {type: 'accessor', value: 0},
getTargetTimestamp: {type: 'accessor', value: 1},
timeRange: {type: 'array', compare: true, value: [0, 1]}
};
141 changes: 141 additions & 0 deletions examples/website/mask-extension/app.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React from 'react';
import {useState, useMemo} from 'react';
import {createRoot} from 'react-dom/client';
import {Map} from 'react-map-gl';
import maplibregl from 'maplibre-gl';

import DeckGL from '@deck.gl/react';
import {GeoJsonLayer} from '@deck.gl/layers';
import {MaskExtension} from '@deck.gl/extensions';

import {load} from '@loaders.gl/core';
import {CSVLoader} from '@loaders.gl/csv';

import AnimatedArcLayer from './animated-arc-group-layer';
import RangeInput from './range-input';

// Data source
const DATA_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/globe/2020-01-14.csv';
const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json';

const INITIAL_VIEW_STATE = {
longitude: -40,
latitude: 40,
zoom: 2,
maxZoom: 6
};

/* eslint-disable react/no-deprecated */
export default function App({
data,
mapStyle = MAP_STYLE,
showFlights = true,
timeWindow = 30,
animationSpeed = 3
}) {
const [currentTime, setCurrentTime] = useState(0);

const citiesLayers = useMemo(
() => [
new GeoJsonLayer({
id: 'cities',
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_populated_places_simple.geojson',

pointType: 'circle',
pointRadiusUnits: 'pixels',
getFillColor: [255, 232, 180]
}),

new GeoJsonLayer({
id: 'cities-highlight',
data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_10m_populated_places_simple.geojson',

pointType: 'circle',
pointRadiusUnits: 'common',
pointRadiusScale: 0.3,
pointRadiusMinPixels: 2,
pointRadiusMaxPixels: 30,
getLineColor: [255, 232, 180, 90],
getLineWidth: 3,
lineWidthUnits: 'pixels',
filled: false,
stroked: true,

extensions: [new MaskExtension()],
maskId: 'flight-mask'
})
],
[]
);

const flightLayerProps = {
data,
greatCircle: true,
getSourcePosition: d => [d.lon1, d.lat1],
getTargetPosition: d => [d.lon2, d.lat2],
getSourceTimestamp: d => d.time1,
getTargetTimestamp: d => d.time2,
getHeight: 0
};

const flightPathsLayer = showFlights && new AnimatedArcLayer({
...flightLayerProps,
id: 'flight-paths',
timeRange: [currentTime - 600, currentTime], // 10 minutes
getWidth: 0.2,
widthMinPixels: 1,
widthMaxPixels: 4,
widthUnits: 'common',
getSourceColor: [180, 232, 255],
getTargetColor: [180, 232, 255],
parameters: {depthTest: false}
});

const flightMaskLayer = new AnimatedArcLayer({
...flightLayerProps,
id: 'flight-mask',
timeRange: [currentTime - timeWindow * 60, currentTime],
operation: 'mask',
getWidth: 5000,
widthUnits: 'meters',
});

return (
<>
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
layers={[flightPathsLayer, flightMaskLayer, citiesLayers]}
>
<Map reuseMaps mapLib={maplibregl} mapStyle={mapStyle} preventStyleDiffing={true} />
</DeckGL>
{data && (
<RangeInput
min={0}
max={86400}
value={currentTime}
animationSpeed={animationSpeed}
formatLabel={formatTimeLabel}
onChange={setCurrentTime}
/>
)}
</>
);
}

function formatTimeLabel(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor(seconds / 60) % 60;
const s = seconds % 60;
return [h, m, s].map(x => x.toString().padStart(2, '0')).join(':');
}

export function renderToDOM(container) {
const root = createRoot(container);
root.render(<App />);

load(DATA_URL, CSVLoader)
.then(flights => {
root.render(<App data={flights} showFlights />);
});
}
18 changes: 18 additions & 0 deletions examples/website/mask-extension/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>deck.gl Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {margin: 0; font-family: sans-serif; width: 100vw; height: 100vh; overflow: hidden; background: #111;}
</style>
</head>
<body>
<div id="app"></div>
</body>
<script type="module">
import {renderToDOM} from './app.jsx';
renderToDOM(document.getElementById('app'));
</script>
</html>
25 changes: 25 additions & 0 deletions examples/website/mask-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "deckgl-examples-mask-extension",
"version": "0.0.0",
"private": true,
"license": "MIT",
"scripts": {
"start": "vite --open",
"start-local": "vite --config ../../vite.config.local.mjs",
"build": "vite build"
},
"dependencies": {
"@loaders.gl/csv": "^3.3.1",
"@material-ui/core": "^4.10.2",
"@material-ui/icons": "^4.9.1",
"deck.gl": "^8.8.0",
"maplibre-gl": "^2.4.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-map-gl": "^7.0.0"
},
"devDependencies": {
"typescript": "^4.6.0",
"vite": "^4.0.0"
}
}
Loading

0 comments on commit bed6396

Please sign in to comment.