Skip to content
NewYour data: charts, lists and meters that take your records
<GeoGeofence />ReactAccess & securityFree

Geofence

Geofence zones as true geodesic circles on a dot map. A device that leaves every zone plucks the fence it crossed, and a wave runs around it.

View as Markdown
<GeoGeofence />

Trydrag a zone away from a device, or shrink it until one falls outside.

Your data

Pass your tracked devices as devices and your zones as zones or defaultZones. Without devices the component shows six demo trackers on a simulated walk. See Your data.

Playground

Change a prop and the component re-renders. Props marked remounts set an initial value, so the component starts over.

Loading <GeoGeofence />
Props
editablelive
minRadiusKmlive
maxRadiusKmlive
React
<GeoGeofence
  label="Tracker zones"
  editable
  minRadiusKm={50}
  maxRadiusKm={3000}
/>

Installation

pnpm dlx shadcn@latest add @lumesec/geo-geofence
First timeRegister the @lumesec registry once, or install by URL+

Adds @lumesec to your components.json:

pnpm dlx shadcn@latest registry add @lumesec=https://elements.lumesec.ai/r/{name}.json

Or skip the setup and install by URL:

npx shadcn@latest add https://elements.lumesec.ai/r/geo-geofence.json

Usage

geo-geofence-example.tsx
import * as React from "react";

import { GeoGeofence, type GeofenceZone, type TrackedDevice } from "@/components/lumesec/geo-geofence";

const initialZones: GeofenceZone[] = [
  { id: "depot", label: "Depot region", center: { lat: 52.52, lon: 13.405 }, radiusKm: 400 },
];

export function Example({ vehicles }: { vehicles: TrackedDevice[] }) {
  const [zones, setZones] = React.useState<readonly GeofenceZone[]>(initialZones);
  return (
    <GeoGeofence
      zones={zones}
      onZonesChange={setZones}
      devices={vehicles}
      onBreach={({ device, zone }) => console.log(`${device.label} left ${zone.label}`)}
    />
  );
}

Behaviour

Allowed zones as true geodesic circles on a flat map, with tracked devices inside them. Drag a zone's centre to move it and the handle on its edge to resize it; the circle keeps its real shape, so it stretches toward the poles.

When a device leaves every zone, the fence it crossed rings like a plucked string: a wave runs around the circle from the crossing point in both directions, and the device turns to a warning colour.

API reference

Props

Also accepts every prop of <div> (React.ComponentProps<"div">), spread onto the root element.

zonesTypereadonly GeofenceZone[]

The zones (controlled): { id, label, center: { lat, lon }, radiusKm }. Use with onZonesChange. A radius that would put a pole inside the circle is drawn and checked at the largest radius that does not.

defaultZonesTypereadonly GeofenceZone[]Default2 demo zones

The zones when uncontrolled. The default is Western Europe (1,200 km around Paris) and East coast (900 km around New York).

onZonesChangeType(zones: readonly GeofenceZone[]) => void

Called with every zone when a drag ends or a key press moves or resizes a zone.

onZonesInputType(zones: readonly GeofenceZone[]) => void

Called with every zone on each pointer move while a zone is dragged or resized.

devicesTypereadonly TrackedDevice[]Default6 demo trackers

The tracked devices: { id, label?, lat, lon }. When a position changes, the device glides there in about half a second and is checked against the zones on the way. Without it, six demo trackers walk slowly in and out of the zones. An empty array shows no devices.

editableTypebooleanDefaulttrue

Shows the centre and radius handles, so zones can be moved and resized.

minRadiusKmTypenumberDefault50

Smallest radius a drag or key press can set, in km.

maxRadiusKmTypenumberDefault3000

Largest radius a drag or key press can set, in km.

onBreachType(event: GeofenceEvent) => void

Called once each time a device leaves every zone: { device, zone, point }, with the zone it left last and the point on the fence where it crossed.

onEnterType(event: GeofenceEvent) => void

Called when a device that was outside every zone comes back into one: { device, zone, point }.

boundsTypeGeoBoundsDefaultfitted to the zones

The part of the world the map shows, { west, south, east, north } in degrees (east may exceed 180 across the antimeridian), widened to the map's aspect. Without it the map fits the zones, and fits again when zones are added or removed, never while one is dragged.

labelTypestringDefault"Geofence map"

Accessible name of the map.

Ref

ref points at the root HTMLDivElement.

Keyboard

KeysAction
TabMoves through each zone's centre handle and radius handle, then into the devices, which share one tab stop.
ArrowUp / ArrowDown / ArrowLeft / ArrowRightOn a centre handle: move the zone 0.5° north, south, west or east (Shift: 5°). The centre stays on the map.
ArrowRight / ArrowUp, ArrowLeft / ArrowDownOn a radius handle: add or remove 25 km (Shift: 250 km).
PageUp / PageDownOn a radius handle: add or remove 250 km.
Home / EndOn a radius handle: set minRadiusKm / maxRadiusKm. On a device: focus the first / last device.
ArrowUp / ArrowDown / ArrowLeft / ArrowRightOn a device: focus the nearest device in that direction on the map.

Accessibility

  • The map is a role="group" with aria-roledescription="map" and the label as its name; the canvases are aria-hidden.
  • Each centre handle is a native <button> with aria-roledescription="zone centre" and a name of the zone label and its coordinates, for example "Western Europe, 48.9° N, 2.4° E".
  • Each radius handle has role="slider" in km, aria-label "Radius of Western Europe", aria-valuemin, aria-valuemax, aria-valuenow and an aria-valuetext such as "1,200 kilometres". While it has keyboard focus or is dragged, a chip beside it shows the radius.
  • Devices are native <button> elements whose names say where they are: "Tracker 3, inside Western Europe" or "Tracker 3, outside every zone". Hover or keyboard focus shows a chip with the same text.
  • Every breach is announced once in an assertive live region: "Tracker 3 left Western Europe".
  • The zone list is a <table> with column headers for the zone, its radius and the number of devices inside.
  • Handles have a 2 px accent focus ring and a hit area of about 30 px for touch; only the handles block page scrolling while dragged.
  • Reduced motion: there is no pluck wave, no sparks and no fence spring: a breach turns the crossed section of the fence to the warning colour for one second, handles move the fence directly, new zones appear at once, devices jump to new positions, and numbers change without rolling.

Theming

Styled with Tailwind classes on your shadcn theme tokens, so light and dark follow your theme. The accent comes from --lumesec. See Theming.

This component reads --lumesec, --lumesec-foreground, --lumesec-shine, --lumesec-success, --lumesec-warning, --foreground, --muted-foreground, --muted, --card and --border.

Notes

  • Each zone is a real geodesic circle: 128 points at even bearings around the centre, projected as one ring and resampled into fence dots every 4 px. On this equirectangular map it stretches east to west toward the poles.
  • A device is outside only when it has left every zone. The zone it left last is the one that rings: two pulses leave the crossing point in opposite directions at 220 px/s, pass through each other on the far side and fade within 1.2 s.
  • Containment is checked every frame by great-circle distance, so a breach fires when the device crosses the fence on screen, whether the device moved or the zone was dragged off it.
  • With no zones every device counts as outside, without breach events. The first check of a device never fires an event.
  • While a zone is dragged, the fence dots spring after the handle: when resizing, the dots near the handle follow fastest, so the ring stretches like rubber.
  • Devices outside the shown area are pinned to the map's edge.
  • The fence spring, plucks, the demo walk and device glides run only while the map is on screen. With devices passed and nothing moving, the animation stops.

Was this page helpful?