examples/website/scenegraph/app.js (129 lines of code) (raw):

/* global fetch, setTimeout, clearTimeout */ import React, {useEffect, useState} from 'react'; import {render} from 'react-dom'; import {StaticMap} from 'react-map-gl'; import DeckGL from '@deck.gl/react'; import {ScenegraphLayer} from '@deck.gl/mesh-layers'; import {GLTFLoader} from '@loaders.gl/gltf'; import {registerLoaders} from '@loaders.gl/core'; registerLoaders([GLTFLoader]); // Set your mapbox token here const MAPBOX_TOKEN = process.env.MapboxAccessToken; // eslint-disable-line // mapbox style file path const MAPBOX_STYLE = 'https://rivulet-zhang.github.io/dataRepo/mapbox/style/map-style-dark-v9-no-labels.json'; // Data provided by the OpenSky Network, http://www.opensky-network.org const DATA_URL = 'https://opensky-network.org/api/states/all'; const MODEL_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/scenegraph-layer/airplane.glb'; const REFRESH_TIME = 30000; const ANIMATIONS = { '*': {speed: 1} }; const INITIAL_VIEW_STATE = { latitude: 39.1, longitude: -94.57, zoom: 3.8, maxZoom: 16, pitch: 0, bearing: 0 }; const DATA_INDEX = { UNIQUE_ID: 0, CALL_SIGN: 1, ORIGIN_COUNTRY: 2, LONGITUDE: 5, LATITUDE: 6, BARO_ALTITUDE: 7, VELOCITY: 9, TRUE_TRACK: 10, VERTICAL_RATE: 11, GEO_ALTITUDE: 13, POSITION_SOURCE: 16 }; function verticalRateToAngle(object) { // Return: -90 looking up, +90 looking down const verticalRate = object[DATA_INDEX.VERTICAL_RATE] || 0; const velocity = object[DATA_INDEX.VELOCITY] || 0; return (-Math.atan2(verticalRate, velocity) * 180) / Math.PI; } function getTooltip({object}) { return ( object && `\ Call Sign: ${object[DATA_INDEX.CALL_SIGN] || ''} Country: ${object[DATA_INDEX.ORIGIN_COUNTRY] || ''} Vertical Rate: ${object[DATA_INDEX.VERTICAL_RATE] || 0} m/s Velocity: ${object[DATA_INDEX.VELOCITY] || 0} m/s Direction: ${object[DATA_INDEX.TRUE_TRACK] || 0}` ); } export default function App({sizeScale = 25, onDataLoad, mapStyle = MAPBOX_STYLE}) { const [data, setData] = useState(null); const [timer, setTimer] = useState({}); useEffect( () => { fetch(DATA_URL) .then(resp => resp.json()) .then(resp => { if (resp && resp.states && timer.id !== null) { // In order to keep the animation smooth we need to always return the same // objects in the exact same order. This function will discard new objects // and only update existing ones. let sortedData = resp.states; if (data) { const dataAsObj = {}; sortedData.forEach(entry => (dataAsObj[entry[DATA_INDEX.UNIQUE_ID]] = entry)); sortedData = data.map(entry => dataAsObj[entry[DATA_INDEX.UNIQUE_ID]] || entry); } setData(sortedData); if (onDataLoad) { onDataLoad(sortedData.length); } } }) .finally(() => { timer.nextTimeoutId = setTimeout(() => setTimer({id: timer.nextTimeoutId}), REFRESH_TIME); }); return () => { clearTimeout(timer.nextTimeoutId); timer.id = null; }; }, [timer] ); const layer = data && new ScenegraphLayer({ id: 'scenegraph-layer', data, pickable: true, sizeScale, scenegraph: MODEL_URL, _animations: ANIMATIONS, sizeMinPixels: 0.1, sizeMaxPixels: 1.5, getPosition: d => [ d[DATA_INDEX.LONGITUDE] || 0, d[DATA_INDEX.LATITUDE] || 0, d[DATA_INDEX.GEO_ALTITUDE] || 0 ], getOrientation: d => [verticalRateToAngle(d), -d[DATA_INDEX.TRUE_TRACK] || 0, 90], transitions: { getPosition: REFRESH_TIME * 0.9 } }); return ( <DeckGL layers={[layer]} initialViewState={INITIAL_VIEW_STATE} controller={true} getTooltip={getTooltip} > <StaticMap reuseMaps mapStyle={mapStyle} preventStyleDiffing={true} mapboxApiAccessToken={MAPBOX_TOKEN} /> </DeckGL> ); } export function renderToDOM(container) { render(<App />, container); }