Search

Event Handling

Interactions

See how MapWeave handles interaction events.

Event Handling
View live example →

Interact with the map to see the event object in the Event Details tab. Use the Filter Events tab to stop listening for specific events.

You can add event handlers to MapWeave to respond to user interactions. MapWeave will pass an object with details about the event to these event handlers.

import { MapWeave } from "mapweave/mapbox";
import { GeoJsonLayer, NetworkLayer, ObservationsLayer } from "mapweave/layers";
import { networkData, geojsonData, observationsData } from "./data";

const startTime = new Date(2025, 4, 6);

function setupTabPanels() {
  const tabNames = ["last-event", "history", "filter-events"];
  const tabs = tabNames.map((tabName) => byId(`${tabName}-tab`));
  const panels = tabNames.map((tabName) => byId(`${tabName}-panel`));
  for (const [index, tab] of Object.entries(tabs)) {
    const panel = panels[index];
    tab.addEventListener("click", () => {
      for (const tabElement of tabs) {
        tabElement.classList.remove("tab--active");
      }
      for (const panelElement of panels) {
        panelElement.classList.remove("tab-panel--active");
      }
      panel.classList.add("tab-panel--active");
      tab.classList.add("tab--active");
    });
  }
}

function populateDetailsPanel({ name, details }) {
  eventNameHeader.innerHTML = name;
  eventDetailsElement.innerText = details;
}

function stringifyDetails(eventDetails) {
  function replacer(key, value) {
    // Hide private fields
    if (key.startsWith("_")) {
      return undefined;
    }
    // Show the layer id instead of the full layer object
    if (key === "layer" && value && value.id !== undefined) {
      return { id: value.id };
    }
    return value;
  }
  return eventDetails ? JSON.stringify(eventDetails, replacer, 2) : "";
}

function selectListElement(listElement) {
  previousDisplayedListElement = displayedListElement;
  displayedListElement = listElement;
  displayedListElement.classList.add("list-item--active");
  if (previousDisplayedListElement !== undefined) {
    previousDisplayedListElement.classList.remove("list-item--active");
  }
  const { type: eventName, info: eventDetails } = JSON.parse(
    displayedListElement.getAttribute("data-event-details")
  );
  populateDetailsPanel({
    name: eventName,
    details: stringifyDetails(eventDetails),
  });
}

function addEvent(event) {
  const listElement = document.createElement("li");
  listElement.classList.add("list-item");
  listElement.innerText = event.type;
  listElement.setAttribute("data-event-details", stringifyDetails(event));
  eventListElement.insertBefore(listElement, eventListElement.querySelector("li"));
  return listElement;
}

function setActiveModifiers(modifierKeys) {
  Object.entries(modifierKeys).forEach(([modifierKey, isActive]) => {
    const span = modifiersContainer.querySelector(`[data-event-name="${modifierKey}"]`);
    if (isActive) {
      span.classList.add("modifier-key--active");
    } else {
      span.classList.remove("modifier-key--active");
    }
  });
}

function genericEventHandler(eventName, event) {
  if (selectedEvents[eventName]) {
    const newListElement = addEvent({ type: eventName, info: event });
    // display the details for the new event
    selectListElement(newListElement);
    // show which modifier keys are down
    if (event && event.modifierKeys) {
      setActiveModifiers(event.modifierKeys);
    }
  }
}

const byId = document.getElementById.bind(document);

const modifiersContainer = byId("modifiers");
const eventListElement = byId("event-list");
const clearEventsListButton = byId("clear-event-list");
const eventNameHeader = document.querySelector(".header--plain");
const eventDetailsElement = document.querySelector(".json-block");
const eventCheckboxes = [...document.querySelectorAll('.event-filter-list input[type="checkbox"]')];
const selectAll = byId("select-all");
const deselectAll = byId("deselect-all");

const eventMap = {
  click: true,
  "drag-start": true,
  "drag-move": true,
  "drag-end": true,
  hover: true,
  load: true,
  "view-change": true,
};

const initialSelectedEvents = {
  ...eventMap,
};

let selectedEvents = initialSelectedEvents;
let displayedListElement;
let previousDisplayedListElement;

setupTabPanels();

clearEventsListButton.addEventListener("click", () => {
  eventListElement.innerHTML = "";
  populateDetailsPanel({ name: " ", details: "{}" });
});

eventListElement.addEventListener("click", (evt) => {
  if (evt.target instanceof HTMLLIElement) {
    selectListElement(evt.target);
  }
});

selectAll.addEventListener("click", () => {
  eventCheckboxes.forEach((checkbox) => {
    checkbox.checked = true;
  });
  selectedEvents = eventMap;
});

deselectAll.addEventListener("click", () => {
  eventCheckboxes.forEach((checkbox) => {
    checkbox.checked = false;
  });
  selectedEvents = {};
});

eventCheckboxes.forEach((inputElement) => {
  inputElement.addEventListener("change", (evt) => {
    const eventName = evt.target.getAttribute("data-event-name");
    selectedEvents = { ...selectedEvents, [eventName]: evt.target.checked };
  });
});

const mapweave = new MapWeave({
  container: "mw",
  options: { accessToken: VITE_MAPBOX_API_KEY },
});

const geojsonLayer = new GeoJsonLayer({
  data: geojsonData,
});

const networkLayer = new NetworkLayer({
  data: networkData,
});

const observationsLayerOptions = {
  trajectory: {
    maximumInterval: 60 * 60 * 1000,
  },
  trackerNodes: {
    // These options link the network layer icons to the observed entities
    networkLayerId: networkLayer.id,
    currentTime: startTime,
  },
  individual: {
    autoRange: { min: 0 },
    minimumRadiusMeters: 1000,
  },
};

const observationsLayer = new ObservationsLayer({
  data: observationsData,
  options: observationsLayerOptions,
});

mapweave.layers([geojsonLayer, observationsLayer, networkLayer]);
mapweave.fitBounds();

// register event handlers
Object.keys(eventMap).forEach((eventName) => {
  mapweave.on(eventName, (event) => genericEventHandler(eventName, event));
});
export const observationsData = {
  entities: {
    blueShip: {
      color: "rgb(83, 131, 236)",
    },
    redShip: {
      color: "rgb(216, 81, 64)",
    },
    yellowShip: {
      color: "rgb(241, 191, 66)",
    },
    greenShip: {
      color: "rgb(88, 166, 92)",
    },
  },
  observations: {
    "blue-0": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.578355,
      longitude: -5.197542,
      time: 1746489600000,
    },
    "blue-1": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.406994,
      longitude: -5.210292,
      time: 1746493200000,
    },
    "blue-2": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.272794,
      longitude: -5.208974,
      time: 1746496800000,
    },
    "blue-3": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.142754,
      longitude: -5.184721,
      time: 1746500400000,
    },
    "blue-4": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.97328,
      longitude: -5.185769,
      time: 1746504000000,
    },
    "blue-5": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.837737,
      longitude: -5.164967,
      time: 1746507600000,
    },
    "blue-6": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.670558,
      longitude: -5.192878,
      time: 1746511200000,
    },
    "blue-7": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.504643,
      longitude: -5.186715,
      time: 1746514800000,
    },
    "blue-8": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.365212,
      longitude: -5.17317,
      time: 1746518400000,
    },
    "blue-9": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.2161,
      longitude: -5.162903,
      time: 1746522000000,
    },
    "blue-10": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.052743,
      longitude: -5.226897,
      time: 1746525600000,
    },
    "blue-11": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.892953,
      longitude: -5.333351,
      time: 1746529200000,
    },
    "blue-12": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.711297,
      longitude: -5.375885,
      time: 1746532800000,
    },
    "blue-13": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.568214,
      longitude: -5.457204,
      time: 1746536400000,
    },
    "blue-14": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.409118,
      longitude: -5.529224,
      time: 1746540000000,
    },
    "blue-15": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.242918,
      longitude: -5.60549,
      time: 1746543600000,
    },
    "blue-16": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.071092,
      longitude: -5.692136,
      time: 1746547200000,
    },
    "blue-17": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.899728,
      longitude: -5.787732,
      time: 1746550800000,
    },
    "blue-18": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.739952,
      longitude: -5.85027,
      time: 1746554400000,
    },
    "blue-19": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.602765,
      longitude: -5.974733,
      time: 1746558000000,
    },
    "blue-20": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.474164,
      longitude: -6.114349,
      time: 1746561600000,
    },
    "blue-21": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.362769,
      longitude: -6.264626,
      time: 1746565200000,
    },
    "blue-22": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.204582,
      longitude: -6.360486,
      time: 1746568800000,
    },
    "blue-23": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.084279,
      longitude: -6.526525,
      time: 1746572400000,
    },
    "blue-24": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.941434,
      longitude: -6.627714,
      time: 1746576000000,
    },
    "blue-25": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.815822,
      longitude: -6.768253,
      time: 1746579600000,
    },
    "blue-26": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.68009,
      longitude: -6.922329,
      time: 1746583200000,
    },
    "blue-27": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.56413,
      longitude: -7.037884,
      time: 1746586800000,
    },
    "blue-28": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.406445,
      longitude: -7.036782,
      time: 1746590400000,
    },
    "blue-29": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.247185,
      longitude: -7.004507,
      time: 1746594000000,
    },
    "blue-30": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.08487,
      longitude: -7.014869,
      time: 1746597600000,
    },
    "blue-31": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.937541,
      longitude: -6.993953,
      time: 1746601200000,
    },
    "blue-32": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.784533,
      longitude: -6.984443,
      time: 1746604800000,
    },
    "blue-33": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.639579,
      longitude: -6.960981,
      time: 1746608400000,
    },
    "blue-34": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.467846,
      longitude: -6.961437,
      time: 1746612000000,
    },
    "blue-35": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.32475,
      longitude: -6.92981,
      time: 1746615600000,
    },
    "blue-36": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.152295,
      longitude: -6.93082,
      time: 1746619200000,
    },
    "red-0": {
      type: "observation",
      entityId: "redShip",
      latitude: 52.370025,
      longitude: 3.706578,
      time: 1746489600000,
    },
    "red-1": {
      type: "observation",
      entityId: "redShip",
      latitude: 52.215551,
      longitude: 3.465443,
      time: 1746493200000,
    },
    "red-2": {
      type: "observation",
      entityId: "redShip",
      latitude: 52.06524,
      longitude: 3.234558,
      time: 1746496800000,
    },
    "red-3": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.919175,
      longitude: 2.966129,
      time: 1746500400000,
    },
    "red-4": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.736291,
      longitude: 2.750087,
      time: 1746504000000,
    },
    "red-5": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.575317,
      longitude: 2.48578,
      time: 1746507600000,
    },
    "red-6": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.418045,
      longitude: 2.244322,
      time: 1746511200000,
    },
    "red-7": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.263755,
      longitude: 1.993267,
      time: 1746514800000,
    },
    "red-8": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.140054,
      longitude: 1.762104,
      time: 1746518400000,
    },
    "red-9": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.971223,
      longitude: 1.516238,
      time: 1746522000000,
    },
    "red-10": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.852823,
      longitude: 1.357977,
      time: 1746525600000,
    },
    "red-11": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.741735,
      longitude: 1.215997,
      time: 1746529200000,
    },
    "red-12": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.631068,
      longitude: 1.037502,
      time: 1746532800000,
    },
    "red-13": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.53398,
      longitude: 0.888554,
      time: 1746536400000,
    },
    "red-14": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.428207,
      longitude: 0.717581,
      time: 1746540000000,
    },
    "red-15": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.30806,
      longitude: 0.549199,
      time: 1746543600000,
    },
    "red-16": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.196893,
      longitude: 0.41216,
      time: 1746547200000,
    },
    "red-17": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.11447,
      longitude: 0.251521,
      time: 1746550800000,
    },
    "red-18": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.001293,
      longitude: 0.076072,
      time: 1746554400000,
    },
    "red-19": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.976381,
      longitude: -0.224351,
      time: 1746558000000,
    },
    "red-20": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.953019,
      longitude: -0.49948,
      time: 1746561600000,
    },
    "red-21": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.941289,
      longitude: -0.756986,
      time: 1746565200000,
    },
    "red-22": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.922577,
      longitude: -1.06356,
      time: 1746568800000,
    },
    "red-23": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.919638,
      longitude: -1.322502,
      time: 1746572400000,
    },
    "red-24": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.864749,
      longitude: -1.595358,
      time: 1746576000000,
    },
    "red-25": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.843586,
      longitude: -1.889306,
      time: 1746579600000,
    },
    "red-26": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.86042,
      longitude: -2.184169,
      time: 1746583200000,
    },
    "red-27": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.822904,
      longitude: -2.451595,
      time: 1746586800000,
    },
    "red-28": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.717736,
      longitude: -2.732433,
      time: 1746590400000,
    },
    "red-29": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.587322,
      longitude: -3.005417,
      time: 1746594000000,
    },
    "red-30": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.45591,
      longitude: -3.266489,
      time: 1746597600000,
    },
    "red-31": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.319815,
      longitude: -3.515813,
      time: 1746601200000,
    },
    "red-32": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.199566,
      longitude: -3.802024,
      time: 1746604800000,
    },
    "red-33": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.107795,
      longitude: -4.089816,
      time: 1746608400000,
    },
    "red-34": {
      type: "observation",
      entityId: "redShip",
      latitude: 48.968048,
      longitude: -4.332165,
      time: 1746612000000,
    },
    "red-35": {
      type: "observation",
      entityId: "redShip",
      latitude: 48.84175,
      longitude: -4.635675,
      time: 1746615600000,
    },
    "red-36": {
      type: "observation",
      entityId: "redShip",
      latitude: 48.729006,
      longitude: -4.887932,
      time: 1746619200000,
    },
    "yellow-0": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.742687,
      longitude: -5.517006,
      time: 1746489600000,
    },
    "yellow-1": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.77585,
      longitude: -5.174162,
      time: 1746493200000,
    },
    "yellow-2": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.859453,
      longitude: -4.812579,
      time: 1746496800000,
    },
    "yellow-3": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.887234,
      longitude: -4.482283,
      time: 1746500400000,
    },
    "yellow-4": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.969189,
      longitude: -4.153157,
      time: 1746504000000,
    },
    "yellow-5": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.996704,
      longitude: -3.79536,
      time: 1746507600000,
    },
    "yellow-6": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.062269,
      longitude: -3.458585,
      time: 1746511200000,
    },
    "yellow-7": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.110711,
      longitude: -3.103859,
      time: 1746514800000,
    },
    "yellow-8": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.177785,
      longitude: -2.747572,
      time: 1746518400000,
    },
    "yellow-9": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.219657,
      longitude: -2.413479,
      time: 1746522000000,
    },
    "yellow-10": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.225445,
      longitude: -2.173332,
      time: 1746525600000,
    },
    "yellow-11": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.27729,
      longitude: -1.883357,
      time: 1746529200000,
    },
    "yellow-12": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.268126,
      longitude: -1.636639,
      time: 1746532800000,
    },
    "yellow-13": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.293733,
      longitude: -1.368887,
      time: 1746536400000,
    },
    "yellow-14": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.309745,
      longitude: -1.121686,
      time: 1746540000000,
    },
    "yellow-15": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.33365,
      longitude: -0.863134,
      time: 1746543600000,
    },
    "yellow-16": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.349588,
      longitude: -0.589228,
      time: 1746547200000,
    },
    "yellow-17": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.376335,
      longitude: -0.357044,
      time: 1746550800000,
    },
    "yellow-18": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.395133,
      longitude: -0.088295,
      time: 1746554400000,
    },
    "yellow-19": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.460482,
      longitude: 0.086023,
      time: 1746558000000,
    },
    "yellow-20": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.550902,
      longitude: 0.238852,
      time: 1746561600000,
    },
    "yellow-21": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.611105,
      longitude: 0.411044,
      time: 1746565200000,
    },
    "yellow-22": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.694637,
      longitude: 0.578058,
      time: 1746568800000,
    },
    "yellow-23": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.77433,
      longitude: 0.737261,
      time: 1746572400000,
    },
    "yellow-24": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.853582,
      longitude: 0.898914,
      time: 1746576000000,
    },
    "yellow-25": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.922793,
      longitude: 1.071283,
      time: 1746579600000,
    },
    "yellow-26": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.997163,
      longitude: 1.246802,
      time: 1746583200000,
    },
    "yellow-27": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.061204,
      longitude: 1.421502,
      time: 1746586800000,
    },
    "yellow-28": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.179615,
      longitude: 1.491568,
      time: 1746590400000,
    },
    "yellow-29": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.331974,
      longitude: 1.535249,
      time: 1746594000000,
    },
    "yellow-30": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.446136,
      longitude: 1.606495,
      time: 1746597600000,
    },
    "yellow-31": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.577289,
      longitude: 1.67907,
      time: 1746601200000,
    },
    "yellow-32": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.712057,
      longitude: 1.730155,
      time: 1746604800000,
    },
    "yellow-33": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.842222,
      longitude: 1.787359,
      time: 1746608400000,
    },
    "yellow-34": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.967187,
      longitude: 1.847835,
      time: 1746612000000,
    },
    "yellow-35": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 52.110784,
      longitude: 1.920065,
      time: 1746615600000,
    },
    "yellow-36": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 52.220649,
      longitude: 1.988451,
      time: 1746619200000,
    },
    "green-0": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.642166,
      longitude: -0.206432,
      time: 1746489600000,
    },
    "green-1": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.72148,
      longitude: -0.045392,
      time: 1746493200000,
    },
    "green-2": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.773523,
      longitude: 0.094019,
      time: 1746496800000,
    },
    "green-3": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.812568,
      longitude: 0.246296,
      time: 1746500400000,
    },
    "green-4": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.89297,
      longitude: 0.393587,
      time: 1746504000000,
    },
    "green-5": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.922868,
      longitude: 0.517232,
      time: 1746507600000,
    },
    "green-6": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.015095,
      longitude: 0.678897,
      time: 1746511200000,
    },
    "green-7": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.0724,
      longitude: 0.813281,
      time: 1746514800000,
    },
    "green-8": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.130035,
      longitude: 0.976137,
      time: 1746518400000,
    },
    "green-9": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.177675,
      longitude: 1.110818,
      time: 1746522000000,
    },
    "green-10": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.242837,
      longitude: 1.146687,
      time: 1746525600000,
    },
    "green-11": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.336519,
      longitude: 1.199466,
      time: 1746529200000,
    },
    "green-12": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.396971,
      longitude: 1.248527,
      time: 1746532800000,
    },
    "green-13": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.493277,
      longitude: 1.311625,
      time: 1746536400000,
    },
    "green-14": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.589783,
      longitude: 1.326871,
      time: 1746540000000,
    },
    "green-15": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.668123,
      longitude: 1.401063,
      time: 1746543600000,
    },
    "green-16": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.746436,
      longitude: 1.422752,
      time: 1746547200000,
    },
    "green-17": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.826394,
      longitude: 1.484131,
      time: 1746550800000,
    },
    "green-18": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.893871,
      longitude: 1.529005,
      time: 1746554400000,
    },
    "green-19": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.967241,
      longitude: 1.67732,
      time: 1746558000000,
    },
    "green-20": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.013555,
      longitude: 1.84518,
      time: 1746561600000,
    },
    "green-21": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.086254,
      longitude: 2.040142,
      time: 1746565200000,
    },
    "green-22": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.174896,
      longitude: 2.184954,
      time: 1746568800000,
    },
    "green-23": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.239722,
      longitude: 2.367642,
      time: 1746572400000,
    },
    "green-24": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.291813,
      longitude: 2.526876,
      time: 1746576000000,
    },
    "green-25": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.368212,
      longitude: 2.66567,
      time: 1746579600000,
    },
    "green-26": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.438635,
      longitude: 2.857788,
      time: 1746583200000,
    },
    "green-27": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.511591,
      longitude: 3.003932,
      time: 1746586800000,
    },
    "green-28": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.573699,
      longitude: 3.11906,
      time: 1746590400000,
    },
    "green-29": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.660869,
      longitude: 3.197849,
      time: 1746594000000,
    },
    "green-30": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.728586,
      longitude: 3.320669,
      time: 1746597600000,
    },
    "green-31": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.828897,
      longitude: 3.432333,
      time: 1746601200000,
    },
    "green-32": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.902916,
      longitude: 3.548628,
      time: 1746604800000,
    },
    "green-33": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.965803,
      longitude: 3.648003,
      time: 1746608400000,
    },
    "green-34": {
      type: "observation",
      entityId: "greenShip",
      latitude: 52.045515,
      longitude: 3.761248,
      time: 1746612000000,
    },
    "green-35": {
      type: "observation",
      entityId: "greenShip",
      latitude: 52.135806,
      longitude: 3.87378,
      time: 1746615600000,
    },
    "green-36": {
      type: "observation",
      entityId: "greenShip",
      latitude: 52.215531,
      longitude: 3.963111,
      time: 1746619200000,
    },
  },
};

