Interact with the timeline to filter the observations. The observations shown on the map correspond to the time range in the timeline, so narrow the time range with zoom or scroll to move forwards and backwards in time.
MapWeave can be integrated with KronoGraph to visualize individual observations as events and allow filtering of data based on the timeline range. The KronoGraph controls, such as focus and pin, can be used to filter specific entities and their journeys on the map. Clicking on observations on the map can be synced to the KronoGraph ping feature to highlight the corresponding position on the timeline, which is very useful for busy datasets.
import { createTimeline } from "kronograph";
import { ObservationsLayer } from "mapweave/layers";
import { MapWeave } from "mapweave/mapbox";
import {
backgroundedMapEntities,
boundsPerEvent,
mapEntities,
observations,
observationsPerEntity,
observationsPerEvent,
timelineData,
} from "./data";
const timelineOptions = {
focus: { backgroundColor: "rgba(255, 255, 255, 0.1)" },
scales: { showAtBottom: false },
};
const mainLayerOptions = {
mode: "individual",
};
const highlightLayerOptions = {
individual: { autoRange: { min: 0 } },
};
let mapweave;
let timeline;
let mainObservationsLayer;
let highlightObservationsLayer;
let state = {
focusedEntityIds: [],
pinnedEntityIds: [],
selectedEventId: null,
};
function updateLayer(layer, entities, observations) {
const isVisible = entities !== undefined && observations !== undefined;
layer.visible(isVisible);
if (isVisible) {
layer.data({ entities, observations });
}
}
function observationsForEntities(entityIds) {
return entityIds.length === 1
? observationsPerEntity[entityIds[0]]
: entityIds.reduce((prev, id) => Object.assign(prev, observationsPerEntity[id]), {});
}
function updateLayersFromState() {
const { focusedEntityIds, pinnedEntityIds, selectedEventId } = state;
// For a selected event, highlight that ride only, otherwise all pinned and focused taxis
const highlightObservations = selectedEventId
? observationsPerEvent[selectedEventId]
: observationsForEntities([...focusedEntityIds, ...pinnedEntityIds]);
// If no selected event or focused taxi, show all taxis, foregrounding any pinned ones
let mainLayerEntities;
if (selectedEventId === null && focusedEntityIds.length === 0) {
mainLayerEntities = pinnedEntityIds.length > 0 ? backgroundedMapEntities : mapEntities;
}
updateLayer(mainObservationsLayer, mainLayerEntities, observations);
updateLayer(highlightObservationsLayer, mapEntities, highlightObservations);
}
function updateState(changes) {
state = { ...state, ...changes };
updateLayersFromState();
}
// Use the start and end properties of the event directly
function handleTimelineRange(timeFilterRange) {
mainObservationsLayer.options({ timeFilterRange });
highlightObservationsLayer.options({ timeFilterRange });
}
function timelineClickHandler({ eventIds, targetType }) {
let selectedEventId = null;
if (targetType === "event") {
selectedEventId = eventIds[0];
// Pan and zoom to the event bounds using the Mapbox fitBounds API
mapweave.map.fitBounds(boundsPerEvent[selectedEventId], { padding: 50 });
}
timeline.selection(selectedEventId ?? []);
updateState({ selectedEventId });
}
function mapweaveClickHandler(e) {
// Get the time for a clicked observation or trajectory
const time = e.item?.time ?? e.item?.observations?.[0].time;
if (typeof time === "number") {
// Find the event for the entity that includes the required time
const eventId = Object.keys(timelineData.events).find((eventId) => {
const {
entityIds: [entityId],
time: { start, end },
} = timelineData.events[eventId];
return entityId === e.item.entityId && time >= start && time <= end;
});
if (eventId) {
timeline.ping(eventId);
}
}
}
function main() {
mapweave = new MapWeave({ container: "mw", options: { accessToken: VITE_MAPBOX_API_KEY } });
mainObservationsLayer = new ObservationsLayer({ options: mainLayerOptions });
highlightObservationsLayer = new ObservationsLayer({ options: highlightLayerOptions });
mapweave.layers([mainObservationsLayer, highlightObservationsLayer]);
mapweave.on("click", mapweaveClickHandler);
updateLayersFromState();
mapweave.fitBounds();
timeline = createTimeline({ container: "kg", options: timelineOptions });
timeline.set(timelineData);
timeline.fit();
timeline.on("range", handleTimelineRange);
timeline.on("focus", ({ focus }) => updateState({ focusedEntityIds: focus }));
timeline.on("pin", ({ pin }) => updateState({ pinnedEntityIds: pin }));
timeline.on("click", timelineClickHandler);
}
main(); // [r, g, b]
const taxiColors = {
"Taxi-304": [255, 17, 92],
"Taxi-424": [255, 177, 52],
"Taxi-529": [0, 161, 113],
};
const taxiBackgroundAlpha = 0.4;
// 15 second interval between positions
const positionInterval = 15000;
const taxiRides = {
"Taxi-304": [
{
time: 1375481298000,
positions: [
[41.140899, -8.610084],
[41.140917, -8.610093],
[41.140863, -8.610021],
[41.141007, -8.610165],
[41.141493, -8.613216],
[41.141448, -8.613378],
[41.141403, -8.613504],
[41.141151, -8.614341],
[41.140863, -8.61516],
[41.140773, -8.61543],
[41.140647, -8.616843],
[41.141052, -8.617698],
[41.141367, -8.618391],
[41.141736, -8.619066],
[41.142303, -8.619957],
[41.14269, -8.620461],
[41.143365, -8.62119],
[41.143977, -8.622603],
[41.1444, -8.623935],
[41.144814, -8.625978],
[41.144706, -8.628264],
[41.1453, -8.630262],
[41.145642, -8.630766],
[41.145633, -8.630739],
[41.145966, -8.631225],
[41.146839, -8.632503],
[41.147739, -8.632188],
[41.148981, -8.631099],
[41.150295, -8.629992],
[41.151132, -8.629191],
[41.151222, -8.629146],
[41.151204, -8.629137],
[41.152122, -8.628804],
[41.152347, -8.628723],
[41.152428, -8.628723],
[41.152626, -8.630298],
[41.152734, -8.632404],
[41.152446, -8.635212],
[41.152869, -8.637642],
[41.153526, -8.640468],
[41.154237, -8.642907],
[41.154408, -8.645571],
[41.154273, -8.648289],
[41.154327, -8.649333],
[41.154453, -8.649837],
[41.154426, -8.649828],
[41.154372, -8.649747],
[41.15439, -8.650017],
[41.153796, -8.650341],
[41.15304, -8.649756],
[41.15214, -8.649261],
[41.151078, -8.649738],
[41.150142, -8.649054],
[41.148774, -8.648964],
[41.148243, -8.648478],
],
},
{
time: 1375483130000,
positions: [
[41.140953, -8.610228],
[41.140962, -8.610192],
[41.141214, -8.610678],
[41.14152, -8.61318],
[41.141367, -8.613729],
[41.141106, -8.614566],
[41.140773, -8.61543],
[41.140629, -8.615862],
[41.140962, -8.617545],
[41.141952, -8.619408],
[41.143203, -8.620992],
[41.144139, -8.623071],
[41.144607, -8.624619],
[41.144796, -8.626365],
[41.144796, -8.628975],
[41.145993, -8.631279],
[41.147091, -8.632773],
[41.148207, -8.631738],
[41.149746, -8.630442],
[41.151204, -8.629164],
[41.15232, -8.628741],
[41.152401, -8.62875],
[41.152626, -8.6301],
[41.152689, -8.631594],
[41.152608, -8.633565],
[41.152482, -8.635986],
[41.153031, -8.638389],
[41.154246, -8.638218],
[41.156154, -8.637264],
[41.158044, -8.636328],
[41.15889, -8.635905],
[41.159223, -8.6364],
[41.159556, -8.638551],
[41.159889, -8.640504],
[41.159907, -8.640621],
[41.159934, -8.640783],
[41.160411, -8.641539],
[41.161167, -8.640693],
[41.161212, -8.640621],
[41.161167, -8.640594],
[41.161185, -8.640648],
[41.161266, -8.640756],
[41.161311, -8.640819],
],
},
],
"Taxi-424": [
{
time: 1375482458000,
positions: [
[41.153724, -8.662059],
[41.154426, -8.662572],
[41.156019, -8.663661],
[41.157738, -8.664795],
[41.158368, -8.664399],
[41.159394, -8.661807],
[41.160681, -8.65845],
[41.161896, -8.656056],
[41.162031, -8.655777],
[41.162049, -8.655768],
[41.16213, -8.654733],
[41.161806, -8.652861],
[41.1615, -8.651034],
[41.16042, -8.650269],
[41.15916, -8.648712],
[41.15754, -8.646651],
[41.154228, -8.645076],
[41.151024, -8.644707],
[41.149179, -8.641215],
[41.150682, -8.636409],
[41.151348, -8.632989],
[41.15223, -8.632494],
[41.152221, -8.632521],
[41.152221, -8.63253],
[41.152293, -8.632521],
[41.152626, -8.631603],
[41.152536, -8.629992],
[41.152437, -8.626842],
[41.152554, -8.626419],
[41.151879, -8.625969],
[41.150556, -8.625897],
[41.148756, -8.62551],
[41.147919, -8.62335],
[41.147802, -8.622855],
[41.14737, -8.620794],
[41.146524, -8.620272],
[41.146146, -8.620137],
[41.146308, -8.618382],
[41.145966, -8.617545],
[41.14494, -8.61732],
[41.144976, -8.616726],
[41.145354, -8.615907],
[41.145507, -8.615574],
[41.145876, -8.614521],
[41.145966, -8.61318],
[41.146047, -8.612172],
[41.146794, -8.612262],
[41.147703, -8.612118],
[41.147874, -8.612172],
[41.148144, -8.612478],
[41.148261, -8.612865],
[41.14845, -8.612991],
[41.148468, -8.613036],
[41.14872, -8.613009],
[41.148954, -8.612955],
[41.149125, -8.612964],
[41.149467, -8.612946],
[41.149917, -8.612946],
[41.149935, -8.613792],
[41.149323, -8.614197],
],
},
{
time: 1375483996000,
positions: [
[41.157441, -8.628435],
[41.157495, -8.6283],
[41.157927, -8.627985],
[41.158827, -8.628822],
[41.158602, -8.629992],
[41.158521, -8.630046],
[41.157873, -8.630361],
[41.156982, -8.629362],
[41.157261, -8.628273],
[41.157441, -8.626608],
[41.157207, -8.625132],
[41.157198, -8.62506],
[41.156325, -8.625168],
[41.155551, -8.62506],
[41.155731, -8.623449],
[41.156028, -8.621667],
[41.155434, -8.620047],
[41.15529, -8.619273],
[41.155146, -8.618508],
[41.154705, -8.61615],
[41.154228, -8.613603],
[41.153598, -8.612496],
[41.152977, -8.6121],
[41.151924, -8.612235],
[41.15115, -8.612172],
[41.15052, -8.612109],
[41.150196, -8.612433],
[41.14998, -8.613297],
[41.149989, -8.614224],
],
},
],
"Taxi-529": [
{
time: 1375481253000,
positions: [
[41.150574, -8.646624],
[41.149215, -8.646255],
[41.148387, -8.645904],
[41.148162, -8.644185],
[41.148081, -8.64216],
[41.148459, -8.640144],
[41.148369, -8.638137],
[41.148171, -8.636148],
[41.147667, -8.63433],
[41.146812, -8.632566],
[41.146326, -8.63127],
[41.14647, -8.62965],
[41.145336, -8.628183],
[41.145417, -8.626257],
[41.145624, -8.623971],
[41.145903, -8.621316],
[41.146002, -8.620569],
[41.145984, -8.620569],
[41.145984, -8.620389],
[41.146218, -8.618931],
[41.146065, -8.617599],
[41.144949, -8.617302],
[41.145057, -8.616537],
[41.145255, -8.616141],
[41.145534, -8.615538],
[41.145759, -8.614908],
[41.145984, -8.614233],
[41.146128, -8.61408],
[41.146344, -8.614278],
[41.146353, -8.614269],
],
},
{
time: 1375482720000,
positions: [
[41.150628, -8.647641],
[41.150673, -8.646723],
[41.149206, -8.646201],
[41.148459, -8.645958],
[41.148171, -8.644302],
[41.14809, -8.642088],
[41.148387, -8.640738],
[41.148423, -8.639055],
[41.148324, -8.637318],
[41.148018, -8.635527],
[41.147487, -8.633889],
[41.146533, -8.63217],
[41.145102, -8.629956],
[41.14467, -8.627184],
[41.14476, -8.626104],
[41.144355, -8.623854],
[41.143572, -8.621595],
[41.142357, -8.620065],
[41.141331, -8.618409],
[41.141034, -8.617689],
[41.140818, -8.617275],
[41.14062, -8.616825],
[41.140521, -8.616051],
[41.140728, -8.615421],
[41.141241, -8.614566],
[41.14125, -8.614233],
[41.141403, -8.61399],
[41.141466, -8.613414],
[41.141142, -8.610624],
[41.140818, -8.609985],
[41.141061, -8.608482],
[41.141439, -8.606871],
[41.141808, -8.605125],
[41.141835, -8.603118],
[41.142033, -8.600976],
[41.141538, -8.598798],
[41.140611, -8.596782],
[41.139882, -8.594595],
[41.140053, -8.592633],
[41.140071, -8.592345],
[41.140143, -8.591508],
[41.140809, -8.589231],
[41.142456, -8.587323],
[41.143617, -8.584965],
[41.143734, -8.58168],
[41.144328, -8.578854],
[41.144949, -8.577747],
[41.146191, -8.578989],
[41.145237, -8.580159],
[41.144904, -8.582364],
[41.145975, -8.583966],
[41.146731, -8.585199],
[41.147235, -8.586783],
[41.147793, -8.586414],
[41.148864, -8.586036],
[41.149224, -8.58762],
[41.148531, -8.588466],
[41.147244, -8.589033],
[41.14719, -8.588286],
[41.147505, -8.588061],
[41.147793, -8.587845],
[41.148144, -8.587575],
[41.148225, -8.587584],
[41.148225, -8.587593],
[41.148243, -8.587602],
],
},
],
};
function makeData() {
for (const [taxiId, [r, g, b]] of Object.entries(taxiColors)) {
const color = `rgb(${r},${g},${b})`;
const backgroundColor = `rgba(${r},${g},${b},${taxiBackgroundAlpha})`;
mapEntities[taxiId] = { color };
backgroundedMapEntities[taxiId] = { color: backgroundColor };
timelineEntities[taxiId] = { color, label: taxiId };
}
for (const [taxiId, rides] of Object.entries(taxiRides)) {
observationsPerEntity[taxiId] = {};
for (const { time: start, positions } of rides) {
const rideId = `${taxiId}/${start}`;
const end = start + (positions.length - 1) * positionInterval;
events[rideId] = {
entityIds: [taxiId],
time: { start, end },
};
observationsPerEvent[rideId] = {};
let minLat = Infinity;
let maxLat = -Infinity;
let minLon = Infinity;
let maxLon = -Infinity;
for (const [index, [latitude, longitude]] of positions.entries()) {
const observationId = `${rideId}/${index}`;
const observation = {
type: "observation",
entityId: taxiId,
latitude,
longitude,
time: start + index * positionInterval,
};
observations[observationId] = observation;
observationsPerEvent[rideId][observationId] = observation;
observationsPerEntity[taxiId][observationId] = observation;
if (latitude < minLat) {
minLat = latitude;
}
if (latitude > maxLat) {
maxLat = latitude;
}
if (longitude < minLon) {
minLon = longitude;
}
if (longitude > maxLon) {
maxLon = longitude;
}
}
boundsPerEvent[rideId] = [
[minLon, minLat],
[maxLon, maxLat],
];
}
}
}
export const mapEntities = {};
export const backgroundedMapEntities = {};
export const observations = {};
const timelineEntities = {};
const events = {};
export const observationsPerEvent = {};
export const observationsPerEntity = {};
export const boundsPerEvent = {};
export const timelineData = { entities: timelineEntities, events };
makeData(); <!doctype html>
<html>
<head>
<link rel="stylesheet" href="@ci/theme/mw/css/examples.css" />
</head>
<body>
<div style="display: flex; flex-direction: column; height: 100vh">
<div id="mw" style="flex: 1"></div>
<div id="kg" style="height: 160px"></div>
</div>
<script type="module" src="./code.js"></script>
</body>
</html> import Timeline from "kronograph/react/Timeline";
import { ObservationsLayer } from "mapweave/react/layers";
import { MapWeave } from "mapweave/react/mapbox";
import React, { useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import {
backgroundedMapEntities,
boundsPerEvent,
mapEntities,
observations,
observationsPerEntity,
observationsPerEvent,
timelineData,
} from "./data";
const mapWeaveOptions = { accessToken: VITE_MAPBOX_API_KEY };
const timelineOptions = {
focus: { backgroundColor: "rgba(255, 255, 255, 0.1)" },
scales: { showAtBottom: false },
};
const mainLayerOptions = {
mode: "individual",
};
const highlightLayerOptions = {
individual: { autoRange: { min: 0 } },
};
function observationsForEntities(entityIds) {
return entityIds.length === 1
? observationsPerEntity[entityIds[0]]
: entityIds.reduce((prev, id) => Object.assign(prev, observationsPerEntity[id]), {});
}
function Demo() {
const [timeFilterRange, setTimeFilterRange] = useState(null);
const [focusedEntityIds, setFocusedEntityIds] = useState([]);
const [pinnedEntityIds, setPinnedEntityIds] = useState([]);
const [selectedEventId, setSelectedEventId] = useState(null);
const { events, entities } = timelineData;
const timelineRef = useRef(null);
const mapweaveRef = useRef(null);
// For a selected event, highlight that ride only, otherwise all pinned and focused taxis
const highlightObservations = selectedEventId
? observationsPerEvent[selectedEventId]
: observationsForEntities([...focusedEntityIds, ...pinnedEntityIds]);
// If no selected event or focused taxi, show all taxis, foregrounding any pinned ones
let mainLayerEntities;
if (selectedEventId === null && focusedEntityIds.length === 0) {
mainLayerEntities = pinnedEntityIds.length > 0 ? backgroundedMapEntities : mapEntities;
}
return (
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
<MapWeave
style={{ flex: 1 }}
options={mapWeaveOptions}
ref={mapweaveRef}
onClick={(e) => {
// Get the time for a clicked observation or trajectory
const time = e.item?.time ?? e.item?.observations?.[0].time;
if (typeof time === "number") {
// Find the event for the entity that includes the required time
const eventId = Object.keys(timelineData.events).find((eventId) => {
const {
entityIds: [entityId],
time: { start, end },
} = timelineData.events[eventId];
return entityId === e.item.entityId && time >= start && time <= end;
});
if (eventId) {
timelineRef.current.ping(eventId);
}
}
}}
>
<ObservationsLayer
data={{ entities: mainLayerEntities || {}, observations }}
visible={mainLayerEntities !== undefined}
options={{ ...mainLayerOptions, timeFilterRange }}
/>
<ObservationsLayer
data={{ entities: mapEntities, observations: highlightObservations || {} }}
visible={highlightObservations !== undefined}
options={{ ...highlightLayerOptions, timeFilterRange }}
/>
</MapWeave>
<Timeline
entities={entities}
events={events}
style={{ height: "160px" }}
options={timelineOptions}
ref={timelineRef}
selection={selectedEventId || []}
onTimelineClick={({ eventIds, targetType }) => {
let clickedEventId = null;
if (targetType === "event") {
clickedEventId = eventIds[0];
// Pan and zoom to the event bounds using the Mapbox fitBounds API
mapweaveRef.current?.map.fitBounds(boundsPerEvent[clickedEventId], { padding: 50 });
}
setSelectedEventId(clickedEventId);
}}
onTimelineChange={({ focus, pin, range }) => {
if (focus !== undefined) {
setFocusedEntityIds(focus);
}
if (pin !== undefined) {
setPinnedEntityIds(pin);
}
if (range !== undefined) {
setTimeFilterRange(range);
}
}}
></Timeline>
</div>
);
}
const root = createRoot(document.getElementById("mw"));
root.render(<Demo />); // [r, g, b]
const taxiColors = {
"Taxi-304": [255, 17, 92],
"Taxi-424": [255, 177, 52],
"Taxi-529": [0, 161, 113],
};
const taxiBackgroundAlpha = 0.4;
// 15 second interval between positions
const positionInterval = 15000;
const taxiRides = {
"Taxi-304": [
{
time: 1375481298000,
positions: [
[41.140899, -8.610084],
[41.140917, -8.610093],
[41.140863, -8.610021],
[41.141007, -8.610165],
[41.141493, -8.613216],
[41.141448, -8.613378],
[41.141403, -8.613504],
[41.141151, -8.614341],
[41.140863, -8.61516],
[41.140773, -8.61543],
[41.140647, -8.616843],
[41.141052, -8.617698],
[41.141367, -8.618391],
[41.141736, -8.619066],
[41.142303, -8.619957],
[41.14269, -8.620461],
[41.143365, -8.62119],
[41.143977, -8.622603],
[41.1444, -8.623935],
[41.144814, -8.625978],
[41.144706, -8.628264],
[41.1453, -8.630262],
[41.145642, -8.630766],
[41.145633, -8.630739],
[41.145966, -8.631225],
[41.146839, -8.632503],
[41.147739, -8.632188],
[41.148981, -8.631099],
[41.150295, -8.629992],
[41.151132, -8.629191],
[41.151222, -8.629146],
[41.151204, -8.629137],
[41.152122, -8.628804],
[41.152347, -8.628723],
[41.152428, -8.628723],
[41.152626, -8.630298],
[41.152734, -8.632404],
[41.152446, -8.635212],
[41.152869, -8.637642],
[41.153526, -8.640468],
[41.154237, -8.642907],
[41.154408, -8.645571],
[41.154273, -8.648289],
[41.154327, -8.649333],
[41.154453, -8.649837],
[41.154426, -8.649828],
[41.154372, -8.649747],
[41.15439, -8.650017],
[41.153796, -8.650341],
[41.15304, -8.649756],
[41.15214, -8.649261],
[41.151078, -8.649738],
[41.150142, -8.649054],
[41.148774, -8.648964],
[41.148243, -8.648478],
],
},
{
time: 1375483130000,
positions: [
[41.140953, -8.610228],
[41.140962, -8.610192],
[41.141214, -8.610678],
[41.14152, -8.61318],
[41.141367, -8.613729],
[41.141106, -8.614566],
[41.140773, -8.61543],
[41.140629, -8.615862],
[41.140962, -8.617545],
[41.141952, -8.619408],
[41.143203, -8.620992],
[41.144139, -8.623071],
[41.144607, -8.624619],
[41.144796, -8.626365],
[41.144796, -8.628975],
[41.145993, -8.631279],
[41.147091, -8.632773],
[41.148207, -8.631738],
[41.149746, -8.630442],
[41.151204, -8.629164],
[41.15232, -8.628741],
[41.152401, -8.62875],
[41.152626, -8.6301],
[41.152689, -8.631594],
[41.152608, -8.633565],
[41.152482, -8.635986],
[41.153031, -8.638389],
[41.154246, -8.638218],
[41.156154, -8.637264],
[41.158044, -8.636328],
[41.15889, -8.635905],
[41.159223, -8.6364],
[41.159556, -8.638551],
[41.159889, -8.640504],
[41.159907, -8.640621],
[41.159934, -8.640783],
[41.160411, -8.641539],
[41.161167, -8.640693],
[41.161212, -8.640621],
[41.161167, -8.640594],
[41.161185, -8.640648],
[41.161266, -8.640756],
[41.161311, -8.640819],
],
},
],
"Taxi-424": [
{
time: 1375482458000,
positions: [
[41.153724, -8.662059],
[41.154426, -8.662572],
[41.156019, -8.663661],
[41.157738, -8.664795],
[41.158368, -8.664399],
[41.159394, -8.661807],
[41.160681, -8.65845],
[41.161896, -8.656056],
[41.162031, -8.655777],
[41.162049, -8.655768],
[41.16213, -8.654733],
[41.161806, -8.652861],
[41.1615, -8.651034],
[41.16042, -8.650269],
[41.15916, -8.648712],
[41.15754, -8.646651],
[41.154228, -8.645076],
[41.151024, -8.644707],
[41.149179, -8.641215],
[41.150682, -8.636409],
[41.151348, -8.632989],
[41.15223, -8.632494],
[41.152221, -8.632521],
[41.152221, -8.63253],
[41.152293, -8.632521],
[41.152626, -8.631603],
[41.152536, -8.629992],
[41.152437, -8.626842],
[41.152554, -8.626419],
[41.151879, -8.625969],
[41.150556, -8.625897],
[41.148756, -8.62551],
[41.147919, -8.62335],
[41.147802, -8.622855],
[41.14737, -8.620794],
[41.146524, -8.620272],
[41.146146, -8.620137],
[41.146308, -8.618382],
[41.145966, -8.617545],
[41.14494, -8.61732],
[41.144976, -8.616726],
[41.145354, -8.615907],
[41.145507, -8.615574],
[41.145876, -8.614521],
[41.145966, -8.61318],
[41.146047, -8.612172],
[41.146794, -8.612262],
[41.147703, -8.612118],
[41.147874, -8.612172],
[41.148144, -8.612478],
[41.148261, -8.612865],
[41.14845, -8.612991],
[41.148468, -8.613036],
[41.14872, -8.613009],
[41.148954, -8.612955],
[41.149125, -8.612964],
[41.149467, -8.612946],
[41.149917, -8.612946],
[41.149935, -8.613792],
[41.149323, -8.614197],
],
},
{
time: 1375483996000,
positions: [
[41.157441, -8.628435],
[41.157495, -8.6283],
[41.157927, -8.627985],
[41.158827, -8.628822],
[41.158602, -8.629992],
[41.158521, -8.630046],
[41.157873, -8.630361],
[41.156982, -8.629362],
[41.157261, -8.628273],
[41.157441, -8.626608],
[41.157207, -8.625132],
[41.157198, -8.62506],
[41.156325, -8.625168],
[41.155551, -8.62506],
[41.155731, -8.623449],
[41.156028, -8.621667],
[41.155434, -8.620047],
[41.15529, -8.619273],
[41.155146, -8.618508],
[41.154705, -8.61615],
[41.154228, -8.613603],
[41.153598, -8.612496],
[41.152977, -8.6121],
[41.151924, -8.612235],
[41.15115, -8.612172],
[41.15052, -8.612109],
[41.150196, -8.612433],
[41.14998, -8.613297],
[41.149989, -8.614224],
],
},
],
"Taxi-529": [
{
time: 1375481253000,
positions: [
[41.150574, -8.646624],
[41.149215, -8.646255],
[41.148387, -8.645904],
[41.148162, -8.644185],
[41.148081, -8.64216],
[41.148459, -8.640144],
[41.148369, -8.638137],
[41.148171, -8.636148],
[41.147667, -8.63433],
[41.146812, -8.632566],
[41.146326, -8.63127],
[41.14647, -8.62965],
[41.145336, -8.628183],
[41.145417, -8.626257],
[41.145624, -8.623971],
[41.145903, -8.621316],
[41.146002, -8.620569],
[41.145984, -8.620569],
[41.145984, -8.620389],
[41.146218, -8.618931],
[41.146065, -8.617599],
[41.144949, -8.617302],
[41.145057, -8.616537],
[41.145255, -8.616141],
[41.145534, -8.615538],
[41.145759, -8.614908],
[41.145984, -8.614233],
[41.146128, -8.61408],
[41.146344, -8.614278],
[41.146353, -8.614269],
],
},
{
time: 1375482720000,
positions: [
[41.150628, -8.647641],
[41.150673, -8.646723],
[41.149206, -8.646201],
[41.148459, -8.645958],
[41.148171, -8.644302],
[41.14809, -8.642088],
[41.148387, -8.640738],
[41.148423, -8.639055],
[41.148324, -8.637318],
[41.148018, -8.635527],
[41.147487, -8.633889],
[41.146533, -8.63217],
[41.145102, -8.629956],
[41.14467, -8.627184],
[41.14476, -8.626104],
[41.144355, -8.623854],
[41.143572, -8.621595],
[41.142357, -8.620065],
[41.141331, -8.618409],
[41.141034, -8.617689],
[41.140818, -8.617275],
[41.14062, -8.616825],
[41.140521, -8.616051],
[41.140728, -8.615421],
[41.141241, -8.614566],
[41.14125, -8.614233],
[41.141403, -8.61399],
[41.141466, -8.613414],
[41.141142, -8.610624],
[41.140818, -8.609985],
[41.141061, -8.608482],
[41.141439, -8.606871],
[41.141808, -8.605125],
[41.141835, -8.603118],
[41.142033, -8.600976],
[41.141538, -8.598798],
[41.140611, -8.596782],
[41.139882, -8.594595],
[41.140053, -8.592633],
[41.140071, -8.592345],
[41.140143, -8.591508],
[41.140809, -8.589231],
[41.142456, -8.587323],
[41.143617, -8.584965],
[41.143734, -8.58168],
[41.144328, -8.578854],
[41.144949, -8.577747],
[41.146191, -8.578989],
[41.145237, -8.580159],
[41.144904, -8.582364],
[41.145975, -8.583966],
[41.146731, -8.585199],
[41.147235, -8.586783],
[41.147793, -8.586414],
[41.148864, -8.586036],
[41.149224, -8.58762],
[41.148531, -8.588466],
[41.147244, -8.589033],
[41.14719, -8.588286],
[41.147505, -8.588061],
[41.147793, -8.587845],
[41.148144, -8.587575],
[41.148225, -8.587584],
[41.148225, -8.587593],
[41.148243, -8.587602],
],
},
],
};
function makeData() {
for (const [taxiId, [r, g, b]] of Object.entries(taxiColors)) {
const color = `rgb(${r},${g},${b})`;
const backgroundColor = `rgba(${r},${g},${b},${taxiBackgroundAlpha})`;
mapEntities[taxiId] = { color };
backgroundedMapEntities[taxiId] = { color: backgroundColor };
timelineEntities[taxiId] = { color, label: taxiId };
}
for (const [taxiId, rides] of Object.entries(taxiRides)) {
observationsPerEntity[taxiId] = {};
for (const { time: start, positions } of rides) {
const rideId = `${taxiId}/${start}`;
const end = start + (positions.length - 1) * positionInterval;
events[rideId] = {
entityIds: [taxiId],
time: { start, end },
};
observationsPerEvent[rideId] = {};
let minLat = Infinity;
let maxLat = -Infinity;
let minLon = Infinity;
let maxLon = -Infinity;
for (const [index, [latitude, longitude]] of positions.entries()) {
const observationId = `${rideId}/${index}`;
const observation = {
type: "observation",
entityId: taxiId,
latitude,
longitude,
time: start + index * positionInterval,
};
observations[observationId] = observation;
observationsPerEvent[rideId][observationId] = observation;
observationsPerEntity[taxiId][observationId] = observation;
if (latitude < minLat) {
minLat = latitude;
}
if (latitude > maxLat) {
maxLat = latitude;
}
if (longitude < minLon) {
minLon = longitude;
}
if (longitude > maxLon) {
maxLon = longitude;
}
}
boundsPerEvent[rideId] = [
[minLon, minLat],
[maxLon, maxLat],
];
}
}
}
export const mapEntities = {};
export const backgroundedMapEntities = {};
export const observations = {};
const timelineEntities = {};
const events = {};
export const observationsPerEvent = {};
export const observationsPerEntity = {};
export const boundsPerEvent = {};
export const timelineData = { entities: timelineEntities, events };
makeData(); <!doctype html>
<html>
<head>
<link rel="stylesheet" href="@ci/theme/mw/css/examples.css" />
</head>
<body>
<div id="mw"></div>
<script type="module" src="./code.jsx"></script>
</body>
</html>