npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@georeferencing/react

v0.6.0

Published

React editor and panels for the headless georeferencing core, with any map library through map adapters

Readme

@georeferencing/react

React 19 components and hooks for georeferencing local images on an existing OpenLayers, MapLibre GL or Leaflet map, connected through a map adapter. This package provides the ready-made editor, composable panels, subscription hook, unsaved-work dialog and scoped styles.

The authoritative session and processing engine live in @georeferencing/core. Optional exports come from @georeferencing/plugins. React has no runtime dependency on plugins, pdf-lib or any map library: it displays formats configured on the controller and attaches the map adapter you pass.

Installation and compatibility

pnpm add @georeferencing/core @georeferencing/react react@19 react-dom@19
# A map adapter, for example OpenLayers (or @georeferencing/maplibre, @georeferencing/leaflet):
pnpm add @georeferencing/openlayers ol@10
# Optional output formats:
pnpm add @georeferencing/plugins
# TypeScript React applications also need the React declarations:
pnpm add -D @types/react@19 @types/react-dom@19

Verified peer ranges are React/React DOM >=19.3.0 <20; map-library ranges are peers of the adapter packages. The host owns these libraries and its map. Packages ship ESM and TypeDoc-bearing TypeScript declarations. Node.js 22.12+ is the declared tooling requirement; the repository uses pnpm 12.x.

Importing the package is SSR-safe. Create maps and start browser processing only on the client.

Install the same version of @georeferencing/react, @georeferencing/core, @georeferencing/plugins and your map adapter, and upgrade them together. React declares core as a peer dependency and re-exports its controller; a mismatched version is reported as a peer-dependency conflict rather than installing a second core with a separate projection registry. See keep package versions aligned.

Integrate with a host-owned map

The map must already have a visible target, dimensions and initialized view. Make one stable controller/engine per session, and inject your real save service. Create the map adapter once per map (here with useMemo): a new adapter re-attaches the map. This example needs no export plugin and sends geographic features to the host.

import type { ControllerOptions } from "@georeferencing/core";
import { GeoreferencerController } from "@georeferencing/core";
import { createWorkerEngine } from "@georeferencing/core/engine";
import { openLayers } from "@georeferencing/openlayers";
import { Georeferencer } from "@georeferencing/react";
import type OLMap from "ol/Map.js";
import { useMemo } from "react";
import "ol/ol.css";
import "@georeferencing/react/styles.css";

export function createEditorSession(
  workingCrs: string,
  persistFeatures: NonNullable<ControllerOptions["onSave"]>,
  persistDraft?: ControllerOptions["onSaveDraft"],
) {
  const engine = createWorkerEngine();
  const controller = new GeoreferencerController({
    workingCrs,
    engine,
    digitizing: true,
    onSave: persistFeatures,
    onSaveDraft: persistDraft,
  });
  return {
    controller,
    dispose() {
      controller.dispose();
      engine.dispose();
    },
  };
}

export function ImageEditor({
  map,
  session,
}: {
  map: OLMap;
  session: ReturnType<typeof createEditorSession>;
}) {
  const adapter = useMemo(() => openLayers(map), [map]);
  return <Georeferencer controller={session.controller} map={adapter} />;
}

Map libraries

The editor works the same way with every adapter; only creating the map and the adapter differs:

| | OpenLayers | MapLibre GL JS | Leaflet | | --- | --- | --- | --- | | Adapter | openLayers(map, options) | maplibre(map, options) | leaflet(map, { lib: L, ...options }) | | Package | @georeferencing/openlayers + ol | @georeferencing/maplibre + maplibre-gl | @georeferencing/leaflet + leaflet | | Stylesheet | ol/ol.css | maplibre-gl/dist/maplibre-gl.css (required for marker placement) | leaflet/dist/leaflet.css | | Setup | defaults({ onFocusOnly: false }) for a focusable map target | setWorkerUrl(...) before creating maps | Pass the Leaflet module as lib | | Drawing | Built in | Optional terra-draw + terra-draw-maplibre-gl-adapter | Optional terra-draw + terra-draw-leaflet-adapter | | PDF map page | captureOpenLayersMap(map) | captureMapLibreMap(map) | Not available |