function makePolygon(y, x, dx) {
  const dy = Math.cos((y * Math.PI) / 180) * dx;
  return [
    [x + dx, y],
    [x, y + dy],
    [x - dx, y],
    [x, y - dy],
    [x + dx, y],
  ];
}

const londonCoordinates = makePolygon(51.5, -0.13, 0.7);
const cambridgeCoordinates = makePolygon(52.2, 0.12, 0.4);
const oxfordCoordinates = makePolygon(51.75, -1.26, 0.4);

export const geojsonData = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      id: "London",
      geometry: {
        type: "Polygon",
        coordinates: [londonCoordinates],
      },
      properties: {
        color: "rgba(255,0,0,0.6)",
      },
    },
    {
      type: "Feature",
      id: "Cambridge",
      geometry: {
        type: "Polygon",
        coordinates: [cambridgeCoordinates],
      },
      properties: {
        color: "rgba(0,255,0,0.6)",
      },
    },
    {
      type: "Feature",
      id: "Oxford",
      geometry: {
        type: "Polygon",
        coordinates: [oxfordCoordinates],
      },
      properties: {
        color: "rgba(0,255,255,0.6)",
      },
    },
  ],
};

const shipsNetworkData = Object.fromEntries(
  Object.entries(observationsData.entities).map(([shipId, { color }]) => [
    shipId,
    {
      type: "node",
      color: "#101010",
      size: 10,
      border: { width: 2, color },
      image: { url: "/public/images/icons/ci/yacht.svg", color },
    },
  ])
);

