Skip to content

Commit

Permalink
GeoArrow-based Trips Layer (#34)
Browse files Browse the repository at this point in the history
* wip trips layer

* update lockfile

* wip

* updates

* lint

* fix package json

* Working trips layer

* lint

* bump version
  • Loading branch information
kylebarron authored Dec 5, 2023
1 parent 5cc954d commit a45b49e
Show file tree
Hide file tree
Showing 13 changed files with 1,808 additions and 4 deletions.
Empty file added examples/trips/README.md
Empty file.
103 changes: 103 additions & 0 deletions examples/trips/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useState, useEffect } from "react";
import { createRoot } from "react-dom/client";
import { StaticMap, MapContext, NavigationControl } from "react-map-gl";
import DeckGL, { Layer, PickingInfo } from "deck.gl/typed";
import { GeoArrowTripsLayer } from "@geoarrow/deck.gl-layers";
import * as arrow from "apache-arrow";

const GEOARROW_POINT_DATA = "http://localhost:8080/trips.feather";

const INITIAL_VIEW_STATE = {
longitude: -74,
latitude: 40.72,
zoom: 13,
pitch: 45,
bearing: 0,
};

const MAP_STYLE =
"https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json";
const NAV_CONTROL_STYLE = {
position: "absolute",
top: 10,
left: 10,
};

function Root() {
const trailLength = 180;
// unit corresponds to the timestamp in source data
const loopLength = 1800;
const animationSpeed = 1;

const [time, setTime] = useState(0);
const [animation] = useState<{ id: number }>({ id: 0 });

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 onClick = (info: PickingInfo) => {
if (info.object) {
console.log(JSON.stringify(info.object.toJSON()));
}
};

const [table, setTable] = useState<arrow.Table | null>(null);

useEffect(() => {
// declare the data fetching function
const fetchData = async () => {
const data = await fetch(GEOARROW_POINT_DATA);
const buffer = await data.arrayBuffer();
const table = arrow.tableFromIPC(buffer);
setTable(table);
};

if (!table) {
fetchData().catch(console.error);
}
});

const layers: Layer[] = [];

table &&
layers.push(
new GeoArrowTripsLayer({
id: "geoarrow-linestring",
data: table,
getColor: [255, 0, 0],
getPath: table.getChild("geometry")!,
getTimestamps: table.getChild("timestamps")!,
widthMinPixels: 2,
pickable: true,
trailLength,
currentTime: time,
}),
);

return (
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
controller={true}
layers={layers}
ContextProvider={MapContext.Provider}
onClick={onClick}
glOptions={{
powerPreference: "high-performance",
}}
>
<StaticMap mapStyle={MAP_STYLE} />
<NavigationControl style={NAV_CONTROL_STYLE} />
</DeckGL>
);
}

/* global document */
const container = document.body.appendChild(document.createElement("div"));
createRoot(container).render(<Root />);
42 changes: 42 additions & 0 deletions examples/trips/generate_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import requests
import numpy as np
import pyarrow as pa
import pyarrow.feather as feather

url = "https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/trips/trips-v7.json"
r = requests.get(url)
data = r.json()

coord_num = 0
geoms_num = len(data)
offsets = np.zeros(geoms_num + 1, dtype=np.int32)

for i, item in enumerate(data):
assert len(item["path"]) == len(item["timestamps"])
coord_num += len(item["path"])
offsets[i + 1] = coord_num

vendor = np.zeros(geoms_num, dtype=np.uint8)
coords = np.zeros((coord_num, 2), dtype=np.float32)
timestamps = np.zeros(coord_num, dtype=np.float32)

for i, item in enumerate(data):
start_offset = offsets[i]
end_offset = offsets[i + 1]
path = np.array(item["path"])
assert end_offset - start_offset == path.shape[0]
coords[start_offset:end_offset, :] = path
timestamps[start_offset:end_offset] = item["timestamps"]
vendor[i] = item["vendor"]

coords_fixed_size_list = pa.FixedSizeListArray.from_arrays(
pa.array(coords.flatten("C")), 2
)
linestrings_arr = pa.ListArray.from_arrays(pa.array(offsets), coords_fixed_size_list)
timestamp_arr = pa.ListArray.from_arrays(pa.array(offsets), timestamps)

table = pa.table(
{"geometry": linestrings_arr, "timestamps": timestamp_arr, "vendor": vendor}
)

feather.write_feather(table, "trips.feather", compression="uncompressed")
13 changes: 13 additions & 0 deletions examples/trips/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>deck.gl GeoArrowTripsLayer Example</title>
<style>
body {margin: 0; width: 100vw; height: 100vh; overflow: hidden;}
</style>
</head>
<body>
</body>
<script type="module" src="app.tsx"></script>
</html>
26 changes: 26 additions & 0 deletions examples/trips/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "deckgl-example-geoarrow-trips-layer",
"version": "0.0.0",
"private": true,
"license": "MIT",
"scripts": {
"start": "vite --open",
"build": "vite build"
},
"dependencies": {
"@geoarrow/deck.gl-layers": "../../",
"apache-arrow": ">=14",
"deck.gl": "^8.9.23",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-map-gl": "^5.3.0"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"vite": "^4.0.0"
},
"volta": {
"extends": "../../package.json"
}
}
Loading

0 comments on commit a45b49e

Please sign in to comment.