In the guided layout, the map's container is rendered by the editor (inside referenceView), so the map cannot exist before the editor first renders. The React and map libraries guide shows a small useHostMap hook for this case and a complete, tested integration for each library. The map adapters guide compares the adapters' capabilities.

The host calls createEditorSession once, for example when opening an editor, and retains it across renders. Render ImageEditor only after the host map exists. On permanent close, resolve unsaved work, unmount the editor, then dispose the session. A temporary detach/remount must retain the same controller.

With Vite, use ES module workers:

export default { worker: { format: "es" } };

The stylesheet is optional. It uses rg- classes; the host controls map layout and dimensions. Core's engine and each optional codec support explicit worker URLs/factories for deployments that do not preserve default bundled asset paths.

Ready-made editor props

| Prop | Contract | | --- | --- | | controller | Required stable authoritative session controller | | map | Required stable map adapter, such as openLayers(map, options), maplibre(map, options) or leaflet(map, { lib: L }); references, projections, snapping and initial framing are adapter options | | definitions | Host projection definitions for coordinate-unit labels in output settings | | t | Translate default English text, including configured format labels | | formatError | Format structured errors by code/message/operation ID | | className | Additional editor root class | | propertyEditor | Host feature-property form; update replacement JSON properties | | onExport | Receive any configured format's artifacts instead of automatic downloads |

Create the adapter once (for example with useMemo) or outside rendering. Changing its identity re-attaches the map. The ready-made component owns its attachment; do not also attach the same controller and map yourself. Map-history and "Finish drawing" buttons appear only when the adapter's binding supports them.

Select exports on demand

No format is enabled implicitly. Configure the controller at creation or replace its registry at runtime:

import type { GeoreferencerController } from "@georeferencing/core";
import { geoTiff } from "@georeferencing/plugins/geotiff";
import { jpeg } from "@georeferencing/plugins/jpeg";
import type { MapCapture } from "@georeferencing/core/map";
import { pdf } from "@georeferencing/plugins/pdf";

export function enableExports(
  controller: GeoreferencerController,
  captureMap: () => Promise<MapCapture> | undefined,
) {
  controller.setExportFormats([
    geoTiff(),
    jpeg({ quality: 0.92 }),
    pdf({ capture: captureMap }),
  ]);
}

The UI shows only these actions. setExportFormats replaces the registry and cancels active export work. Import a subset to exclude unused implementations from the browser bundle; heavy format code loads on request.

The default UI downloads every artifact. onExport(result) can handle them in host code; inspect result.format, result.document and each { name, blob } in result.files. JPEG and world-file exports include required CRS/placement sidecars. Export completion never acknowledges host persistence. See the plugins README for format settings, cancellation and limits.

Guided four-step layout

Supply referenceView to Georeferencer to use the guided layout. It is a React node containing the host's map target and optional map controls; the host still owns the map and must attach its target before editor effects run (a React callback ref is suitable). Without this prop, the classic workbench with every control remains. See the complete demo host for a typed integration with target attachment and cleanup.

The guided layout leads users through four steps, each with one main action in a sticky bar:

  1. Load image: drop zone with Choose image and any emptyImageActions (for example a sample-image button).
  2. Match points: click a spot in the image, then the same spot on the map. Progress shows how many points the model needs; Run alignment fits them.
  3. Check alignment: the overlay on the map, a plain-language accuracy summary (average residual in image pixels, or a hint to add a point when the fit is exact) and the point list. Adjust points goes back; Looks good, continue confirms the alignment.
  4. Export or draw: download buttons for the configured formats and, with digitizing, drawing tools and Save features. Back to check returns.

Completed steps in the step bar are clickable. Desktop keeps both views side by side; below 720 px, Image/Map buttons switch panes and follow pending points. In both views, dragging pans and scrolling zooms; a click without dragging places a point. Zooming the image beyond the reduced preview's resolution loads the full-resolution image (controller.requestDetailImage()), and source pixels are shown crisply once each covers several screen pixels, so points can be placed at the original resolution.

With OpenLayers, the Map creates its default interactions with onFocusOnly: true. If the map target has a tabindex (for keyboard navigation), mouse panning and wheel zoom then only work after the map has focus. Create the host map with interactions: defaults({ onFocusOnly: false }) from ol/interaction/defaults.js, as the demo does, so both work immediately.

Choose the controls

