Skip to content
Waiel5Public

About

React geospatial visualization library. Heatmaps, clusters, trajectories, geofences — built on Deck.gl and MapLibre.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7,416 Commits

Folders and files

Repository files navigation

Terra

React geospatial visualization library built on Deck.gl and MapLibre GL. Pre-built layers for heatmaps, point clusters, animated trajectories, geofence zones, and icon markers with a clean, composable API.

Think of it as a higher-level react-map-gl with batteries included.

[ Screenshot: Dark-themed map with a plasma heatmap overlay showing earthquake   ]
[ density across the Mediterranean. Cluster markers visible at lower zoom levels ]
[ with animated vehicle trajectories rendered as glowing green trails.            ]

Features

  • HeatmapLayer -- Weighted density heatmaps with configurable color scales (heat, viridis, plasma, coolwarm)
  • ClusterLayer -- Automatic spatial clustering with zoom-based expansion and click-to-zoom
  • TrajectoryLayer -- Animated path rendering with trails, directional arrows, and endpoint markers
  • GeofenceLayer -- Polygon zones with real-time enter/exit detection for tracked points
  • IconLayer -- Custom markers (emoji, SVG, image) with hover tooltips and click handlers
  • Built-in controls -- Zoom, layer toggle panel, geocoding search box
  • Hooks -- useViewport, useGeoJSON, useRealtime (WebSocket subscriptions)
  • Geo utilities -- Haversine distance, point-in-polygon, bounding box, path simplification
  • Color scales -- Interpolation, sampling, domain mapping with built-in presets
  • Themes -- Dark, satellite, light, and streets base maps out of the box

Install

npm install @terra-gl/react

Peer dependencies

npm install react react-dom @deck.gl/core @deck.gl/layers @deck.gl/aggregation-layers @deck.gl/react maplibre-gl

Quickstart

import {
  TerraMap,
  HeatmapLayer,
  TrajectoryLayer,
  GeofenceLayer,
  ZoomControl,
} from '@terra-gl/react';

function App() {
  return (
    <TerraMap center={[35.5, 33.9]} zoom={8} theme="dark">
      <HeatmapLayer data={earthquakes} weight="magnitude" radius={30} />
      <TrajectoryLayer data={vehicleTracks} color="#00ff88" animated />
      <GeofenceLayer
        zones={restrictedAreas}
        onEnter={(e) => alert(`Entered ${e.zone.name}`)}
      />
      <ZoomControl position="top-right" />
    </TerraMap>
  );
}
[ Screenshot: The quickstart example rendered -- a dark map centered on Beirut  ]
[ with earthquake heatmap, animated vehicle paths, and red geofence polygons.   ]

API Reference

<TerraMap>

Root container component. All layers and controls must be children of TerraMap.

Prop Type Default Description
center [lon, lat] [0, 0] Initial map center
zoom number 2 Initial zoom level (0-22)
pitch number 0 Map pitch in degrees
bearing number 0 Map bearing in degrees
theme 'dark' | 'satellite' | 'light' | 'streets' | string 'dark' Theme or custom style URL
width string | number '100%' Container width
height string | number '100%' Container height
interactive boolean true Enable/disable user interactions
onViewportChange (viewport) => void -- Viewport change callback
onClick (info) => void -- Click callback with pick info

The component exposes an imperative handle via React.forwardRef:

const mapRef = useRef<TerraMapHandle>(null);

mapRef.current.flyTo(-73.98, 40.75, 12);
mapRef.current.fitBounds([-74.1, 40.6, -73.8, 40.9]);

Layers

<HeatmapLayer>

Prop Type Default Description
data T[] required Point data array
weight keyof T | number 1 Weight field name or constant
radius number 30 Heatmap radius in pixels
intensity number 1 Intensity multiplier
colorScale 'heat' | 'viridis' | 'plasma' | 'coolwarm' | RGBA[] 'heat' Color scale
opacity number 0.8 Layer opacity
getPosition (d: T) => [lon, lat] -- Custom position accessor
getWeight (d: T) => number -- Custom weight accessor

<ClusterLayer>