export const networkData = {
  ...shipsNetworkData,
  cardiff: {
    type: "node",
    latitude: 51.48,
    longitude: -3.17,
    color: "#2471a3",
    label: [
      {
        text: "Cardiff",
        backgroundColor: "#566573",
      },
    ],
  },
  edinburgh: {
    type: "node",
    latitude: 55.95,
    longitude: -3.19,
    color: "#2471a3",
    label: [
      {
        text: "Edinburgh",
        backgroundColor: "#566573",
      },
    ],
  },
  alice: {
    type: "node",
    color: "#2471a3",
    label: [
      {
        text: "Alice",
        backgroundColor: "#566573",
      },
    ],
  },
  bob: {
    type: "node",
    color: "#2471a3",
    label: [
      {
        text: "Bob",
        backgroundColor: "#566573",
      },
    ],
  },
  link1: {
    type: "link",
    id1: "edinburgh",
    id2: "cardiff",
    color: "#2471a3",
  },
  link2: {
    type: "link",
    id1: "alice",
    id2: "edinburgh",
    color: "#2471a3",
  },
  link3: {
    type: "link",
    id1: "bob",
    id2: "cardiff",
    color: "#2471a3",
  },
};
<!doctype html>
<html>
  <body>
    <div style="height: 100%; display: flex; flex-direction: column; max-height: 100%">
      <div style="display: flex; flex: 1 0 auto; box-sizing: border-box; max-height: 100%">
        <div id="mw" style="flex: 2 0 50%"></div>
        <div class="tab-panel-container">
          <div style="display: flex">
            <button id="last-event-tab" class="tab tab--active">Event Details</button>
            <button id="history-tab" class="tab">History</button>
            <button id="filter-events-tab" class="tab">Filter Events</button>
          </div>
          <div id="last-event-panel" class="tab-panel tab-panel--active">
            <h3 class="header--plain">&nbsp;</h3>
            <pre class="json-block">{}</pre>
          </div>
          <div id="history-panel" class="tab-panel" style="flex-wrap: nowrap; align-items: center">
            <button id="clear-event-list" class="button button--small" style="flex: 0 0 auto">
              clear
            </button>
            <ul id="event-list" class="event-list"></ul>
          </div>
          <div id="filter-events-panel" class="tab-panel" style="flex-wrap: nowrap">
            <div class="filter-buttons">
              <button id="select-all" class="button button--small">select all</button>
              <button id="deselect-all" class="button button--small">deselect all</button>
            </div>
            <div class="event-filter-list">
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="click" />click
              </label>
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="drag-start" />drag-start
              </label>
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="drag-move" />drag-move
              </label>
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="drag-end" />drag-end
              </label>
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="hover" />hover
              </label>
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="load" />load
              </label>
              <label class="event-label">
                <input checked="true" type="checkbox" data-event-name="view-change" />view-change
              </label>
            </div>
          </div>
        </div>
      </div>
    </div>
    <script type="module" src="./code.js"></script>
  </body>
</html>
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import { MapWeave } from "mapweave/react/mapbox";
import { GeoJsonLayer, NetworkLayer, ObservationsLayer } from "mapweave/react/layers";

import { networkData, geojsonData, observationsData } from "./data";

const startTime = new Date(2025, 4, 6);
const networkLayerId = "trackerNodes";

const mapWeaveOptions = { accessToken: VITE_MAPBOX_API_KEY };

const observationsLayerOptions = {
  trajectory: {
    maximumInterval: 60 * 60 * 1000,
  },
  trackerNodes: {
    // These options link the network layer icons to the observed entities
    networkLayerId,
    currentTime: startTime,
  },

  individual: {
    autoRange: { min: 0 },
    minimumRadiusMeters: 1000,
  },
};

function FilterEvents({ changeHandler, selectedEvents }) {
  return (
    <div className="event-filter-list">
      {Object.keys(eventMap).map((propName) => (
        <label className="event-label" style={{ width: "265px" }} key={propName}>
          <input
            onChange={() => changeHandler(propName)}
            checked={selectedEvents[propName]}
            type="checkbox"
          />
          {propName}
        </label>
      ))}
    </div>
  );
}

function EventList({ events, handleClick, displayedEventIndex }) {
  return (
    <ul className="event-list">
      {events.map((event, eventIdx) => (
        <li
          key={eventIdx}
          className={`list-item${displayedEventIndex === eventIdx ? "list-item--active" : ""}`}
          onClick={() => handleClick(eventIdx)}
        >
          {event.type}
        </li>
      ))}
    </ul>
  );
}

function TabPanels({ tabs, setSelectedTab, selectedTab }) {
  return (
    <div className="tab-panel-container">
      <div style={{ display: "flex" }}>
        {tabs.map((tab) => (
          <button
            onClick={() => setSelectedTab(tab.id)}
            className={`tab${selectedTab === tab.id ? "tab--active" : ""}`}
            key={tab.id}
          >
            {tab.title}
          </button>
        ))}
      </div>
      {tabs.map((tab) => (
        <div
          key={tab.id}
          style={tab.panelStyle}
          className={`tab-panel${selectedTab === tab.id ? "tab-panel--active" : ""}`}
        >
          {tab.panel}
        </div>
      ))}
    </div>
  );
}

const eventMap = {
  onClick: true,
  onDragEnd: true,
  onDragMove: true,
  onDragStart: true,
  onHover: true,
  onLoad: true,
  onViewChange: true,
};

function stringifyDetails(eventDetails) {
  function replacer(key, value) {
    // Hide private fields
    if (key.startsWith("_")) {
      return undefined;
    }
    // Show the layer id instead of the full layer object
    if (key === "layer" && value && value.id !== undefined) {
      return { id: value.id };
    }
    return value;
  }
  return eventDetails ? JSON.stringify(eventDetails, replacer, 2) : "";
}

function Demo() {
  const [eventList, setEventList] = useState([]);
  const [selectedTab, setSelectedTab] = useState("event-details");
  const [selectedEvents, setSelectedEvents] = useState({
    ...eventMap,
  });
  const [displayedEventIndex, setDisplayedEventIndex] = useState(0);

  const displayedEvent = eventList[displayedEventIndex];

  function genericEventHandler(propName, event) {
    if (selectedEvents[propName]) {
      setDisplayedEventIndex(0);
      setEventList((prevEventList) => [{ type: propName, info: event }, ...prevEventList]);
    }
  }

  const eventDetailsPanel = (
    <>
      <h3 className="header--plain">{displayedEvent ? displayedEvent.type : <br />}</h3>
      <pre className="json-block">{}</pre>
    </>
  );

  const historyPanel = (
    <>
      <button
        className="button button--small"
        style={{ flex: "0 0 auto" }}
        onClick={() => setEventList([])}
      >
        clear
      </button>
      <EventList
        events={eventList}
        handleClick={(idx) => setDisplayedEventIndex(idx)}
        displayedEventIndex={displayedEventIndex}
      />
    </>
  );

  const filterEventsPanel = (
    <>
      <div className="filter-buttons">
        <button className="button button--small" onClick={() => setSelectedEvents(eventMap)}>
          select all
        </button>
        <button className="button button--small" onClick={() => setSelectedEvents({})}>
          deselect all
        </button>
      </div>
      <FilterEvents
        changeHandler={(propName) => {
          setSelectedEvents({
            ...selectedEvents,
            [propName]: !selectedEvents[propName],
          });
        }}
        selectedEvents={selectedEvents}
      />
    </>
  );

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column", maxHeight: "100%" }}>
      <div
        style={{
          display: "flex",
          flex: "1 0 auto",
          boxSizing: "border-box",
          maxHeight: "100%",
        }}
      >
        <MapWeave
          options={mapWeaveOptions}
          style={{ flex: "2 0 50%" }}
          onClick={(event) => genericEventHandler("onClick", event)}
          onDragEnd={(event) => genericEventHandler("onDragEnd", event)}
          onDragMove={(event) => genericEventHandler("onDragMove", event)}
          onDragStart={(event) => genericEventHandler("onDragStart", event)}
          onHover={(event) => genericEventHandler("onHover", event)}
          onLoad={(event) => genericEventHandler("onLoad", event)}
          onViewChange={(event) => genericEventHandler("onViewChange", event)}
        >
          <GeoJsonLayer data={geojsonData} />
          <ObservationsLayer data={observationsData} options={observationsLayerOptions} />
          <NetworkLayer data={networkData} id={networkLayerId} />
        </MapWeave>
        <TabPanels
          tabs={[
            {
              title: "Event Details",
              id: "event-details",
              panel: eventDetailsPanel,
            },
            {
              title: "History",
              id: "event-history",
              panelStyle: { flexWrap: "nowrap", alignItems: "center" },
              panel: historyPanel,
            },
            {
              title: "Filter Events",
              id: "filter-events",
              panelStyle: { flexWrap: "nowrap" },
              panel: filterEventsPanel,
            },
          ]}
          setSelectedTab={setSelectedTab}
          selectedTab={selectedTab}
        />
      </div>
    </div>
  );
}

const root = createRoot(document.getElementById("mw"));
root.render(<Demo />);
export const observationsData = {
  entities: {
    blueShip: {
      color: "rgb(83, 131, 236)",
    },
    redShip: {
      color: "rgb(216, 81, 64)",
    },
    yellowShip: {
      color: "rgb(241, 191, 66)",
    },
    greenShip: {
      color: "rgb(88, 166, 92)",
    },
  },
  observations: {
    "blue-0": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.578355,
      longitude: -5.197542,
      time: 1746489600000,
    },
    "blue-1": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.406994,
      longitude: -5.210292,
      time: 1746493200000,
    },
    "blue-2": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.272794,
      longitude: -5.208974,
      time: 1746496800000,
    },
    "blue-3": {
      type: "observation",
      entityId: "blueShip",
      latitude: 54.142754,
      longitude: -5.184721,
      time: 1746500400000,
    },
    "blue-4": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.97328,
      longitude: -5.185769,
      time: 1746504000000,
    },
    "blue-5": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.837737,
      longitude: -5.164967,
      time: 1746507600000,
    },
    "blue-6": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.670558,
      longitude: -5.192878,
      time: 1746511200000,
    },
    "blue-7": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.504643,
      longitude: -5.186715,
      time: 1746514800000,
    },
    "blue-8": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.365212,
      longitude: -5.17317,
      time: 1746518400000,
    },
    "blue-9": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.2161,
      longitude: -5.162903,
      time: 1746522000000,
    },
    "blue-10": {
      type: "observation",
      entityId: "blueShip",
      latitude: 53.052743,
      longitude: -5.226897,
      time: 1746525600000,
    },
    "blue-11": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.892953,
      longitude: -5.333351,
      time: 1746529200000,
    },
    "blue-12": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.711297,
      longitude: -5.375885,
      time: 1746532800000,
    },
    "blue-13": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.568214,
      longitude: -5.457204,
      time: 1746536400000,
    },
    "blue-14": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.409118,
      longitude: -5.529224,
      time: 1746540000000,
    },
    "blue-15": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.242918,
      longitude: -5.60549,
      time: 1746543600000,
    },
    "blue-16": {
      type: "observation",
      entityId: "blueShip",
      latitude: 52.071092,
      longitude: -5.692136,
      time: 1746547200000,
    },
    "blue-17": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.899728,
      longitude: -5.787732,
      time: 1746550800000,
    },
    "blue-18": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.739952,
      longitude: -5.85027,
      time: 1746554400000,
    },
    "blue-19": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.602765,
      longitude: -5.974733,
      time: 1746558000000,
    },
    "blue-20": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.474164,
      longitude: -6.114349,
      time: 1746561600000,
    },
    "blue-21": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.362769,
      longitude: -6.264626,
      time: 1746565200000,
    },
    "blue-22": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.204582,
      longitude: -6.360486,
      time: 1746568800000,
    },
    "blue-23": {
      type: "observation",
      entityId: "blueShip",
      latitude: 51.084279,
      longitude: -6.526525,
      time: 1746572400000,
    },
    "blue-24": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.941434,
      longitude: -6.627714,
      time: 1746576000000,
    },
    "blue-25": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.815822,
      longitude: -6.768253,
      time: 1746579600000,
    },
    "blue-26": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.68009,
      longitude: -6.922329,
      time: 1746583200000,
    },
    "blue-27": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.56413,
      longitude: -7.037884,
      time: 1746586800000,
    },
    "blue-28": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.406445,
      longitude: -7.036782,
      time: 1746590400000,
    },
    "blue-29": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.247185,
      longitude: -7.004507,
      time: 1746594000000,
    },
    "blue-30": {
      type: "observation",
      entityId: "blueShip",
      latitude: 50.08487,
      longitude: -7.014869,
      time: 1746597600000,
    },
    "blue-31": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.937541,
      longitude: -6.993953,
      time: 1746601200000,
    },
    "blue-32": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.784533,
      longitude: -6.984443,
      time: 1746604800000,
    },
    "blue-33": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.639579,
      longitude: -6.960981,
      time: 1746608400000,
    },
    "blue-34": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.467846,
      longitude: -6.961437,
      time: 1746612000000,
    },
    "blue-35": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.32475,
      longitude: -6.92981,
      time: 1746615600000,
    },
    "blue-36": {
      type: "observation",
      entityId: "blueShip",
      latitude: 49.152295,
      longitude: -6.93082,
      time: 1746619200000,
    },
    "red-0": {
      type: "observation",
      entityId: "redShip",
      latitude: 52.370025,
      longitude: 3.706578,
      time: 1746489600000,
    },
    "red-1": {
      type: "observation",
      entityId: "redShip",
      latitude: 52.215551,
      longitude: 3.465443,
      time: 1746493200000,
    },
    "red-2": {
      type: "observation",
      entityId: "redShip",
      latitude: 52.06524,
      longitude: 3.234558,
      time: 1746496800000,
    },
    "red-3": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.919175,
      longitude: 2.966129,
      time: 1746500400000,
    },
    "red-4": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.736291,
      longitude: 2.750087,
      time: 1746504000000,
    },
    "red-5": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.575317,
      longitude: 2.48578,
      time: 1746507600000,
    },
    "red-6": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.418045,
      longitude: 2.244322,
      time: 1746511200000,
    },
    "red-7": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.263755,
      longitude: 1.993267,
      time: 1746514800000,
    },
    "red-8": {
      type: "observation",
      entityId: "redShip",
      latitude: 51.140054,
      longitude: 1.762104,
      time: 1746518400000,
    },
    "red-9": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.971223,
      longitude: 1.516238,
      time: 1746522000000,
    },
    "red-10": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.852823,
      longitude: 1.357977,
      time: 1746525600000,
    },
    "red-11": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.741735,
      longitude: 1.215997,
      time: 1746529200000,
    },
    "red-12": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.631068,
      longitude: 1.037502,
      time: 1746532800000,
    },
    "red-13": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.53398,
      longitude: 0.888554,
      time: 1746536400000,
    },
    "red-14": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.428207,
      longitude: 0.717581,
      time: 1746540000000,
    },
    "red-15": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.30806,
      longitude: 0.549199,
      time: 1746543600000,
    },
    "red-16": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.196893,
      longitude: 0.41216,
      time: 1746547200000,
    },
    "red-17": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.11447,
      longitude: 0.251521,
      time: 1746550800000,
    },
    "red-18": {
      type: "observation",
      entityId: "redShip",
      latitude: 50.001293,
      longitude: 0.076072,
      time: 1746554400000,
    },
    "red-19": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.976381,
      longitude: -0.224351,
      time: 1746558000000,
    },
    "red-20": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.953019,
      longitude: -0.49948,
      time: 1746561600000,
    },
    "red-21": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.941289,
      longitude: -0.756986,
      time: 1746565200000,
    },
    "red-22": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.922577,
      longitude: -1.06356,
      time: 1746568800000,
    },
    "red-23": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.919638,
      longitude: -1.322502,
      time: 1746572400000,
    },
    "red-24": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.864749,
      longitude: -1.595358,
      time: 1746576000000,
    },
    "red-25": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.843586,
      longitude: -1.889306,
      time: 1746579600000,
    },
    "red-26": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.86042,
      longitude: -2.184169,
      time: 1746583200000,
    },
    "red-27": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.822904,
      longitude: -2.451595,
      time: 1746586800000,
    },
    "red-28": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.717736,
      longitude: -2.732433,
      time: 1746590400000,
    },
    "red-29": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.587322,
      longitude: -3.005417,
      time: 1746594000000,
    },
    "red-30": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.45591,
      longitude: -3.266489,
      time: 1746597600000,
    },
    "red-31": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.319815,
      longitude: -3.515813,
      time: 1746601200000,
    },
    "red-32": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.199566,
      longitude: -3.802024,
      time: 1746604800000,
    },
    "red-33": {
      type: "observation",
      entityId: "redShip",
      latitude: 49.107795,
      longitude: -4.089816,
      time: 1746608400000,
    },
    "red-34": {
      type: "observation",
      entityId: "redShip",
      latitude: 48.968048,
      longitude: -4.332165,
      time: 1746612000000,
    },
    "red-35": {
      type: "observation",
      entityId: "redShip",
      latitude: 48.84175,
      longitude: -4.635675,
      time: 1746615600000,
    },
    "red-36": {
      type: "observation",
      entityId: "redShip",
      latitude: 48.729006,
      longitude: -4.887932,
      time: 1746619200000,
    },
    "yellow-0": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.742687,
      longitude: -5.517006,
      time: 1746489600000,
    },
    "yellow-1": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.77585,
      longitude: -5.174162,
      time: 1746493200000,
    },
    "yellow-2": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.859453,
      longitude: -4.812579,
      time: 1746496800000,
    },
    "yellow-3": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.887234,
      longitude: -4.482283,
      time: 1746500400000,
    },
    "yellow-4": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.969189,
      longitude: -4.153157,
      time: 1746504000000,
    },
    "yellow-5": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 49.996704,
      longitude: -3.79536,
      time: 1746507600000,
    },
    "yellow-6": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.062269,
      longitude: -3.458585,
      time: 1746511200000,
    },
    "yellow-7": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.110711,
      longitude: -3.103859,
      time: 1746514800000,
    },
    "yellow-8": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.177785,
      longitude: -2.747572,
      time: 1746518400000,
    },
    "yellow-9": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.219657,
      longitude: -2.413479,
      time: 1746522000000,
    },
    "yellow-10": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.225445,
      longitude: -2.173332,
      time: 1746525600000,
    },
    "yellow-11": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.27729,
      longitude: -1.883357,
      time: 1746529200000,
    },
    "yellow-12": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.268126,
      longitude: -1.636639,
      time: 1746532800000,
    },
    "yellow-13": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.293733,
      longitude: -1.368887,
      time: 1746536400000,
    },
    "yellow-14": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.309745,
      longitude: -1.121686,
      time: 1746540000000,
    },
    "yellow-15": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.33365,
      longitude: -0.863134,
      time: 1746543600000,
    },
    "yellow-16": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.349588,
      longitude: -0.589228,
      time: 1746547200000,
    },
    "yellow-17": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.376335,
      longitude: -0.357044,
      time: 1746550800000,
    },
    "yellow-18": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.395133,
      longitude: -0.088295,
      time: 1746554400000,
    },
    "yellow-19": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.460482,
      longitude: 0.086023,
      time: 1746558000000,
    },
    "yellow-20": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.550902,
      longitude: 0.238852,
      time: 1746561600000,
    },
    "yellow-21": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.611105,
      longitude: 0.411044,
      time: 1746565200000,
    },
    "yellow-22": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.694637,
      longitude: 0.578058,
      time: 1746568800000,
    },
    "yellow-23": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.77433,
      longitude: 0.737261,
      time: 1746572400000,
    },
    "yellow-24": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.853582,
      longitude: 0.898914,
      time: 1746576000000,
    },
    "yellow-25": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.922793,
      longitude: 1.071283,
      time: 1746579600000,
    },
    "yellow-26": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 50.997163,
      longitude: 1.246802,
      time: 1746583200000,
    },
    "yellow-27": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.061204,
      longitude: 1.421502,
      time: 1746586800000,
    },
    "yellow-28": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.179615,
      longitude: 1.491568,
      time: 1746590400000,
    },
    "yellow-29": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.331974,
      longitude: 1.535249,
      time: 1746594000000,
    },
    "yellow-30": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.446136,
      longitude: 1.606495,
      time: 1746597600000,
    },
    "yellow-31": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.577289,
      longitude: 1.67907,
      time: 1746601200000,
    },
    "yellow-32": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.712057,
      longitude: 1.730155,
      time: 1746604800000,
    },
    "yellow-33": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.842222,
      longitude: 1.787359,
      time: 1746608400000,
    },
    "yellow-34": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 51.967187,
      longitude: 1.847835,
      time: 1746612000000,
    },
    "yellow-35": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 52.110784,
      longitude: 1.920065,
      time: 1746615600000,
    },
    "yellow-36": {
      type: "observation",
      entityId: "yellowShip",
      latitude: 52.220649,
      longitude: 1.988451,
      time: 1746619200000,
    },
    "green-0": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.642166,
      longitude: -0.206432,
      time: 1746489600000,
    },
    "green-1": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.72148,
      longitude: -0.045392,
      time: 1746493200000,
    },
    "green-2": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.773523,
      longitude: 0.094019,
      time: 1746496800000,
    },
    "green-3": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.812568,
      longitude: 0.246296,
      time: 1746500400000,
    },
    "green-4": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.89297,
      longitude: 0.393587,
      time: 1746504000000,
    },
    "green-5": {
      type: "observation",
      entityId: "greenShip",
      latitude: 49.922868,
      longitude: 0.517232,
      time: 1746507600000,
    },
    "green-6": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.015095,
      longitude: 0.678897,
      time: 1746511200000,
    },
    "green-7": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.0724,
      longitude: 0.813281,
      time: 1746514800000,
    },
    "green-8": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.130035,
      longitude: 0.976137,
      time: 1746518400000,
    },
    "green-9": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.177675,
      longitude: 1.110818,
      time: 1746522000000,
    },
    "green-10": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.242837,
      longitude: 1.146687,
      time: 1746525600000,
    },
    "green-11": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.336519,
      longitude: 1.199466,
      time: 1746529200000,
    },
    "green-12": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.396971,
      longitude: 1.248527,
      time: 1746532800000,
    },
    "green-13": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.493277,
      longitude: 1.311625,
      time: 1746536400000,
    },
    "green-14": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.589783,
      longitude: 1.326871,
      time: 1746540000000,
    },
    "green-15": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.668123,
      longitude: 1.401063,
      time: 1746543600000,
    },
    "green-16": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.746436,
      longitude: 1.422752,
      time: 1746547200000,
    },
    "green-17": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.826394,
      longitude: 1.484131,
      time: 1746550800000,
    },
    "green-18": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.893871,
      longitude: 1.529005,
      time: 1746554400000,
    },
    "green-19": {
      type: "observation",
      entityId: "greenShip",
      latitude: 50.967241,
      longitude: 1.67732,
      time: 1746558000000,
    },
    "green-20": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.013555,
      longitude: 1.84518,
      time: 1746561600000,
    },
    "green-21": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.086254,
      longitude: 2.040142,
      time: 1746565200000,
    },
    "green-22": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.174896,
      longitude: 2.184954,
      time: 1746568800000,
    },
    "green-23": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.239722,
      longitude: 2.367642,
      time: 1746572400000,
    },
    "green-24": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.291813,
      longitude: 2.526876,
      time: 1746576000000,
    },
    "green-25": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.368212,
      longitude: 2.66567,
      time: 1746579600000,
    },
    "green-26": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.438635,
      longitude: 2.857788,
      time: 1746583200000,
    },
    "green-27": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.511591,
      longitude: 3.003932,
      time: 1746586800000,
    },
    "green-28": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.573699,
      longitude: 3.11906,
      time: 1746590400000,
    },
    "green-29": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.660869,
      longitude: 3.197849,
      time: 1746594000000,
    },
    "green-30": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.728586,
      longitude: 3.320669,
      time: 1746597600000,
    },
    "green-31": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.828897,
      longitude: 3.432333,
      time: 1746601200000,
    },
    "green-32": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.902916,
      longitude: 3.548628,
      time: 1746604800000,
    },
    "green-33": {
      type: "observation",
      entityId: "greenShip",
      latitude: 51.965803,
      longitude: 3.648003,
      time: 1746608400000,
    },
    "green-34": {
      type: "observation",
      entityId: "greenShip",
      latitude: 52.045515,
      longitude: 3.761248,
      time: 1746612000000,
    },
    "green-35": {
      type: "observation",
      entityId: "greenShip",
      latitude: 52.135806,
      longitude: 3.87378,
      time: 1746615600000,
    },
    "green-36": {
      type: "observation",
      entityId: "greenShip",
      latitude: 52.215531,
      longitude: 3.963111,
      time: 1746619200000,
    },
  },
};

