Skip to content

Commit

Permalink
feat: support general transit feed (#408)
Browse files Browse the repository at this point in the history
Co-authored-by: rot1024 <aayhrot@gmail.com>
  • Loading branch information
pyshx and rot1024 committed Feb 7, 2023
1 parent 5085022 commit 49b4a46
Show file tree
Hide file tree
Showing 7 changed files with 587 additions and 10 deletions.
24 changes: 15 additions & 9 deletions src/core/engines/Cesium/Feature/Model/index.tsx
Expand Up @@ -44,6 +44,7 @@ export default function Model({ id, isVisible, property, geometry, layer, featur
lightColor,
silhouette,
silhouetteColor,
bearing,
silhouetteSize = 1,
} = property ?? {};

Expand All @@ -55,16 +56,21 @@ export default function Model({ id, isVisible, property, geometry, layer, featur
const orientation = useMemo(
() =>
position
? Transforms.headingPitchRollQuaternion(
position,
new HeadingPitchRoll(
CesiumMath.toRadians(heading ?? 0),
CesiumMath.toRadians(pitch ?? 0),
CesiumMath.toRadians(roll ?? 0),
),
)
? bearing
? Transforms.headingPitchRollQuaternion(
position,
HeadingPitchRoll.fromDegrees(bearing - 90.0, 0.0, 0.0),
)
: Transforms.headingPitchRollQuaternion(
position,
new HeadingPitchRoll(
CesiumMath.toRadians(heading ?? 0),
CesiumMath.toRadians(pitch ?? 0),
CesiumMath.toRadians(roll ?? 0),
),
)
: undefined,
[heading, pitch, position, roll],
[bearing, heading, pitch, position, roll],
);
const modelColor = useMemo(() => (colorBlend ? toColor(color) : undefined), [colorBlend, color]);
const modelLightColor = useMemo(() => toColor(lightColor), [lightColor]);
Expand Down
1 change: 1 addition & 0 deletions src/core/engines/Cesium/Feature/index.tsx
Expand Up @@ -44,6 +44,7 @@ const displayConfig: Record<DataType, (keyof typeof components)[] | "auto"> = {
"osm-buildings": ["3dtiles"],
gpx: "auto",
shapefile: "auto",
gtfs: "auto",
};

// Some layer that is delegated data is not computed when layer is updated.
Expand Down
112 changes: 112 additions & 0 deletions src/core/mantle/data/gtfs.ts
@@ -0,0 +1,112 @@
import Pbf from "pbf";

import type { Data, DataRange, Feature } from "../types";

import { Trips, Trip, GTFS, GTFSReader } from "./gtfsReader";
import { f } from "./utils";

let gtfs: GTFS = {};
let current: Trips = {};
let previous: Trips = {};
let tripsData: Trips = {};

export async function fetchGTFS(data: Data, range?: DataRange): Promise<Feature[] | void> {
const fetchData = async () => {
try {
const arrayBuffer = data.url ? await (await f(data.url)).arrayBuffer() : data.value;
const pbfBuffer = new Pbf(new Uint8Array(arrayBuffer));
gtfs = new GTFSReader().read(pbfBuffer);
} catch (err) {
throw new Error(`failed to fetch gtfs: ${err}`);
}
};
await fetchData();

return processGTFS(gtfs, range);
}

export function processGTFS(gtfs?: GTFS, _range?: DataRange): Feature[] | void {
if (gtfs) {
if (current) {
previous = current;
const currentTrips = GTFStoTrips(gtfs);
current = currentTrips;
const merged = mergeTrips(currentTrips, previous);

tripsData = merged;
} else {
const currentTrips = GTFStoTrips(gtfs);
current = currentTrips;
tripsData = currentTrips;
}
}

return tripsData?.trips
?.filter(f => !!f.properties.position)
.map(f => {
return {
type: "feature",
id: f.id,
geometry: {
type: "Point",
coordinates: [
f.properties.position?.longitude || 0,
f.properties.position?.latitude || 0,
],
},
properties: {},
};
});
}

export const GTFStoTrips = (gtfs: GTFS): Trips => {
const trips = gtfs.entities?.map(entity => ({
id: entity.id,
properties: entity.vehicle,
path: [[entity.vehicle?.position?.longitude, entity.vehicle?.position?.latitude]],
timestamps: [entity.vehicle?.timestamp],
}));
return {
timestamp: gtfs.header?.timestamp,
trips: trips as Trip[],
};
};

export const mergeTrips = (current: Trips, prev?: Trips) => {
if (prev) {
const prevMap = new Map();
prev?.trips?.forEach(trip => {
prevMap.set(trip.id, trip);
});
const mergedMap = new Map();
const newIds: string[] = [];
current?.trips?.forEach(entity => {
const previousEntity = prevMap.get(entity.id);
if (previousEntity) {
const previousPath = previousEntity.path;
const previousTimestamps = previousEntity.timestamps;
const trip = {
id: entity.id,
properties: entity.properties,
path: [...previousPath, ...entity.path],
timestamps: [...previousTimestamps, ...entity.timestamps],
};
mergedMap.set(entity.id, trip);
} else {
mergedMap.set(entity.id, entity);
newIds.push(entity.id);
}
});
const trips = Object.fromEntries(mergedMap);
const mergedTrips = Object.values(trips).filter(
entity =>
newIds.includes((entity as Trip).id) ||
(entity as Trip).path.length > prevMap.get((entity as Trip).id).path.length,
);
return {
timestamp: current.timestamp,
trips: mergedTrips as Trip[],
};
}
return current;
};

0 comments on commit 49b4a46

Please sign in to comment.