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

> Learn to implement a custom data layer module for God's Eye View. Discover how to create a plain JavaScript object with essential methods and register it using the DataLayerManager for enhanced data tracking.

- Repository: [Bilawal Sidhu/gods-eye-view](https://github.com/bilawalsidhu/gods-eye-view)
- Tags: how-to-guide
- Published: 2026-09-09

---

**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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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)](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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js) and [`src/data/localLayers.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/localLayers.js). For example, create [`src/data/myCustomLayer.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js)** and register it with the manager instance:

```javascript
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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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:

```javascript
// 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:

```javascript
// 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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/manager.js)**: Core `DataLayerManager` class implementation handling registration, UI toggle panel generation, and lifecycle forwarding.
- **[`src/data/flights.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/flights.js)**: Complex reference implementation featuring live flight tracking via `/api/opensky`, billboard management, and dead-reckoning calculations.
- **[`src/data/localLayers.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/data/localLayers.js)**: Export array for bundled GeoJSON datasets loaded lazily via dynamic imports.
- **[`src/data/createLocalGeoJsonLayer.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/src/main.js) or add them to [`src/data/localLayers.js`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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`](https://github.com/bilawalsidhu/gods-eye-view/blob/main/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.