function makePolygon(y, x, dx) {
  const dy = Math.cos((y * Math.PI) / 180) * dx;
  return [
    [x + dx, y],
    [x, y + dy],
    [x - dx, y],
    [x, y - dy],
    [x + dx, y],
  ];
}

const londonCoordinates = makePolygon(51.5, -0.13, 0.7);
const cambridgeCoordinates = makePolygon(52.2, 0.12, 0.4);
const oxfordCoordinates = makePolygon(51.75, -1.26, 0.4);

export const geojsonData = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      id: "London",
      geometry: {
        type: "Polygon",
        coordinates: [londonCoordinates],
      },
      properties: {
        color: "rgba(255,0,0,0.6)",
      },
    },
    {
      type: "Feature",
      id: "Cambridge",
      geometry: {
        type: "Polygon",
        coordinates: [cambridgeCoordinates],
      },
      properties: {
        color: "rgba(0,255,0,0.6)",
      },
    },
    {
      type: "Feature",
      id: "Oxford",
      geometry: {
        type: "Polygon",
        coordinates: [oxfordCoordinates],
      },
      properties: {
        color: "rgba(0,255,255,0.6)",
      },
    },
  ],
};

const shipsNetworkData = Object.fromEntries(
  Object.entries(observationsData.entities).map(([shipId, { color }]) => [
    shipId,
    {
      type: "node",
      color: "#101010",
      size: 10,
      border: { width: 2, color },
      image: { url: "/public/images/icons/ci/yacht.svg", color },
    },
  ])
);