Prop Type Default Description
data T[] required Point data array
clusterRadius number 50 Clustering radius in pixels
maxClusterZoom number 14 Max zoom for clustering
minPoints number 2 Min points per cluster
clusterColor ColorValue '#ff6b6b' Cluster circle color
pointColor ColorValue '#4dabf7' Unclustered point color
onClusterClick (cluster) => void -- Cluster click handler
onPointClick (point) => void -- Point click handler

<TrajectoryLayer>

Prop Type Default Description
data Trajectory[] required Trajectory data
color ColorValue '#00ff88' Path color
width number 2 Path width in pixels
animated boolean false Enable path animation
speed number 1 Animation speed multiplier
trailLength number 0.15 Trail length (0-1)
showArrows boolean false Show direction arrows
showEndpoints boolean false Show start/end markers

<GeofenceLayer>

Prop Type Default Description
zones GeofenceZone[] required Polygon zone definitions
fillColor ColorValue 'rgba(66,135,245,0.2)' Zone fill color
strokeColor ColorValue '#4287f5' Zone border color
showLabels boolean false Show zone name labels
trackedPoints Coordinate[] -- Points to track for enter/exit
onEnter (event) => void -- Zone enter callback
onExit (event) => void -- Zone exit callback

<IconLayer>

Prop Type Default Description
data T[] required Marker data array
icon string '📍' Default icon (emoji/URL)
iconSize number 24 Icon size in pixels
showTooltip boolean false Enable hover tooltips
renderTooltip (item: T) => ReactNode -- Custom tooltip renderer
onClick (item: T) => void -- Click handler

Controls

<ZoomControl>

<ZoomControl position="top-right" showCompass />

<LayerToggle>

<LayerToggle
  layers={[
    { id: 'heatmap', label: 'Heatmap', visible: true },
    { id: 'clusters', label: 'Clusters', visible: false },
  ]}
  onToggle={(id, visible) => handleToggle(id, visible)}
/>

<SearchBox>

<SearchBox
  position="top-left"
  onSelect={(result) => mapRef.current.flyTo(...result.position, 14)}
/>

Hooks

useViewport

const { viewport, flyTo, fitBounds, zoomIn, zoomOut } = useViewport({
  longitude: -73.98,
  latitude: 40.75,
  zoom: 12,
  minZoom: 4,
  maxZoom: 18,
});

useGeoJSON

const { data, loading, error, refetch } = useGeoJSON({
  url: 'https://api.example.com/points.geojson',
  refetchInterval: 30000,
});

useRealtime

const { data, status, send } = useRealtime({
  url: 'wss://api.example.com/vehicles/stream',
  messageFilter: 'position_update',
  reconnect: true,
});

Utilities

Geo

import { haversineDistance, pointInPolygon, computeBBox, simplifyPath } from '@terra-gl/react';

const km = haversineDistance([35.5, 33.9], [35.8, 34.1]);
const inside = pointInPolygon([35.5, 33.9], polygonCoords);
const bbox = computeBBox(points);
const simplified = simplifyPath(densePath, 0.001);

Color

import { createColorScale, domainColor, parseColor } from '@terra-gl/react';

const scale = createColorScale('viridis');
const color = scale(0.5); // [38, 130, 142, 255]
const mapped = domainColor(6.5, [0, 10], scale);

Themes

Theme Description
dark Dark Matter (Carto) -- ideal for data-dense overlays
satellite Voyager (Carto) -- good for terrain context
light Positron (Carto) -- clean, minimal background
streets Voyager variant -- labeled roads and POIs

Pass a custom MapLibre style URL for full control:

<TerraMap theme="https://tiles.example.com/style.json" />

Data Types

interface WeightedPoint {
  position: [longitude: number, latitude: number];
  weight?: number;
}

interface Trajectory {
  id: string;
  path: { position: [lon, lat]; timestamp: number }[];
}

interface GeofenceZone {
  id: string;
  name: string;
  polygon: [lon, lat][];
}

interface IconMarker {
  id: string;
  position: [lon, lat];
  icon?: string;
  label?: string;
}

Development

git clone https://github.com/Waiel5/terra.git
cd terra
npm install
npm run dev          # watch mode
npm run storybook    # component stories
npm test             # run tests

License

MIT

About

React geospatial visualization library. Heatmaps, clusters, trajectories, geofences — built on Deck.gl and MapLibre.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages