How to Implement a Custom Data Layer Module for God's Eye View

A custom data layer module for God's Eye View is a plain JavaScript object implementing the IDataLayer contract with id, name, enable(), and disable() methods, registered via the DataLayerManager in src/main.js.

God's Eye View structures every live data source as an independent data layer module that the central DataLayerManager registers at startup (see the registration logic in [src/main.js](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js#L4-L7)). This modular architecture allows developers to integrate custom visualizations—whether GeoJSON feeds, CZML streams, or proprietary APIs—while the manager handles UI toggles, persistence, and lifecycle events.

Understanding the IDataLayer Contract

Every custom data layer module must export a plain object adhering to the internal IDataLayer contract. The DataLayerManager in src/data/manager.js validates and stores these objects, forwarding callbacks when users interact with the layer toggle panel.

The required and optional properties are:

  • id (string): Unique identifier used for UI toggles, localStorage persistence, and event dispatching.
  • name (string): Human-readable label displayed in the data-toggle panel.
  • description (string, optional): Tooltip text or supplemental information shown on hover.
  • enable(viewer, dataManager) (function): Lifecycle hook called when the user activates the layer. Receives the Cesium viewer instance and the dataManager reference. Implementations should create Cesium data sources (e.g., Cesium.GeoJsonDataSource, Cesium.CzmlDataSource, or custom BillboardCollection primitives) and add them to the scene.
  • disable() (function): Cleanup hook called when the user deactivates the layer. Must remove data sources from the scene and clear any timers, intervals, or event listeners to prevent memory leaks.
  • refresh?(force) (function, optional): Force a re-fetch of remote data. Useful for layers polling external APIs that need a manual reload button.
  • requiresKey? (boolean): When set to true, the layer remains hidden in the UI until a provider key is supplied through the POWER UP panel.

Building Your Custom Data Layer Module

Create the Module File

Place your implementation under src/data/ to maintain consistency with built-in layers such as src/data/flights.js and src/data/localLayers.js. For example, create src/data/myCustomLayer.js.

Implement the Lifecycle Methods

At minimum, your module must implement enable() and disable(). The enable method initializes the visualization, while disable tears it down. For external APIs requiring authentication, use Vite's proxy configuration (endpoint prefixed with /api/) to keep secrets server-side, following the pattern used in src/data/flights.js for the OpenSky Network integration.

Handle Cleanup Properly

In disable(), explicitly remove Cesium primitives using viewer.scene.primitives.remove() and nullify any stored references. If you started polling intervals in enable, clear them here to avoid ghost updates after the layer is off.

Register with DataLayerManager

Import your module into src/main.js and register it with the manager instance:

import { myCustomLayer } from './data/myCustomLayer.js';

const dataManager = new DataLayerManager(viewer, { 
  allowQaRegistration: import.meta.env.DEV 
});
dataManager.register(myCustomLayer);

Alternatively, for static GeoJSON bundled with the application, add your layer to the array exported by src/data/localLayers.js, following the pattern used for the Datacenters and Dams layers.

Complete Working Example

The following module fetches a public GeoJSON feed through a Vite proxy endpoint, renders it using Cesium's GeoJsonDataSource, and provides an optional refresh method:

// src/data/myCustomLayer.js
import { GeoJsonDataSource } from 'cesium';

/**
 * Custom data layer that visualises a public GeoJSON feed.
 * The feed is proxied through `/api/myfeed` so any required API key
 * lives only on the server side (see docs/PROXY.md for the setup).
 */
export const myCustomLayer = {
  id: 'my-custom-geojson',
  name: 'My GeoJSON Feed',
  description: 'Shows points of interest from the public XYZ API.',

  // Called when the user enables the layer.
  async enable(viewer) {
    // Fetch the GeoJSON via the Vite dev-server proxy.
    const response = await fetch('/api/myfeed');
    const geojson = await response.json();

    // Create a Cesium data source from the GeoJSON.
    this._source = await GeoJsonDataSource.load(geojson, {
      clampToGround: true,
      markerSize: 12,
      markerColor: Cesium.Color.YELLOW,
    });

    // Add it to the scene.
    viewer.scene.primitives.add(this._source);
  },

  // Called when the user disables the layer.
  disable() {
    if (this._source) {
      this._source.viewer.scene.primitives.remove(this._source);
      this._source = null;
    }
  },

  // Optional: expose a refresh method for a manual reload button.
  async refresh() {
    if (!this._source) return;
    await this.disable();
    await this.enable(this._source.viewer);
  },
};

Registration in the application entry point:

// src/main.js
import { myCustomLayer } from './data/myCustomLayer.js';

const dataManager = new DataLayerManager(viewer, { 
  allowQaRegistration: import.meta.env.DEV 
});
dataManager.register(myCustomLayer);

Reference Implementations and Key Files

  • src/data/manager.js: Core DataLayerManager class implementation handling registration, UI toggle panel generation, and lifecycle forwarding.
  • src/data/flights.js: Complex reference implementation featuring live flight tracking via /api/opensky, billboard management, and dead-reckoning calculations.
  • src/data/localLayers.js: Export array for bundled GeoJSON datasets loaded lazily via dynamic imports.
  • src/data/createLocalGeoJsonLayer.js: Utility factory function that generates compliant IDataLayer objects for static GeoJSON files.

Summary

  • A valid custom data layer module implements the IDataLayer contract with id, name, enable(), and disable() properties.
  • The DataLayerManager in src/data/manager.js orchestrates all registered layers, managing UI state and persistence.
  • Use Vite's proxy (/api/...) to authenticate external requests without exposing keys in the browser.
  • Always clean up Cesium primitives and timers in disable() to prevent memory leaks and visual artifacts.
  • Register modules in src/main.js or add them to src/data/localLayers.js for bundled datasets.

Frequently Asked Questions

What happens if I don't implement the disable() method?

If disable() is omitted or improperly implemented, Cesium primitives remain in the scene after the user toggles the layer off, causing memory leaks and visual artifacts. The DataLayerManager calls this method immediately when a layer is deactivated, so it must explicitly remove data sources using viewer.scene.primitives.remove() and clear any active intervals or animation frames.

Can I use a custom React component instead of Cesium primitives for the visualization?

No. The IDataLayer contract expects integration with the Cesium viewer instance passed to enable(viewer). While you can manage React state outside the layer system, the actual rendering must use Cesium APIs such as GeoJsonDataSource, CzmlDataSource, or BillboardCollection to appear in the 3D globe view managed by God's Eye View.

How do I handle API rate limits in my custom data layer?

Implement the optional refresh(force) method to allow manual reloads, and use an internal timer within enable() for polling. Store the interval ID as a private property (e.g., this._pollInterval) and clear it in disable(). For production deployments, configure the Vite proxy in vite.config.js to cache responses or implement exponential backoff in your fetch logic within the layer module.

Why is my layer hidden until I enter a key in the POWER UP panel?

If your layer object includes requiresKey: true, the DataLayerManager automatically hides it from the toggle panel until a provider key is supplied. Set this property to false (or omit it) for public data sources, or configure the key storage mechanism in the POWER UP panel settings to persist authentication credentials.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →