The editor works with an existing, host-owned map through a map adapter. The controller, the engine, the export plugins and the React editor do not depend on a map library; an adapter connects one controller to one map. Three adapters are available:
| Adapter | Package | Map library peer |
|---|---|---|
| OpenLayers | @georeferencing/openlayers |
ol >=10.10.0 <11 |
| MapLibre GL JS | @georeferencing/maplibre |
maplibre-gl >=6.0.0 <7 |
| Leaflet | @georeferencing/leaflet |
leaflet ^1.9.4 |
Install the adapter for your map library next to @georeferencing/core (and
@georeferencing/react for the ready-made editor), all in the same version.
| Capability | OpenLayers | MapLibre GL | Leaflet |
|---|---|---|---|
| Map projection | Any CRS with a proj4 definition | Web Mercator | Web Mercator, or a Proj4Leaflet CRS with its definition |
| Preview overlay | Working CRS, reprojected by OpenLayers | Rendered in EPSG:3857 | Rendered in the map CRS |
| Control-point picking, dragging and snapping | Yes | Yes | Yes |
| Drawing points, lines and polygons | Native interactions | Terra Draw (optional peer) | Terra Draw (optional peer) |
| WFS references (GeoJSON) | Yes | Yes | Yes |
| WFS references (GML) | Yes | No | No |
| Layers already owned by the host | existing-vector |
No | No |
| Linked image/map navigation, map history | Yes | Yes | Yes |
| Map page in PDF reports | Yes | Yes | No |
Fitting, residuals and raster exports always use the document's working CRS; the
adapter only decides how results are displayed. MapLibre and Leaflet can only place
rectangular raster overlays in their display projection, so their adapters call
controller.setPreviewCrs() to get previews rendered in that projection; exports are
unaffected.
React and map libraries has a complete integration for each library.
Every adapter package exports a factory that returns a MapAdapter. Pass it to the
editor's map prop and create it once per map, for example with useMemo: a new
adapter object detaches and re-attaches the editor.
const adapter = useMemo(() => openLayers(olMap, { references }), [olMap]);
const adapter = useMemo(() => maplibre(mapLibreMap, { references }), [mapLibreMap]);
const adapter = useMemo(() => leaflet(leafletMap, { lib: L, references }), [leafletMap]);
<Georeferencer controller={controller} map={adapter} definitions={definitions} />
All adapters share these options (MapAdapterOptions in @georeferencing/core/map):
references, definitions, datumGrids, debounceMs, initialView,
digitizingSnapping and onReferenceStatus. Without React, call the attach functions
directly (attachReferenceMap, attachMapLibre, attachLeaflet) and detach() the
returned binding when done; see using core without React.
openLayers(map, options) is the most complete adapter: it displays any projection,
reads WFS GML as well as GeoJSON, can snap to vector layers the host already owns
(kind: "existing-vector") and draws with native OpenLayers interactions. Register
projection definitions and datum grids with registerProjections (the adapter does this
for its own definitions). For PDF reports, pass
pdf({ capture: () => captureOpenLayersMap(map) }).
maplibre(map, options) adds GeoJSON and image sources with layers prefixed georef-,
HTML markers for control points, and re-adds its layers when the host replaces the
style. beforeId inserts its layers below an existing style layer, for example below
labels. Previews are rendered in EPSG:3857; the working CRS can still be any CRS with a
definition.
MapLibre loads its web worker relative to its own module, and bundlers such as Vite do not emit that file on their own. Set the worker URL explicitly; with Vite this works in development and production builds:
import { setWorkerUrl } from "maplibre-gl";
import workerUrl from "maplibre-gl/dist/maplibre-gl-worker.mjs?url";
setWorkerUrl(workerUrl);
captureMapLibreMap(map) captures the map
during the next render, without preserveDrawingBuffer; tile sources must allow CORS.
leaflet(map, { lib: L, ... }) takes the Leaflet module as lib instead of importing it,
so the adapter package stays importable during server-side rendering and works with a
Leaflet loaded as a global. Previews are rendered in the map CRS
(map.options.crs.code): Web Mercator by default, or a Proj4Leaflet CRS whose definition
you pass in definitions. L.CRS.Simple maps are not supported. Leaflet renders tiles as
DOM images, so PDF reports from a Leaflet map omit the map page.
The MapLibre and Leaflet adapters draw with Terra Draw. It is an
optional peer: install terra-draw and terra-draw-maplibre-gl-adapter or
terra-draw-leaflet-adapter when the controller enables digitizing. Terra Draw loads on
first use of a drawing tool. Finished sketches become controller features with stable
IDs and longitude/latitude coordinates; the modify tool loads the current drafts into
Terra Draw and commits every edit. Without the peers, choosing a drawing tool reports an
error and the rest of the editor keeps working.
An adapter is a function from a controller to a MapBinding. A binding needs
detach, fitOverlay and cancelDrawing; optional members are capabilities that the
React editor uses when present (resize, finishDrawing, navigateHistory,
capture). @georeferencing/core/map provides the building blocks the bundled adapters
use:
| Helper | Purpose |
|---|---|
subscribeBinding |
Render every controller snapshot and report errors without feedback loops |
watchReferences, loadReferenceData |
Debounced, cancellable reference loading with status reporting, as longitude/latitude GeoJSON |
snapToReferences |
Vertex and edge snapping in screen pixels |
imageViewToExtent, extentToImageView |
Linked image/map navigation |
ViewHistory |
Bounded back/forward view history |
sharedProj4 |
The proj4 instance core converts with, for map libraries that integrate proj4 |
This minimal adapter for a hypothetical map library picks control points and shows their markers:
import type { GeoreferencerController, XY } from "@georeferencing/core";
import { project } from "@georeferencing/core";
import type { MapAdapter, MapBinding } from "@georeferencing/core/map";
import { subscribeBinding } from "@georeferencing/core/map";
/** The few operations this example needs from a hypothetical host map library. */
export interface HostMap {
/** Register a click handler receiving longitude/latitude; returns an unsubscribe function. */
onClick(handler: (lonLat: XY) => void): () => void;
/** Show markers at longitude/latitude positions. */
setMarkers(markers: { id: string; label: string; at: XY }[]): void;
/** Frame longitude/latitude bounds. */
fit(bounds: [number, number, number, number]): void;
}
/** A minimal adapter: picks control points and shows their markers. */
export function hostMapAdapter(map: HostMap): MapAdapter {
return (controller: GeoreferencerController): MapBinding => {
// Render each snapshot; errors pushed here are reported once by subscribeBinding.
const binding = subscribeBinding(controller, (errors) => {
const markers = [];
for (const gcp of controller.getSnapshot().document.gcps)
try {
markers.push({
id: gcp.id,
label: String(gcp.label),
at: project(gcp.target, gcp.crs, "EPSG:4326"),
});
} catch (error) {
errors.push(error);
}
map.setMarkers(markers);
});
const stopClicks = map.onClick((lonLat) => {
const s = controller.getSnapshot();
if (s.tool !== "gcp" || !s.pendingImagePoint) return;
try {
controller.addGcp(
s.pendingImagePoint,
project(lonLat, "EPSG:4326", s.document.workingCrs),
s.document.workingCrs,
);
} catch (error) {
controller.reportError(error);
}
});
return {
fitOverlay() {
const preview = controller.getSnapshot().preview;
if (!preview) return;
const [x0, y0, x1, y1] = preview.bounds;
const [w, s] = project([x0, y0], preview.crs, "EPSG:4326");
const [e, n] = project([x1, y1], preview.crs, "EPSG:4326");
map.fit([w, s, e, n]);
},
cancelDrawing: () => controller.cancelPending(),
detach() {
stopClicks();
binding.unsubscribe();
map.setMarkers([]);
},
};
};
}
Use controller.setPreviewCrs(crs) when your map can only show raster overlays in its
display projection, and reset it to null on detach.
Before 0.4.0 the OpenLayers integration shipped inside core:
| Before | Now |
|---|---|
@georeferencing/core/openlayers |
@georeferencing/openlayers; shared reference types and WFS discovery in @georeferencing/core/map |
<Georeferencer referenceMap={map} bindingOptions={options} /> |
<Georeferencer map={openLayers(map, options)} definitions={options.definitions} /> |
pdf({ map }) |
pdf({ capture: () => captureOpenLayersMap(map) }) |
ol peer of core and React |
ol peer of @georeferencing/openlayers only |