The guided layout starts minimal. Add expert controls individually with controls; omitted flags fall back to MINIMAL_CONTROLS:

import { ALL_CONTROLS, Georeferencer } from "@georeferencing/react";

<Georeferencer
  controller={controller}
  map={adapter}
  referenceView={mapView}
  controls={{ transformation: true, outputSettings: true }}
/>;
// Every control: controls={ALL_CONTROLS}

| Flag | Default | Adds | | --- | --- | --- | | history | on | Undo and redo | | previewMode | off | Manual/automatic preview selector (otherwise the controller's previewMode applies) | | transformation | off | Transformation model selector while matching | | pointTable | off | Full point table with editable coordinates, CRS and residuals (otherwise a compact list) | | manualEntry | off | Add a point pair by typing coordinates, the keyboard alternative to clicking | | navigation | off | Previous/next image and map views, linked navigation | | displayAdjustment | off | Brightness, contrast and histogram stretch | | referenceStatus | off | Load status of reference providers | | outputSettings | off | Raster output CRS, resampling, compression, no-data, pixel size and bounds | | sessionFiles | off | Restore session, import .points, save draft and early session downloads | | unsavedIndicator | off | Unsaved-changes indicator |

Enable manualEntry when keyboard-only point entry is required.

Preview policy and panels

previewMode: "manual" on the controller waits for Run alignment; "automatic" updates eligible previews after committed edits without advancing the user's step. Worker validation reports singular or unusable arrangements when a run is requested.

The composable panels keep every control by default and accept the same opt-outs: GcpPanel takes variant ("table" or "compact"), manualEntry and tools; ImagePanel takes navigation, displayAdjustment and emptyActions; PreviewControls takes modeSelector and calls onReview only for a successful preview matching the requested revision. AlignmentPanel accepts controls={false} when transformation and preview controls are rendered elsewhere. These panels never take map or engine ownership.

References, projections and snapping

Configure references, projections and snapping as options of the map adapter. Providers include bounded viewport WFS, static GeoJSON and custom abortable loaders with every adapter; the OpenLayers adapter also reads WFS GML and borrowed host vector layers, which retain their host loading and ownership. Reference errors and partial results appear in the UI without disabling manual target entry.

Configure real endpoint/type names, request/response CRSs and axis conventions. Preserve AbortSignal when injecting authentication. Query bounds limit reference loading; initialView only frames the map. Neither georeferences the image. Drawing bounds and raster bounds are independent.

Register the same custom projection definitions and NTv2 grids with the engine and binding. Image GCPs use original-resolution, orientation-normalized pixels; feature drafts/save payloads use longitude/latitude regardless of the map view. Supplying provider snapping opts into GCP snapping. Drawing snapping is separately controlled by digitizingSnapping.references and .drafts. The reference guide contains typed provider examples.

Image lifecycle, confirmation and saving

The image picker and drop zone use controller lifecycle methods. Selection supports files up to 25 MiB, subject to decoded-pixel and memory budgets. Replacing/removing an image invokes the ready-made Save/Discard/Cancel dialog when needed, cancels old processing and preserves the host map view. Original bytes stay outside the serializable document. Restoring a JSON session requires the matching source file.

Digitizing is opt-in through digitizing: true. A valid current alignment must be explicitly confirmed before drawing multiple points, lines or polygons. Features can be modified, deleted and undone/redone. Returning to alignment leaves them geographically anchored; subsequent alignment changes require confirmation and explicit feature review before saving accepted features again.

onChange mirrors draft state. onSave receives an immutable accepted-feature envelope with stable IDs, source fingerprint, alignment and provenance. onSaveDraft can persist unfinished sessions separately. A save callback resolves after actual storage success and rejects on failure; a failed draft remains retryable. Older save completion does not acknowledge newer edits. Feature saving never requires raster export. Hosts must separately persist source bytes and protect route/window closure; the image-transition dialog is not a general router or browser-close guard.

React Strict Mode and ownership

The editor resumes the controller during setup and suspends it during cleanup. It adds/removes only package-owned map layers/interactions. It does not dispose the host map, borrowed layers, controller or engine on unmount. This permits React 19 Strict Mode setup/cleanup cycles and later remounts.

Use permanent dispose() only after the host has finished with the session; do not put it in an effect cleanup that Strict Mode uses for temporary teardown. Host interactions remain host-owned. Use onActiveToolChange in controller options to coordinate conflicting host drawing tools explicitly. Pending host saves are revision-safe but are not cancelled by UI suspension.

Compose a custom UI

| Export | Purpose | | --- | --- | | Georeferencer | Complete editor, map attachment, guard dialog and output controls | | useGeoreferencer(controller) | Subscribe to the authoritative snapshot | | ImagePanel | Image selection, image view and image-side GCP interaction | | GcpPanel | Paired-point table, numeric editing and diagnostics | | PreviewControls | Manual/automatic preview policy, readiness and explicit run/review | | AlignmentPanel | Transformation selection, confirmation and alignment controls | | ReferencePanel | Reference loading/error/incomplete-result feedback | | FeaturePanel | Drawing tools, features/properties, review and save controls |

Composable panels do not install a map binding or unsaved-work dialog. Supply a controller with a host guard, attach the map once with an adapter and manage suspension yourself:

import type { GeoreferencerController } from "@georeferencing/core";
import { attachReferenceMap, type BindingOptions } from "@georeferencing/openlayers";
import {
  AlignmentPanel, FeaturePanel, GcpPanel, ImagePanel,
  ReferencePanel, useGeoreferencer,
} from "@georeferencing/react";
import type OLMap from "ol/Map.js";
import { useEffect } from "react";

export function CustomEditor({ map, controller, options }: {
  map: OLMap;
  controller: GeoreferencerController;
  options: BindingOptions;
}) {
  const state = useGeoreferencer(controller);
  useEffect(() => {
    controller.start();
    const binding = attachReferenceMap(map, controller, options);
    return () => {
      binding.detach();
      controller.suspend();
    };
  }, [map, controller, options]);

  return (
    <div className="rg-editor">
      <ImagePanel controller={controller} />
      <GcpPanel controller={controller} />
      <AlignmentPanel controller={controller} />
      <ReferencePanel controller={controller} />
      <FeaturePanel controller={controller} />
      <p role="status">{state.error ?? `Document revision ${state.document.documentRevision}`}</p>
    </div>
  );
}

Custom layouts provide their own export/session actions through controller APIs. Keep the options object stable. Omitting a headless guard safely cancels dirty image transitions instead of silently discarding them.

Localization and property forms

Use t for UI messages and formatError for structured dynamic errors. Business properties remain host-defined JSON. Never mutate the frozen feature passed to a property renderer; call its update callback with replacement properties.

import type { GeoreferencerProps } from "@georeferencing/react";

export const propertyEditor: GeoreferencerProps["propertyEditor"] = (feature, update) => (
  <label>
    Name
    <input
      value={String(feature.properties.name ?? "")}
      onChange={(event) => update({ ...feature.properties, name: event.target.value })}
    />
  </label>
);

export const translate: NonNullable<GeoreferencerProps["t"]> = (message) => {
  const messages: Record<string, string> = { "Choose image": "Bild auswählen" };
  return messages[message] ?? message;
};

Pass these functions as props to Georeferencer, or to the relevant composable panels. The package includes focusable map/image controls, labeled inputs and status feedback; hosts remain responsible for accessible custom property editors, layout, translations and their application's overall keyboard behavior.

Source organization

src/index.ts defines the public exports. The editor lives in Georeferencer.tsx, composable panels in panels/, the subscription hook in hooks/, shared fields in components/, and download handling in utils/. Translation types/defaults live in localization.ts. These internal modules preserve the public API; consumers continue importing from @georeferencing/react and its styles.css entry.

Troubleshooting and license

@georeferencing/react exposes . and /styles.css. Declarations carry TypeDoc comments for editor help; the same comments are published as the API documentation. Source, issues and contribution notes are in the GitHub repository. MIT licensed.

| Symptom | Check | | --- | --- | | Blank map or missing overlay | Host map target/size, valid current fit and overlay visibility | | Repeated reference loads / lost map interaction state | Stable controller, map and memoized binding options | | Duplicate editor layers/interactions | Only one attachment per controller/map; avoid manual binding alongside Georeferencer | | Drawing/save controls unavailable | Opt-in digitizing, valid confirmation, feature review and configured save handler | | Export controls absent | Register the desired optional plugins | | Worker or map-capture error | Worker URLs/CSP, supported browser APIs and CORS-safe map layers |