export const networkData = {
  ...shipsNetworkData,
  cardiff: {
    type: "node",
    latitude: 51.48,
    longitude: -3.17,
    color: "#2471a3",
    label: [
      {
        text: "Cardiff",
        backgroundColor: "#566573",
      },
    ],
  },
  edinburgh: {
    type: "node",
    latitude: 55.95,
    longitude: -3.19,
    color: "#2471a3",
    label: [
      {
        text: "Edinburgh",
        backgroundColor: "#566573",
      },
    ],
  },
  alice: {
    type: "node",
    color: "#2471a3",
    label: [
      {
        text: "Alice",
        backgroundColor: "#566573",
      },
    ],
  },
  bob: {
    type: "node",
    color: "#2471a3",
    label: [
      {
        text: "Bob",
        backgroundColor: "#566573",
      },
    ],
  },
  link1: {
    type: "link",
    id1: "edinburgh",
    id2: "cardiff",
    color: "#2471a3",
  },
  link2: {
    type: "link",
    id1: "alice",
    id2: "edinburgh",
    color: "#2471a3",
  },
  link3: {
    type: "link",
    id1: "bob",
    id2: "cardiff",
    color: "#2471a3",
  },
};
<!doctype html>
<html>
  <body>
    <div id="mw"></div>
    <script type="module" src="./code.jsx"></script>
  </body>
</html>

Terms of use

These terms do not alter or supersede any existing agreements between you (or your employer) and us.

By accessing or using any Content you agree to be bound by these Terms of Use. Please review these terms carefully before using the website.

The contents of this website, including but not limited to any text, code samples, API references, schemas, interactive tools, and other materials (collectively, the 'Content'), are made available for informational and internal evaluation purposes only. All intellectual property rights in the Content are reserved. No licence is granted to use the Content for any commercial purpose, or to copy, distribute, modify, reverse-engineer, or incorporate any part of the Content into any product or service, without our prior written consent.

This Content is provided “as is” and “as available,” without any representations, warranties, or guarantees of any kind, whether express or implied, including but not limited to implied warranties of merchantability, fitness for a particular purpose, non-infringement, or accuracy. To the fullest extent permitted by applicable law, we expressly exclude and disclaim all implied warranties, conditions, and other terms that might otherwise be implied.

We disclaim all liability for any loss or damage, whether direct, indirect, incidental, consequential, or otherwise, arising from any reliance placed on the Content or from your use of it, to the fullest extent permitted by applicable law. By continuing to access or use the Content, you acknowledge and agree to these terms.