# MapLibre-Geoman Documentation (Full) > Complete documentation for MapLibre-Geoman. > Source: https://geoman.io/docs/maplibre > Index: https://geoman.io/docs/maplibre/llms.txt --- ## MapLibre drawing and editing with Geoman > Add polygon drawing, geometry editing and GeoJSON import/export to MapLibre GL JS with Geoman. Start with Free, explore Pro tools, or use the Mapbox adapter. **URL:** https://geoman.io/docs/maplibre Geoman adds drawing and geometry editing to **MapLibre GL JS**. Let users create polygons, lines and markers, edit vertices, snap to nearby geometry and export their work as GeoJSON. This documentation also covers **Mapbox-Geoman**, a separate adapter with the same Geoman API for Mapbox GL JS. Choose the package for the renderer your application uses. [Try the MapLibre demo](https://geoman.io/demo/maplibre) · [Install Geoman](/basics) · [Compare Free and Pro](https://geoman.io/pricing) ## Install the Free package For MapLibre, install the drawing plugin and its renderer in your application: ```sh npm install maplibre-gl@^6 @geoman-io/maplibre-geoman-free@^0.10 ``` Follow [Basic Usage and Installation](/basics) to load the CSS, configure the MapLibre 6 worker and initialize `map` and `geoman`. The guide also covers the Mapbox adapter and Pro packages. Initialize the map after the container mounts when using an SSR framework. ## Draw your first polygon, then edit it Add these listeners immediately after creating your `geoman` instance in the installation example: ```javascript geoman.mapAdapter.once('gm:loaded', async () => { await geoman.enableDraw('polygon'); }); geoman.mapAdapter.on('gm:create', async () => { await geoman.disableDraw(); await geoman.enableGlobalEditMode(); }); geoman.mapAdapter.on('gm:editend', () => { const geojson = geoman.features.exportGeoJson(); console.log('Edited geometry:', geojson); }); ``` Click to add vertices and click the first vertex again to close the polygon. The example then enables editing: drag a vertex to change its shape. `exportGeoJson()` returns the current FeatureCollection; your application decides when and where to save it. The toolbar provides drawing and editing controls too. ## Continue with your own data - [Import an existing GeoJSON collection](/importing-data): load saved features before editing and choose how to handle feature IDs. - [Export GeoJSON](/exporting-data): retrieve the current geometry for your own API or file download. - [Drawing modes](/draw-modes/draw-polygon) and [editing modes](/edit-modes/edit-change): choose the interactions your editor exposes. - [Events](/events): react to completed edits, newly drawn features and removals. - [Configuration](/configuring-geoman): adapt the controls and behavior to your application. - [Migrate from Mapbox GL Draw](https://geoman.io/blog/maplibre-geoman-vs-mapbox-gl-draw): use a complete Vite example and an API mapping. ## Free and Pro tools **Free** includes drawing, vertex editing, dragging, rotation, cutting and snapping. **Pro** adds tools such as splitting, scaling, measurements, auto tracing and undo/redo. Features and options that require Pro are marked with a star (⭐) throughout these docs. The interactive demos include Pro capabilities. The Free packages use the MIT license. Pro uses a commercial license; one MapLibre/Mapbox-Geoman Pro license covers both adapters. Leaflet-Geoman Pro is licensed separately. Review [current plans and support options](https://geoman.io/pricing) for the scope that fits your application. --- ## Accessibility and Localisation ⭐ > Screen-reader announcements and localisation hooks in Geoman Pro 0.11. **URL:** https://geoman.io/docs/maplibre/accessibility Geoman adds a polite `aria-live` region to the map container for selection changes and rejected operations. Set `settings.announceActions: false` to disable it. `settings.announcementResolver` receives the announcement kind, contextual data, and `defaultMessage`. Return translated text, `undefined` to use the default English message, or an empty string to remain silent. A throwing resolver falls back to the default. ```typescript const gm = new Geoman(map, { settings: { announcementResolver: ({ defaultMessage }) => translate(defaultMessage), selectionMessageResolver: ({ defaultMessage }) => translate(defaultMessage), }, }); ``` The selection resolver controls guidance on disabled controls that require a selection. A per-control `requiresSelection.message` takes precedence. Keyboard actions are scoped to the map container and ignore form inputs. Arrow keys nudge the selected features by `keyboard.nudgeStepPx` (default 2); Shift multiplies the distance by ten. Enter completes a valid line/polygon draw. See [Keyboard Shortcuts](/keyboard-shortcuts). Live announcements are not a claim of complete keyboard accessibility: toolbar roving focus and keyboard vertex navigation remain future work. --- ## Basic Usage and Installation > Installation guide, basic setup, and initialization for MapLibre-Geoman and Mapbox-Geoman (free and pro). **URL:** https://geoman.io/docs/maplibre/basics # Basic Usage This documentation applies to both adapter packages: - `@geoman-io/maplibre-geoman-*` - `@geoman-io/mapbox-geoman-*` Both adapters share the same Geoman API and are built from the same core package in the `maplibre-geoman`/`maplibre-geoman-pro` workspaces. ## Installation ### Free Version ```shell # install MapLibre free version npm install @geoman-io/maplibre-geoman-free # install Mapbox free version npm install @geoman-io/mapbox-geoman-free ``` The free packages are available on npm: - [@geoman-io/maplibre-geoman-free](https://www.npmjs.com/package/@geoman-io/maplibre-geoman-free) - [@geoman-io/mapbox-geoman-free](https://www.npmjs.com/package/@geoman-io/mapbox-geoman-free) ### Pro Version Add the following content to .npmrc in your project root ```shell #.npmrc @geoman-io:registry=https://npm.geoman.io/ //npm.geoman.io/:_authToken="" ``` Replace `` with your license key. ```shell # install MapLibre pro version npm install @geoman-io/maplibre-geoman-pro # install Mapbox pro version npm install @geoman-io/mapbox-geoman-pro ``` Don't have a license key yet? Purchase one here. #### Installing the Pro Version on Vercel To install the Pro version on Vercel, follow these steps: 1. Add the License Key to Environment Variables - Set up a new environment variable for your license key. - Environment Variable Name: `NPM_TOKEN_GEOMAN` - Environment Variable Value: `` (replace `` with your actual license key) 2. Set the .npmrc Configuration as an Environment Variable - Create a special environment variable to store your .npmrc configuration. - Environment Variable Name: `NPM_RC` - Environment Variable Value: Use the following content (preserve the line breaks): ``` engine-strict=true @geoman-io:registry=https://npm.geoman.io //npm.geoman.io/:_authToken=${NPM_TOKEN_GEOMAN} ``` Once these variables are set, Vercel will use the correct .npmrc configuration and license key to access the Pro version. ## Expected HTML Structure ```html Geoman MapLibre / Mapbox
``` ## MapLibre and Geoman initialization Pro 0.11 requires MapLibre GL JS 6 (`>=6.0.0 <7.0.0`) and ESM imports. Configure the worker **before** creating a map. The example below uses Vite's `?url` asset import. For Next.js, Angular, or Webpack-based sites, copy both `maplibre-gl-worker.mjs` and `maplibre-gl-shared.mjs` from the installed package's `dist` directory into the same public asset directory. Then use `ml.setWorkerUrl('/vendor/maplibre/maplibre-gl-worker.mjs')`, including your deployment's base path where needed. Keep both files in sync with the installed MapLibre version; the worker imports the shared module using a relative URL. MapLibre 6 has no default export or UMD bundle. ```typescript import 'maplibre-gl/dist/maplibre-gl.css'; import '@geoman-io/maplibre-geoman-free/dist/maplibre-geoman.css'; import * as ml from 'maplibre-gl'; import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'; ml.setWorkerUrl(workerUrl); import { Geoman, type GmOptionsPartial } from '@geoman-io/maplibre-geoman-free'; // or '@geoman-io/maplibre-geoman-pro' const mapLibreStyle: ml.StyleSpecification = { version: 8, glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf', sources: { 'osm-tiles': { type: 'raster', tiles: [ 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', ], tileSize: 256, attribution: '© OpenStreetMap contributors', }, }, layers: [ { id: 'osm-tiles-layer', type: 'raster', source: 'osm-tiles', minzoom: 0, maxzoom: 19, }, ], }; const map = new ml.Map({ container: 'dev-map', style: mapLibreStyle, center: [0, 51], zoom: 5, }); const gmOptions: GmOptionsPartial = { // geoman options here }; // create a new geoman instance const geoman = new Geoman(map, gmOptions); // callback when geoman is fully loaded geoman.mapAdapter.on('gm:loaded', () => { console.log('Geoman fully loaded'); // Here you can add your geojson shapes for example const shapeGeoJson = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 51] }, properties: {}, }; // add a geojson shape to the map geoman.features.importGeoJsonFeature(shapeGeoJson); const shapeGeoJson2 = { type: 'Feature', geometry: { type: 'Point', coordinates: [3, 52] }, properties: {}, }; // geoman instance is also available on the map object map.gm?.features.importGeoJsonFeature(shapeGeoJson2); }); ``` ## Mapbox and Geoman initialization ```typescript import mapboxgl from 'mapbox-gl'; import { Geoman, type GmOptionsPartial } from '@geoman-io/mapbox-geoman-free'; // or '@geoman-io/mapbox-geoman-pro' import 'mapbox-gl/dist/mapbox-gl.css'; import '@geoman-io/mapbox-geoman-free/dist/mapbox-geoman.css'; mapboxgl.accessToken = import.meta.env.VITE_MAPBOX_TOKEN ?? ''; const mapStyle: mapboxgl.StyleSpecification = { version: 8, glyphs: 'https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf', sources: { 'osm-tiles': { type: 'raster', tiles: [ 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', ], tileSize: 256, attribution: '© OpenStreetMap contributors', }, }, layers: [ { id: 'osm-tiles-layer', type: 'raster', source: 'osm-tiles', minzoom: 0, maxzoom: 19, }, ], }; const map = new mapboxgl.Map({ container: 'dev-map', style: mapStyle, center: [0, 51], zoom: 5, }); const gmOptions: GmOptionsPartial = { // geoman options here }; const geoman = new Geoman(map, gmOptions); geoman.mapAdapter.on('gm:loaded', () => { console.log('Geoman fully loaded'); }); ``` --- ## Changelog > Release notes for MapLibre-Geoman (Free) and MapLibre-Geoman Pro. **URL:** https://geoman.io/docs/maplibre/changelog Latest releases of `@geoman-io/maplibre-geoman-free` and `@geoman-io/maplibre-geoman-pro` (also covering the Mapbox variants `@geoman-io/mapbox-geoman-free` / `@geoman-io/mapbox-geoman-pro`, which are versioned in lockstep). For the complete history, see the per-repository changelogs: - [Free: `maplibre-geoman` CHANGELOG](https://github.com/geoman-io/maplibre-geoman/blob/master/CHANGELOG.md) and [releases](https://github.com/geoman-io/maplibre-geoman/releases) - [Pro: `maplibre-geoman-pro` CHANGELOG](https://github.com/geoman-io/maplibre-geoman-pro/blob/master/CHANGELOG.md) and [releases](https://github.com/geoman-io/maplibre-geoman-pro/releases) ## MapLibre-Geoman Free 0.10.0 — 2026-09-23 ### Added - `gm.draw.create(input)` and `gm.draw.finish(input?)` for marker, circle, and rectangle creation and completion. See [Programmatic Drawing](https://github.com/geoman-io/maplibre-geoman/blob/master/docs/programmatic-drawing.md). ### Changed - Development and test baseline is now MapLibre GL JS **6.11.0**. The peer dependency stays `maplibre-gl >=6.0.0 <7.0.0`, so no consumer changes are required. ### Fixed - Preserve explicitly supplied circle centers during feature creation. - Marker draw previews now use registered raster images named by a literal `layerStyles.marker.gm_temporary` `icon-image` ([#122](https://github.com/geoman-io/maplibre-geoman/issues/122)). - TypeScript compilation of the MapLibre adapter against maplibre-gl 6.11's stricter event typings. ## MapLibre-Geoman Pro 0.13.0 — 2026-09-23 ### Changed - Development and test baseline is now MapLibre GL JS **6.11.0**. The peer dependency stays `maplibre-gl >=6.0.0 <7.0.0`, so no consumer changes are required. Both packages publish with the same version numbers for MapLibre GL JS and Mapbox GL JS targets. --- ## Configuring Geoman > Configure Geoman controls, styles, settings, and layer behaviors through the options object. **URL:** https://geoman.io/docs/maplibre/configuring-geoman Geoman can be configured by passing an options object to the Geoman constructor. The configuration allows you to customize various aspects of the library, including controls, styles, and general settings. ## Configuration Structure The main configuration object follows this structure: ```typescript interface GmOptionsData { settings: GmSettings; // High-level, per-source style variables (recommended way to recolour/resize // overlays). Deep-merged over the built-in defaults and compiled into layerStyles. styleVariables: SourceStyles; // Low-level raw MapLibre/Mapbox paint layers, applied on top of styleVariables. layerStyles: typeof defaultLayerStyles; // Each mode entry is optional, so you only specify the controls you want to override. controls: { draw: { [key in DrawModeName]?: ControlOptions }; edit: { [key in EditModeName]?: ControlOptions }; helper: { [key in HelperModeName]?: ControlOptions }; }; } ``` You can provide a partial configuration using `GmOptionsPartial`, which allows you to specify only the options you want to override. Every option below has a built-in default, so you only set the fields you want to change. ## Basic Usage Here's a basic example of configuring Geoman: ```typescript import { Geoman, GmOptionsPartial } from '@geoman-io/maplibre-geoman-free'; // or '@geoman-io/maplibre-geoman-pro', '@geoman-io/mapbox-geoman-free', '@geoman-io/mapbox-geoman-pro' const gmOptions: GmOptionsPartial = { settings: { controlsPosition: 'top-right', throttlingDelay: 100 }, controls: { draw: { polygon: { title: 'Draw Polygon', icon: 'custom-polygon-icon', uiEnabled: true, active: false } } } }; const gm = new Geoman(map, gmOptions); ``` ## Settings Configuration The `settings` object configures global Geoman behavior. The table below lists **every** setting together with its **default value**. | Setting | Type | Default | Description | |:--------|:-----|:--------|:------------| | `throttlingDelay` | `number` | `10` | Delay in milliseconds used to throttle high-frequency events (drag, move). | | `awaitDataUpdatesOnEvents` | `boolean` | `true` | When `true`, events like `gm:create` and `gm:remove` wait for MapLibre to commit data updates before firing, so feature data is accessible via `exportGeoJson()` in handlers. Set to `false` for faster async updates. | | `useControlsUi` | `boolean` | `true` | Render Geoman's built-in control toolbar. Set to `false` to drive everything through the API. | | `controlsPosition` | `'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `'top-left'` | Position of the controls on the map. | | `controlsUiEnabledByDefault` | `boolean` | `true` | Whether controls appear in the UI by default. Individual controls can override this with their own `uiEnabled`. | | `controlsCollapsible` | `boolean` | `false` | Show a button that toggles the visibility of all controls. | | `useDefaultLayers` | `boolean` | `true` | Create the default rendering layers for features. Set to `false` to define all layers manually. | | `useCursorHandlers` | `boolean` | `true` | Let Geoman manage the map cursor while drawing/editing. | | `controlsStyles` | `ControlStyles` | see below | CSS classes applied to the control group/container/button. | | `disableSelectionGating` | `boolean` | `false` | When `true`, controls that require a selection (`add_hole`, `add_part`, `merge_parts`) are never shown disabled. The modes still validate at run time. | | `clearSelectionOnBackgroundClick` | `boolean` | `true` | Clear the global selection when the user clicks the empty map background. | | `renderSelection` | `boolean` | `true` | Render the always-on selection overlay that outlines the globally-selected features in any mode. See [Selection](/selection). | | `history` | `HistorySettings` | see below | Undo/redo + transaction history. See [History & Undo/Redo](/history). | | `keyboard` | `KeyboardSettings` | see below | Container-scoped keyboard shortcuts. See [Keyboard Shortcuts](/keyboard-shortcuts). | | `idGenerator` | `null \| (feature) => string` | `null` | Custom feature-ID generator. When `null`, Geoman auto-generates IDs. See [Feature IDs](/feature-ids). | | `snapDistance` | `number` | `18` | Snapping tolerance in pixels for the snapping helper. | | `snapping` | `SnappingSettings` | see below | Snap against host-owned map sources/layers. See [Snapping to External Sources](/external-snapping). | | `markerIcons` | `{ default: string; control: string }` | built-in SVGs | SVG icons used for markers (used internally). | ```typescript const gmOptions: GmOptionsPartial = { settings: { // Delay in milliseconds for throttling events throttlingDelay: 10, // When true, events like gm:create wait for MapLibre to commit data updates // before firing, so feature data is available via exportGeoJson() in handlers awaitDataUpdatesOnEvents: true, // Render the built-in control toolbar useControlsUi: true, // Position of the controls on the map controlsPosition: 'top-left', // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' // Enable each control in the UI by default; individual controls can override controlsUiEnabledByDefault: true, // Display the button which toggles all controls visibility controlsCollapsible: false, // Create the default layers for rendering features useDefaultLayers: true, // Let Geoman manage the cursor while drawing/editing useCursorHandlers: true, // Controls styling in case you want custom buttons controlsStyles: { controlGroupClass: 'maplibregl-ctrl maplibregl-ctrl-group', controlContainerClass: 'gm-control-container', controlButtonClass: 'gm-control-button', }, // Never grey-out selection-gated controls (add_hole / add_part / merge_parts) disableSelectionGating: false, // Clear the global selection when clicking the empty map background clearSelectionOnBackgroundClick: true, // Outline the globally-selected features in every mode renderSelection: true, // Undo/redo + transaction history (see the History page) history: { enabled: true, mode: 'internal', limit: 50, trackSelection: true, showControls: true, }, // Keyboard shortcuts (see the Keyboard Shortcuts page) keyboard: { enabled: true, bindings: { undo: ['mod+z'], redo: ['mod+shift+z', 'mod+y'], deleteSelected: ['Delete', 'Backspace'], cancel: ['Escape'], }, }, // Custom ID generator function for features (optional) // If null, Geoman will auto-generate IDs like 'feature-1', 'feature-2', etc. idGenerator: null, // Snapping tolerance in pixels for the snapping helper snapDistance: 18, // Snap against host-owned sources/layers (see the External Snapping page) snapping: { targets: [], acquire: 'rendered', maxCandidates: 200, }, // SVG icons for markers (used internally) markerIcons: { default: '...', control: '...', }, } }; ``` ### History, Keyboard and Snapping settings These three settings are sub-objects with their own defaults. They are summarized here and documented in full on their dedicated pages. ```typescript // settings.history — defaults interface HistorySettings { enabled: boolean; // default: true — master switch mode: 'internal' | 'controlled'; // default: 'internal' limit: number; // default: 50 — max ChangeSets on the internal undo stack trackSelection: boolean; // default: true — restore selection on undo showControls: boolean; // default: true — render built-in Undo/Redo buttons } // settings.keyboard — defaults interface KeyboardSettings { enabled: boolean; // default: true bindings: Record; // default bindings: // undo: ['mod+z'] // redo: ['mod+shift+z', 'mod+y'] // deleteSelected: ['Delete', 'Backspace'] // cancel: ['Escape'] } // settings.snapping — defaults interface SnappingSettings { targets: SnapTarget[]; // default: [] (external snapping disabled) acquire: 'rendered' | 'source'; // default: 'rendered' maxCandidates: number; // default: 200 } ``` See [History & Undo/Redo](/history), [Keyboard Shortcuts](/keyboard-shortcuts) and [Snapping to External Sources](/external-snapping) for full details. ## Controls Configuration ### Draw Controls Draw controls manage the creation of new geometries. Available draw modes include: 'marker', 'circle', 'circle_marker', 'ellipse', 'text_marker', 'line', 'rectangle', 'polygon', 'freehand', and 'custom_shape'. ```typescript const gmOptions: GmOptionsPartial = { controls: { draw: { polygon: { title: 'Draw Polygon', icon: 'custom-polygon-icon', uiEnabled: true, active: false, options: { snap: { type: 'toggle', label: 'Snap to Vertices', value: true } } }, line: { title: 'Draw Line', uiEnabled: true, active: false } } } }; ``` ### Edit Controls Edit controls handle modification of existing geometries. Available edit modes include: 'select', 'drag', 'change', 'rotate', 'scale', 'copy', 'cut', 'split', 'union', 'difference', 'line_simplification', 'lasso', 'add_hole', 'add_part', 'remove_ring', 'explode', 'merge_parts', and 'delete'. ```typescript const gmOptions: GmOptionsPartial = { controls: { edit: { drag: { title: 'Drag Features', icon: 'drag-icon', uiEnabled: true, active: false }, rotate: { title: 'Rotate Features', uiEnabled: true, active: false, options: { rotationStep: { type: 'select', label: 'Rotation Step', value: { title: '45°', value: 45 }, choices: [ { title: '15°', value: 15 }, { title: '45°', value: 45 }, { title: '90°', value: 90 } ] } } } } } }; ``` ### Helper Controls Helper controls provide additional functionality. Available helper modes include: 'shape_markers', 'pin', 'snapping', 'snap_guides', 'measurements', 'auto_trace', 'geofencing', 'zoom_to_features', and 'click_to_edit'. ```typescript const gmOptions: GmOptionsPartial = { controls: { helper: { snapping: { title: 'Snap to Features', uiEnabled: true, active: true, options: { snapToVertices: { type: 'toggle', label: 'Snap to Vertices', value: true } } }, measurements: { title: 'Show Measurements', uiEnabled: true, active: false } } } }; ``` ## Style Variables (recommended styling) `styleVariables` is the high-level, recommended way to recolour and resize Geoman's overlays. You set only the fields you want to change per source — they are deep-merged over the built-in defaults and compiled into `layerStyles`. For full control over the raw paint layers, the lower-level [`layerStyles`](#layer-styles-configuration) option is still available and is applied **on top of** whatever `styleVariables` compiles to. The variables are configured per source. The most common source is `main` (your persistent features); `temporary` is used while drawing/editing, `internal` for helper geometry, and (Pro) `standby` for parked features. ```typescript type StyleVariables = { lineColor: string; lineOpacity: number; lineWidth: number; fillColor: string; fillOpacity: number; circleMarkerRadius: number; holeMarkerColor?: string; // selection / sub-editing highlight channels highlightSelectedColor?: string; highlightSelectedWidth?: number; highlightSelectedFillColor?: string; highlightSelectedFillOpacity?: number; highlightCandidateColor?: string; highlightCandidateWidth?: number; highlightCandidateOpacity?: number; highlightHoverColor?: string; highlightHoverWidth?: number; highlightHoverFillOpacity?: number; highlightSnapColor?: string; highlightSnapWidth?: number; }; type SourceStyles = { main: StyleVariables; temporary: StyleVariables; internal: StyleVariables; standby?: StyleVariables; // Pro only }; ``` ### Default style variables The defaults applied to each source are: | Variable | Default | |:---------|:--------| | `lineColor` | `#1971c2` (`temporary`: `#ff5600`, `standby`: `#787878`) | | `lineOpacity` | `0.8` | | `lineWidth` | `3` | | `fillColor` | `#4fb3ff` (`standby`: `#a5a5a5`) | | `fillOpacity` | `0.4` | | `circleMarkerRadius` | `10` | | `holeMarkerColor` | `#9c36b5` | | `highlightSelectedColor` | `#e03131` | | `highlightSelectedFillColor` | `#4fb3ff` | | `highlightCandidateColor` | `#fab005` | | `highlightHoverColor` | `#e8590c` | ### Example ```typescript const gmOptions: GmOptionsPartial = { styleVariables: { main: { lineColor: '#278cda', fillColor: '#278cda', fillOpacity: 0.25, lineWidth: 2, }, }, }; ``` ## Layer Styles Configuration Geoman uses Mapbox/Maplibre style specification to style different geometric shapes. Each shape type can have multiple layer styles, and there are two main categories of styles for each shape: - `gm_main`: The default style used for displaying features - `gm_temporary`: The style used for features being edited or temporary features (like during drawing) ### Layer Style Structure The layer styles configuration follows this structure: ```typescript layerStyles: { [shapeType: string]: { gm_main: Array; gm_temporary: Array; } } ``` ### Available Shape Types You can configure styles for the following shape types: - `marker` - `circle` - `ellipse` - `circle_marker` - `text_marker` - `line` - `rectangle` - `polygon` ### Layer Style Types Geoman supports four types of layer styles: 1. Symbol Layer (for markers and text): ```typescript interface PartialSymbolLayer { type: 'symbol'; layout?: { 'icon-image'?: string; 'icon-size'?: number; 'icon-allow-overlap'?: boolean; 'icon-anchor'?: 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; 'text-field'?: string[]; 'text-size'?: number; 'text-justify'?: 'auto' | 'left' | 'center' | 'right'; 'text-anchor'?: 'center' | 'left' | 'right' | 'top' | 'bottom' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; }; paint?: { 'text-color'?: string; 'text-opacity'?: number; 'text-halo-color'?: string; 'text-halo-width'?: number; }; } ``` 2. Line Layer (for lines and polygon outlines): ```typescript interface PartialLineLayer { type: 'line'; paint?: { 'line-color'?: string; 'line-width'?: number; 'line-opacity'?: number; 'line-dasharray'?: number[]; }; layout?: { 'line-cap'?: 'butt' | 'round' | 'square'; 'line-join'?: 'bevel' | 'round' | 'miter'; }; } ``` 3. Fill Layer (for polygons): ```typescript interface PartialFillLayer { type: 'fill'; paint?: { 'fill-color'?: string; 'fill-opacity'?: number; 'fill-outline-color'?: string; 'fill-antialias'?: boolean; }; } ``` 4. Circle Layer (for circle markers): ```typescript interface PartialCircleLayer { type: 'circle'; paint?: { 'circle-radius'?: number; 'circle-color'?: string; 'circle-opacity'?: number; 'circle-stroke-width'?: number; 'circle-stroke-color'?: string; 'circle-stroke-opacity'?: number; }; } ``` ### Comprehensive Example Here's a detailed example showing how to style different shape types: ```typescript const gmOptions: GmOptionsPartial = { layerStyles: { // Marker styles marker: { gm_main: [ { type: "symbol", layout: { "icon-image": "default-marker", "icon-size": 0.25, "icon-allow-overlap": true, "icon-anchor": "bottom", }, }, ], gm_temporary: [ { type: "symbol", layout: { "icon-image": "temp-marker", "icon-size": 0.25, }, }, ], }, // Text marker styles text_marker: { gm_main: [ { type: "symbol", layout: { "text-field": ["get", "text"], "text-size": 32, "text-justify": "center", "text-anchor": "center", }, paint: { "text-color": "#333333", "text-halo-color": "#ffffff", "text-halo-width": 2, }, }, ], }, // Line styles line: { gm_main: [ { type: "line", paint: { "line-color": "#3388ff", "line-width": 3, "line-opacity": 0.7, }, layout: { "line-cap": "round", "line-join": "round", }, }, ], gm_temporary: [ { type: "line", paint: { "line-color": "#ff3388", "line-width": 3, "line-opacity": 0.7, "line-dasharray": [2, 2], }, }, ], }, // Polygon styles with both fill and line properties polygon: { gm_main: [ { type: "fill", paint: { "fill-color": "#3388ff", "fill-opacity": 0.2, "fill-outline-color": "#3388ff", }, }, { type: "line", paint: { "line-color": "#3388ff", "line-width": 2, "line-opacity": 0.7, }, }, ], // Here we override the temporary style for polygons used during editing gm_temporary: [ { type: "fill", paint: { "fill-color": "#ff3388", "fill-opacity": 0.2, "fill-outline-color": "#ff3388", }, }, { type: "line", paint: { "line-color": "#ff3388", "line-width": 2, "line-opacity": 0.7, "line-dasharray": [2, 2], }, }, ], }, } }; ``` ### Important Notes 1. Layer Order: Multiple layer styles for a single shape are rendered in the order they appear in the array. For polygons, it's common to define a fill layer followed by a line layer for the border. 2. Style Inheritance: If you don't specify `gm_temporary` styles, features will use `gm_main` styles during editing. 3. Custom Icons: For marker symbols using custom icons, make sure to load the images into the map before using them in the style configuration. 4. Dynamic Properties: You can use Mapbox/Maplibre expressions to create dynamic styles based on feature properties. 5. Performance: Consider using simpler styles for temporary features to improve performance during editing operations. ## Control Options Each control can have the following options: ```typescript interface ControlOptions { title: string; // Display title for the control icon: string | null; // Icon for the control button uiEnabled: boolean; // Whether the control appears in the UI active: boolean; // Whether the control is active by default options?: ActionOptions; // Additional control-specific options settings?: ActionSettings; // Control-specific settings (boolean | string | null) order?: number; // Optional ordering of the control in the UI } ``` ### Action Options Types Each control's `options` is an object keyed by option name, where each value is one of the following action option types: ```typescript type ActionOptions = { [name: string]: SelectActionOption | ToggleActionOption | HiddenActionOption; }; type SelectActionOption = { type: 'select'; label: string; value: { title: string; value: boolean | string | number }; choices: Array<{ title: string; value: boolean | string | number }>; }; type ToggleActionOption = { type: 'toggle'; label: string; value: boolean; }; type HiddenActionOption = { type: 'hidden'; value: string | boolean | number | undefined; }; ``` ### Default Control Options Every draw, edit and helper mode ships with a default `ControlOptions` entry. Unless noted below, each control defaults to: ```typescript { title: '', // e.g. 'Polygon', 'Rotate', 'Snapping' icon: '', uiEnabled: true, active: false, } ``` A few controls carry extra default `options`/`settings`, and one helper is hidden by default: | Mode | Type | Default extras | |:-----|:-----|:---------------| | `custom_shape` | draw | `options.shape` — a `select` defaulting to `Triangle` (choices: `Triangle`, `Rectangle`). | | `change` | edit | `settings: { bodyDragEnabled: false, editSelectedOnly: false }`. | | `measurements` | helper | `options.units` — a `hidden` option (auto-detect; set to `'metric'` or `'imperial'`); `settings: { stickyMode: true }`. | | `shape_markers` | helper | `icon: null`, `uiEnabled: false` — internal helper, no toolbar button. See [Shape Markers](/helper-modes/helper-shape_markers). | The full default control title for each mode: | Mode | Default title | |:-----|:--------------| | `marker` / `circle_marker` / `text_marker` | `Marker` / `Circle Marker` / `Text Marker` | | `circle` / `ellipse` / `line` / `rectangle` / `polygon` | `Circle` / `Ellipse` / `Line` / `Rectangle` / `Polygon` | | `freehand` / `custom_shape` | `Freehand` / `Custom shape` | | `select` / `drag` / `change` / `rotate` / `scale` | `Select` / `Drag` / `Change` / `Rotate` / `Scale` | | `copy` / `cut` / `split` / `union` / `difference` | `Copy` / `Cut` / `Split` / `Union` / `Difference` | | `line_simplification` / `lasso` | `Line simplification` / `Lasso select` | | `add_hole` / `add_part` / `remove_ring` | `Add hole` / `Add part` / `Remove hole/part` | | `explode` / `merge_parts` / `delete` | `Explode multipolygon` / `Merge into multipolygon` / `Delete` | | `shape_markers` / `snapping` / `pin` / `snap_guides` | `Shape markers` / `Snapping` / `Pin` / `Snap guides` | | `measurements` / `auto_trace` / `geofencing` | `Measurements` / `Auto trace line` / `Geofencing` | | `zoom_to_features` / `click_to_edit` | `Zoom to features` / `Click to edit` | ## Full Configuration Example Here's a comprehensive example combining various configuration options: ```typescript const gmOptions: GmOptionsPartial = { settings: { controlsPosition: 'top-right', throttlingDelay: 100 }, controls: { draw: { polygon: { title: 'Draw Polygon', icon: 'polygon-icon', uiEnabled: true, active: false, options: { snap: { type: 'toggle', label: 'Snap to Vertices', value: true } } } }, edit: { rotate: { title: 'Rotate Features', uiEnabled: true, active: false, } }, helper: { snapping: { title: 'Snap to Features', uiEnabled: true, active: true } } }, layerStyles: { polygon: { gm_main: [ { type: 'fill', paint: { 'fill-color': '#3388ff', 'fill-opacity': 0.2, 'fill-outline-color': '#3388ff' } } ] } } }; const gm = new Geoman(map, gmOptions); ``` This configuration provides a comprehensive setup for Geoman, including custom controls, styles, and settings. Remember that all these options are partial, so you only need to specify the options you want to customize - all other options will use their default values. --- ## Data Layers ⭐ > Named editable and display layers, thematic styles, schemas, and source ownership. **URL:** https://geoman.io/docs/maplibre/data-layers Pro 0.11 exposes `gm.dataLayers` for named feature collections. The host owns its UI and persistence; Geoman owns editing and rendering for layers it creates. | Kind | Data ownership | Editing | | --- | --- | --- | | Editable | Geoman feature store and source | Full editing; eligible as active draw target | | Display | Lightweight Geoman source | No feature editing | | External, via `adopt()` | Existing host map layers | Never owned, restyled, or removed by Geoman | There is no separate `interactive` kind. ## Create and populate an editable layer Run this after `gm:loaded`: ```typescript gm.dataLayers.add({ id: 'parcels', name: 'Parcels', editable: true, geometryTypes: ['polygon'], style: { polygon: { fillColor: '#2563eb', fillOpacity: 0.3, strokeWidth: 2 }, }, }); await gm.dataLayers.setData('parcels', featureCollection); gm.dataLayers.setActive('parcels'); ``` New draws go to the active layer. Features retain their home layer across editing, copy, compound geometry operations, and undo/redo. ## Manage layers | Method | Purpose | | --- | --- | | `add(spec)`, `remove(id)` | Create or remove an owned layer | | `get(id)`, `list()` | Read resolved layer specs | | `setData(id, geojson)` | Await replacement of a layer's data | | `setStyle(id, style)` | Replace style and reset omitted paint properties | | `setVisibility(id, visible)` | Show or hide | | `setActive(id)`, `getActive()` | Select or read the editable draw target | | `setEditable(id, editable)` | Convert display/editable state | | `reorder(ids)` | First ID is frontmost; unknown/external layers are skipped | | `setSchema(id, schema)`, `validate(id, properties)` | Configure/check attributes | `get()` and `list()` return shallow spec copies; nested metadata remains host-owned. Keep application state changes explicit through the API. ## Attribute-driven styles ```typescript gm.dataLayers.setStyle('parcels', { polygon: { fillColor: { categorical: { field: 'status', categories: { available: '#22c55e', reserved: '#f59e0b' }, fallback: '#94a3b8', }, }, fillOpacity: 0.4, }, label: { field: 'name', size: 12, color: '#111827' }, }); ``` Labels require `glyphs` in the map style. The portable DSL supports `get`, `categorical`, `step`, and `interpolate`; numeric stops are sorted. Raw MapLibre/Mapbox expression arrays are accepted but make that style renderer-specific. Schema labels/descriptions/option colours are presentation metadata for your application; Geoman does not generate an attribute editor or automatically turn schema option colours into paint. ## Integrity and history `geometryTypes` restricts accepted geometry. Enable `settings.validateSchema` for schema enforcement and `settings.validateGeometry` for geometry checks. Bulk `setData()` and display-to-editable conversion validate before mutation. History replay restores prior data even if constraints have since changed. Removing a layer also removes its features parked in editing scratch sources. Disabling the active layer's editability clears that active target. --- ## Draw Circle > Draw circles on the map by defining a center point and radius. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-circle # Draw Circle Mode Circle Mode allows you to add circles to the map by defining a center point and radius. You can enable Draw Circle Mode like this: ```js map.gm.enableDraw("circle"); map.gm.disableDraw(); map.gm.toggleDraw("circle"); map.gm.drawEnabled("circle"); // Or like this: map.gm.enableMode("draw", "circle"); map.gm.disableMode("draw", "circle"); map.gm.toggleMode("draw", "circle"); map.gm.isModeEnabled("draw", "circle"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :-------------------------------- | :------------------------ | | `gm:drawstart` | `event` | Fired when circle drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when a circle is created. | `map`, `shape`, `feature` | ## Behavior Circle drawing operates by: 1. Clicking on the map to place the circle center 2. Moving the mouse to adjust the radius 3. Clicking again to finalize the circle The circle is represented as a GeoJSON polygon with 80 steps by default for smooth rendering. ## Live Draw Circle Example --- ## MapLibre Circle Markers: Drawing and GeoJSON > Draw fixed-size circle markers with MapLibre-Geoman. Compare markers and circles, enable drawing after initialization, and export points as GeoJSON. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-circle_marker # Draw Circle Marker Mode Circle Marker Mode allows you to add circle markers to the map. Circle markers are circles with a fixed radius in pixels that maintain their screen size regardless of zoom level. ## Circle marker, marker or circle? | Shape | Use it for | Behavior when zooming | | --- | --- | --- | | Circle marker | Point locations with a simple round symbol | Keeps a fixed pixel radius | | Marker | Point locations shown with a marker symbol | Keeps a screen-sized symbol | | Circle | A geographic area around a center | Its screen size changes with map zoom | A circle marker represents a point location, not a geographic buffer. Choose [Draw Circle](./03-draw-circle.mdx) when the area on the ground matters. ## Enable circle marker drawing Create the Geoman instance using the [installation guide](/docs/maplibre/basics) first. Wait for it to finish initializing before activating a tool: ```typescript geoman.mapAdapter.once('gm:loaded', () => { geoman.enableDraw('circle_marker'); }); ``` On an already initialized map, the following methods let your own controls toggle drawing: ```js map.gm.enableDraw("circle_marker"); map.gm.disableDraw(); map.gm.toggleDraw("circle_marker"); map.gm.drawEnabled("circle_marker"); // Or like this: map.gm.enableMode("draw", "circle_marker"); map.gm.disableMode("draw", "circle_marker"); map.gm.toggleMode("draw", "circle_marker"); map.gm.isModeEnabled("draw", "circle_marker"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :--------------------------------------- | :------------------------ | | `gm:drawstart` | `event` | Fired when circle marker drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when a circle marker is created. | `map`, `shape`, `feature` | ## Behavior Circle marker drawing operates by: 1. Clicking on the map to place the circle marker center 2. The circle marker is created with a default radius 3. The circle marker maintains its pixel size regardless of zoom level ## Export the drawn points Read the feature collection after a user creates a feature, or from your own save button. Keep the Geoman feature properties when you plan to import the data again. ```typescript geoman.mapAdapter.on('gm:create', () => { const data = geoman.features.exportGeoJson(); console.log(data); }); ``` If no toolbar appears, check that both MapLibre and Geoman CSS are loaded and that the map container has a nonzero height. If drawing is enabled too early, move that call into `gm:loaded` as shown above. See the [event reference](/docs/maplibre/events) for more interactions. ## Live Draw Circle Marker Example --- ## Draw Custom Shape ⭐ > Draw custom predefined shapes like arrows or stars using GeoJSON templates. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-custom_shape # Draw Custom Shape Mode ⭐ Custom Shape Mode allows you to create predefined shapes on the map by selecting from available options. This mode is ideal for adding consistent and reusable shapes without the need to manually define each vertex. You can handle Draw Custom Shape Mode like this: ```js map.gm.enableDraw("custom_shape"); map.gm.disableDraw(); map.gm.toggleDraw("custom_shape"); map.gm.drawEnabled("custom_shape"); // Or like this: map.gm.enableMode("draw", "custom_shape"); map.gm.disableMode("draw", "custom_shape"); map.gm.toggleMode("draw", "custom_shape"); map.gm.isModeEnabled("draw", "custom_shape"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :-------------------------------------- | :------------------------ | | `gm:drawstart` | `event` | Fired when custom shape drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when a custom shape is created. | `map`, `shape`, `feature` | ## Customizing Custom Shape Behavior You can customize the custom shape drawing behavior by defining the available shapes and their configurations. This allows you to offer different predefined shapes that users can select and draw on the map. Example configuration: ```js const customShapeTriangle = { type: "Feature", properties: { shape: "triangle", }, geometry: { type: "Polygon", coordinates: [ [ [4.0, 51.2], [5.4, 52.4], [6.8, 51.2], [4.0, 51.2], ], ], }, }; const customShapeRectangle = { type: "Feature", properties: { shape: "rectangle", }, geometry: { type: "Polygon", coordinates: [ [ [-0.47, 51.67], [1.43, 51.67], [1.43, 53.32], [-0.47, 53.32], [-0.47, 51.67], ], ], }, }; map.gm.options.controls.draw.custom_shape.options = [ { type: "select", label: "Shape", name: "shape", value: { title: "Triangle", value: JSON.stringify(customShapeTriangle) }, choices: [ { title: "Triangle", value: JSON.stringify(customShapeTriangle) }, { title: "Rectangle", value: JSON.stringify(customShapeRectangle) }, ], }, ]; ``` ## Live Draw Custom Shape Example --- ## Draw Ellipse > Draw ellipses on the map by defining a center point and two semi-axis radii. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-ellipse # Draw Ellipse Mode Ellipse Mode allows you to add ellipses to the map by defining a center point and two radii (x and y semi-axes). You can enable Draw Ellipse Mode like this: ```js map.gm.enableDraw("ellipse"); map.gm.disableDraw(); map.gm.toggleDraw("ellipse"); map.gm.drawEnabled("ellipse"); // Or like this: map.gm.enableMode("draw", "ellipse"); map.gm.disableMode("draw", "ellipse"); map.gm.toggleMode("draw", "ellipse"); map.gm.isModeEnabled("draw", "ellipse"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :--------------------------------- | :------------------------ | | `gm:drawstart` | `event` | Fired when ellipse drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when an ellipse is created. | `map`, `shape`, `feature` | ## Behavior Ellipse drawing operates by: 1. Clicking on the map to place the ellipse center 2. Moving the mouse to adjust the x semi-axis (width) 3. Clicking to set the width, then moving to adjust the y semi-axis (height) 4. Clicking again to finalize the ellipse The ellipse is represented as a GeoJSON polygon with smooth edges for rendering. The feature properties include: - `xSemiAxis`: The horizontal radius of the ellipse - `ySemiAxis`: The vertical radius of the ellipse - `center`: The center point coordinates ## Live Draw Ellipse Example --- ## Draw Freehand ⭐ > Draw freehand shapes on the map by clicking and dragging. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-freehand # Draw Freehand Mode ⭐ Freehand Mode allows you to create freehand lines or shapes on the map by continuously dragging the mouse or touch input. This mode is ideal for drawing natural, flowing lines without the need to click for each vertex. You can handle Draw Freehand Mode like this: ```js map.gm.enableDraw("freehand"); map.gm.disableDraw(); map.gm.toggleDraw("freehand"); map.gm.drawEnabled("freehand"); // Or like this: map.gm.enableMode("draw", "freehand"); map.gm.disableMode("draw", "freehand"); map.gm.toggleMode("draw", "freehand"); map.gm.isModeEnabled("draw", "freehand"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :---------------------------------------- | :------------------------ | | `gm:drawstart` | `event` | Fired when freehand drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when a freehand shape is created. | `map`, `shape`, `feature` | ## Behavior Freehand drawing operates by: 1. **Initiating Drawing**: Click and hold the mouse button to start drawing. 2. **Drawing**: Move the cursor to draw a continuous line or shape. The path is dynamically rendered as you move. 3. **Completing Drawing**: Release the mouse button to finalize the shape. ## Live Draw Freehand Example --- ## Draw Line > Draw polylines on the map by clicking to place vertices. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-line # Draw Line Mode Line Mode allows you to create lines on the map by clicking to add vertices. You can enable Draw Line Mode like this: ```js map.gm.enableDraw("line"); map.gm.disableDraw(); map.gm.toggleDraw("line"); map.gm.drawEnabled("line"); // Or like this: map.gm.enableMode("draw", "line"); map.gm.disableMode("draw", "line"); map.gm.toggleMode("draw", "line"); map.gm.isModeEnabled("draw", "line"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :------------------------------ | :------------------------ | | `gm:drawstart` | `event` | Fired when line drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when a line is created. | `map`, `shape`, `feature` | ## Behavior Line drawing operates by: 1. Clicking on the map to place the first vertex 2. Moving the mouse to see a preview of the line segment 3. Clicking again to add additional vertices 4. Double-clicking or clicking on the first vertex to complete the line ## Live Draw Line Example --- ## Draw Marker > Add point markers to the map by clicking on locations. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-marker # Draw Marker Mode Marker Mode allows you to add markers to the map by clicking on desired locations. You can enable Draw Marker Mode like this: ```js map.gm.enableDraw("marker"); map.gm.disableDraw(); map.gm.toggleDraw("marker"); map.gm.drawEnabled("marker"); // Or like this: map.gm.enableMode("draw", "marker"); map.gm.disableMode("draw", "marker"); map.gm.toggleMode("draw", "marker"); map.gm.isModeEnabled("draw", "marker"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :---------- | :------ | :-------------------------------------- | :------------------------ | | `gm:create` | `event` | Fired during marker drawing operations. | `map`, `shape`, `feature` | ## Behavior Marker drawing operates by: 1. Clicking on the map to place a marker 2. The marker is immediately placed at the clicked location ## Live Draw Marker Example --- ## Draw Polygon > Draw polygons on the map by clicking to place vertices and closing the shape. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-polygon # Draw Polygon Mode Polygon Mode allows you to create polygons on the map by clicking to add vertices and closing the shape. You can enable Draw Polygon Mode like this: ```js map.gm.enableDraw('polygon'); map.gm.disableDraw(); map.gm.toggleDraw('polygon'); map.gm.drawEnabled('polygon'); // Or like this: map.gm.enableMode('draw', 'polygon'); map.gm.disableMode('draw', 'polygon'); map.gm.toggleMode('draw', 'polygon'); map.gm.isModeEnabled('draw', 'polygon'); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :-------------- | :----- | :----------------------------------------------- |:--------------------------| | `gm:drawstart` | `event`| Fired when polygon drawing starts. | `map`, `shape` | | `gm:create` | `event`| Fired when a polygon is created. | `map`, `shape`, `feature` | ## Behavior Polygon drawing operates by: 1. Clicking on the map to place the first vertex 2. Moving the mouse to see a preview of the polygon segment 3. Clicking to add additional vertices 4. Closing the polygon by clicking on the first vertex ## Live Draw Polygon Example --- ## Draw Rectangle > Draw rectangles on the map by clicking two corner points. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-rectangle # Draw Rectangle Mode Rectangle Mode allows you to create rectangles on the map by clicking and dragging to define opposite corners. You can enable Draw Rectangle Mode like this: ```js map.gm.enableDraw('rectangle'); map.gm.disableDraw(); map.gm.toggleDraw('rectangle'); map.gm.drawEnabled('rectangle'); // Or like this: map.gm.enableMode('draw', 'rectangle'); map.gm.disableMode('draw', 'rectangle'); map.gm.toggleMode('draw', 'rectangle'); map.gm.isModeEnabled('draw', 'rectangle'); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :-------------- | :----- | :----------------------------------------------- |:--------------------------| | `gm:drawstart` | `event`| Fired when rectangle drawing starts. | `map`, `shape` | | `gm:create` | `event`| Fired when a rectangle is created. | `map`, `shape`, `feature` | ## Behavior Rectangle drawing operates by: 1. Clicking and holding on the map to set the first corner 2. Dragging to define the rectangle dimensions 3. Releasing the mouse button to complete the rectangle ## Live Draw Rectangle Example --- ## Draw Text Marker > Add text labels to the map by clicking and entering custom text. **URL:** https://geoman.io/docs/maplibre/draw-modes/draw-text_marker # Draw Text Marker Mode Text Marker Mode allows you to add text labels to the map by clicking on desired locations and entering custom text. You can enable Draw Text Marker Mode like this: ```js map.gm.enableDraw("text_marker"); map.gm.disableDraw(); map.gm.toggleDraw("text_marker"); map.gm.drawEnabled("text_marker"); // Or like this: map.gm.enableMode("draw", "text_marker"); map.gm.disableMode("draw", "text_marker"); map.gm.toggleMode("draw", "text_marker"); map.gm.isModeEnabled("draw", "text_marker"); ``` The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :------ | :------------------------------------- | :------------------------ | | `gm:drawstart` | `event` | Fired when text marker drawing starts. | `map`, `shape` | | `gm:create` | `event` | Fired when a text marker is created. | `map`, `shape`, `feature` | ## Behavior Text marker drawing operates by: 1. Clicking on the map to place the text marker 2. A text input field appears where you can enter your text 3. Click outside the text field to complete the marker 4. The text marker is placed at the clicked location with the entered text ## Live Draw Text Marker Example --- ## Add Hole ⭐ > Punch an interior ring (hole) into a selected polygon or multipolygon. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-add_hole # Add Hole Mode `add_hole` punches an interior ring (a hole) into a polygon or multipolygon. It is a **selection-gated** mode: select the target polygon first (see [Select Mode](/edit-modes/edit-select)), then the Add Hole control becomes enabled. With the mode active, draw a closed ring **inside** the selected polygon by clicking each vertex. A ring drawn outside the polygon is rejected (the geometry is left untouched and a `gm:operation_rejected` event fires with reason `not_contained`). ```js map.gm.enableMode('edit', 'add_hole'); map.gm.disableMode('edit', 'add_hole'); map.gm.toggleMode('edit', 'add_hole'); map.gm.isModeEnabled('edit', 'add_hole'); ``` ## Programmatic API For host-driven flows you can add a hole by id with the `geoman.edit` façade — it runs the same operation and fires the same events, without any toolbar interaction: ```js // ring: an array of [lng, lat] positions forming a closed ring inside the feature const result = await map.gm.edit.addHole('subedit-polygon', [ [-4, 51], [-2, 51], [-2, 52], [-4, 52], [-4, 51], ]); if (result.ok) { console.log('Updated feature:', result.feature); } else { console.warn('Rejected:', result.reason); // e.g. 'not_contained' } ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'add_hole')` | `Promise` | Enables Add Hole Mode. | | `disableMode('edit', 'add_hole')` | `Promise` | Disables Add Hole Mode. | | `toggleMode('edit', 'add_hole')` | `Promise` | Toggles Add Hole Mode. | | `isModeEnabled('edit', 'add_hole')` | `Boolean` | Returns `true` if Add Hole Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :---------------------- | :------ | :--------------------------------------------------- | :-------------------------- | | `gm:add_hole` | `event` | Fired when a hole is added to a feature. | `map`, `feature`, `shape` | | `gm:operation_rejected` | `event` | Fired when the operation is rejected. | `map`, `mode`, `reason` | ```js map.on('gm:add_hole', (event) => { console.log('Hole added to:', event.feature?.id); }); ``` ## Live Add Hole Example Click the polygon to select it, activate **Add Hole**, then click vertices to draw a ring inside it and close the ring on the first point. --- ## Add Part ⭐ > Append a disjoint part to a selected polygon, promoting it to a multipolygon. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-add_part # Add Part Mode `add_part` appends a new part to a polygon or multipolygon. Adding a part to a plain `Polygon` promotes it to a `MultiPolygon`. It is a **selection-gated** mode: select the target feature first (see [Select Mode](/edit-modes/edit-select)), then activate Add Part and draw the new part by clicking each vertex. ```js map.gm.enableMode('edit', 'add_part'); map.gm.disableMode('edit', 'add_part'); map.gm.toggleMode('edit', 'add_part'); map.gm.isModeEnabled('edit', 'add_part'); ``` ## Programmatic API ```js // partRings: an array of rings ([[ [lng, lat], ... ]]) describing the new part const result = await map.gm.edit.addPart( 'subedit-polygon', [[[0, 50], [2, 50], [2, 52], [0, 52], [0, 50]]], { allowOverlap: false }, // optional; reject parts that overlap the existing geometry ); if (result.ok) { console.log('Feature is now a multipolygon:', result.feature); } else { console.warn('Rejected:', result.reason); // e.g. 'overlap' } ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'add_part')` | `Promise` | Enables Add Part Mode. | | `disableMode('edit', 'add_part')` | `Promise` | Disables Add Part Mode. | | `toggleMode('edit', 'add_part')` | `Promise` | Toggles Add Part Mode. | | `isModeEnabled('edit', 'add_part')` | `Boolean` | Returns `true` if Add Part Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :---------------------- | :------ | :--------------------------------------------------- | :-------------------------- | | `gm:add_part` | `event` | Fired when a part is appended to a feature. | `map`, `feature`, `shape` | | `gm:operation_rejected` | `event` | Fired when the operation is rejected. | `map`, `mode`, `reason` | ```js map.on('gm:add_part', (event) => { console.log('Part added to:', event.feature?.id); }); ``` ## Live Add Part Example Click the polygon to select it, activate **Add Part**, then draw a separate polygon next to it — the feature is promoted to a multipolygon. --- ## Edit > Edit feature vertices to change the geometry shape. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-change # Edit Mode You can handle Edit Mode for all layers on a map like this: ```js map.gm.enableGlobalEditMode(); map.gm.disableGlobalEditMode(); map.gm.toggleGlobalEditMode(); map.gm.globalEditModeEnabled(); // Or like this: map.gm.enableMode('edit', 'change'); map.gm.disableMode('edit', 'change'); map.gm.toggleMode('edit', 'change'); map.gm.isModeEnabled('edit', 'change'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------------------------------------| :-------- | :-----------------------------| | `enableGlobalEditMode()` | - | Enables global Edit Mode. | | `disableGlobalEditMode()` | - | Disables global Edit Mode. | | `toggleGlobalEditMode()` | - | Toggles global Edit Mode. | | `globalEditModeEnabled()` | `Boolean` | Returns `true` if global Edit Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | |:-------------------------| :----- | :------------------------------------------- | :-------------------------| | `gm:editstart` | `event`| Fired when a layer starts being changed. | `map`, `feature`, `shape` | | `gm:edit` | `event`| Fired when a layer is being changed. | `map`, `feature`, `shape` | | `gm:editend` | `event`| Fired when a layer stops being changed. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globaleditmodetoggled` | `event` | Fired when Edit Mode is toggled. | `enabled`, `map` | You can also listen to specific Edit Mode events on the map instance like this: ```js map.on('gm:globaleditmodetoggled', (event) => { console.log(event); }); ``` ## Live Edit Example --- ## Copy ⭐ > Copy features to duplicate them on the map. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-copy # Copy Mode You can handle Copy Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'copy'); map.gm.disableMode('edit', 'copy'); map.gm.toggleMode('edit', 'copy'); map.gm.isModeEnabled('edit', 'copy'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:--------------------------------------------|:----------|:----------------------------------------------------------------------| | `enableMode('edit', 'copy')` | - | Enables global Copy Mode. | | `disableMode('edit', 'copy')` | - | Disables global Copy Mode. | | `toggleMode('edit', 'copy')` | - | Toggles global Copy Mode. | | `isModeEnabled('edit', 'copy')` | `Boolean` | Returns `true` if global Copy Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:copystart` | `event`| Fired when a layer starts being copied. | `map`, `feature`, `shape` | | `gm:copy` | `event`| Fired when a layer is being copied. | `map`, `feature`, `shape` | | `gm:copyend` | `event`| Fired when a layer is copied. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globalcopymodetoggled` | `event` | Fired when Copy Mode is toggled. | `enabled`, `map` | You can also listen to specific Copy Mode events on the map instance like this: ```js map.on('gm:globalcopymodetoggled', (event) => { console.log(event); }); ``` ## Behavior Copy mode operates in two ways: 1. **Sticky Mode Enabled**: A shape can be selected once and then copied multiple times on each subsequent click. 2. **Sticky Mode Disabled**: A shape is copied when a user drags it to a new location. ## Live Copy Example --- ## Cut > Cut and subtract geometry from existing features. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-cut # Cut Mode Cut mode allows you to cut holes in existing shapes or remove parts of them. You can handle Cut Mode for all layers on a map like this: ```js map.gm.enableGlobalCutMode(); map.gm.disableGlobalCutMode(); map.gm.toggleGlobalCutMode(); map.gm.globalCutModeEnabled(); // Or like this: map.gm.enableMode('edit', 'cut'); map.gm.disableMode('edit', 'cut'); map.gm.toggleMode('edit', 'cut'); map.gm.isModeEnabled('edit', 'cut'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------------------------------------| :-------- | :-----------------------------| | `enableGlobalCutMode()` | - | Enables global Cut Mode. | | `disableGlobalCutMode()` | - | Disables global Cut Mode. | | `toggleGlobalCutMode()` | - | Toggles global Cut Mode. | | `globalCutModeEnabled()` | `Boolean` | Returns `true` if global Cut Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:cutstart` | `event`| Fired when a cut operation starts. | `map`, `feature`, `shape` | | `gm:cut` | `event`| Fired during the cut operation. | `map`, `feature`, `shape` | | `gm:cutend` | `event`| Fired when a cut operation is completed. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :----------------------- | :----- | :------------------------------- | :--------------------- | | `gm:globalcutmodetoggled` | `event` | Fired when Cut Mode is toggled. | `enabled`, `map` | You can also listen to specific Cut Mode events on the map instance like this: ```js map.on('gm:globalcutmodetoggled', (event) => { console.log(event); }); ``` ## Behavior The Cut Mode operates by: 1. Selecting a target shape to cut 2. Drawing a polygon that defines the area to be removed 3. The intersection of the drawn polygon with the target shape will be removed ## Live Cut Example --- ## Remove > Delete features from the map. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-delete # Remove Mode Remove Mode allows you to delete features from the map by clicking on them. You can handle Remove Mode for all layers on a map like this: ```js map.gm.enableGlobalRemovalMode(); map.gm.disableGlobalRemovalMode(); map.gm.toggleGlobalRemovalMode(); map.gm.globalRemovalModeEnabled(); // Or like this: map.gm.enableMode('edit', 'delete'); map.gm.disableMode('edit', 'delete'); map.gm.toggleMode('edit', 'delete'); map.gm.isModeEnabled('edit', 'delete'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------------------------------------| :-------- | :-----------------------------| | `enableGlobalRemovalMode()` | - | Enables global Remove Mode. | | `disableGlobalRemovalMode()` | - | Disables global Remove Mode. | | `toggleGlobalRemovalMode()` | - | Toggles global Remove Mode. | | `globalRemovalModeEnabled()` | `Boolean` | Returns `true` if global Remove Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:remove` | `event`| Fired when a feature is removed. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globalremovalmodetoggled` | `event` | Fired when Remove Mode is toggled. | `enabled`, `map` | You can also listen to specific Remove Mode events on the map instance like this: ```js map.on('gm:globalremovalmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Remove mode operates by: 1. Clicking on a feature to remove it from the map 2. The feature and all its associated markers will be deleted immediately 3. The removal cannot be undone ## Live Remove Example --- ## Difference ⭐ > Compute the geometric difference between overlapping features. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-difference # Difference Mode Difference Mode allows you to subtract one polygon from another. The original feature will be replaced with the resulting difference feature. You can handle Difference Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'difference'); map.gm.disableMode('edit', 'difference'); map.gm.toggleMode('edit', 'difference'); map.gm.isModeEnabled('edit', 'difference'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:-------------------------------------------------|:----------|:----------------------------------------------------------------------------| | `enableMode('edit', 'difference')` | - | Enables global Difference Mode. | | `disableMode('edit', 'difference')` | - | Disables global Difference Mode. | | `toggleMode('edit', 'difference')` | - | Toggles global Difference Mode. | | `isModeEnabled('edit', 'difference')` | `Boolean` | Returns `true` if global Difference Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :----------------- | :----- | :------------------------------------------- | :----------------------------------------------------- | | `gm:difference` | `event`| Fired during the difference operation. | `map`, `originalFeatures`, `feature` | | `gm:create` | `event`| Fired during the difference operation. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :---------------------------- | :------ | :------------------------------------- | :--------------------- | | `gm:globaldifferencemodetoggled` | `event` | Fired when Difference Mode is toggled. | `enabled`, `map` | You can also listen to specific Difference Mode events on the map instance like this: ```js map.on('gm:globaldifferencemodetoggled', (event) => { console.log(event); }); ``` ## Behavior Difference mode operates by: 1. Selecting a target polygon (the one to subtract from) 2. Drawing or selecting another polygon that will be subtracted from the target 3. The original feature is replaced with the resulting difference feature ### Supported Shapes Difference mode supports the following feature types: - Polygons - Rectangles - Circles ## Live Difference Example --- ## Drag > Drag and reposition features on the map. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-drag # Drag Mode You can handle Drag Mode for all layers on a map like this: ```js map.gm.enableGlobalDragMode(); map.gm.disableGlobalDragMode(); map.gm.toggleGlobalDragMode(); map.gm.globalDragModeEnabled(); // Or like this: map.gm.enableMode('edit', 'drag'); map.gm.disableMode('edit', 'drag'); map.gm.toggleMode('edit', 'drag'); map.gm.isModeEnabled('edit', 'drag'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------------------------------------| :-------- | :-----------------------------| | `enableGlobalDragMode()` | - | Enables global Drag Mode. | | `disableGlobalDragMode()` | - | Disables global Drag Mode. | | `toggleGlobalDragMode()` | - | Toggles global Drag Mode. | | `globalDragModeEnabled()` | `Boolean` | Returns `true` if global Drag Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:dragstart` | `event`| Fired when a layer starts being dragged. | `map`, `feature`, `shape` | | `gm:drag` | `event`| Fired when a layer is dragged. | `map`, `feature`, `shape` | | `gm:dragend` | `event`| Fired when a layer stops being dragged. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :----------------------- | :----- | :------------------------------- | :--------------------- | | `gm:globaldragmodetoggled` | `event` | Fired when Drag Mode is toggled. | `enabled`, `map` | You can also listen to specific Drag Mode events on the map instance like this: ```js map.on('gm:globaldragmodetoggled', (event) => { console.log(event); }); ``` ## Live Drag Example --- ## Explode ⭐ > Split a selected multipolygon into separate single-polygon features. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-explode # Explode Mode `explode` splits a multipolygon into separate single-polygon features. It is a **selection-gated** mode and only applies to multipolygons — select a multipolygon first (see [Select Mode](/edit-modes/edit-select)) and the Explode control becomes enabled. The control stays disabled for a plain `Polygon`, since there is nothing to split (`requiresSelection` context `multipolygon`). When a multipolygon is exploded, the original feature is removed (`gm:remove`) and one new feature is created per part (`gm:create`). ```js map.gm.enableMode('edit', 'explode'); map.gm.disableMode('edit', 'explode'); map.gm.toggleMode('edit', 'explode'); map.gm.isModeEnabled('edit', 'explode'); ``` ## Programmatic API ```js const result = await map.gm.edit.explode('subedit-polygon'); if (result.ok) { console.log('New features:', result.features); // one per part } else { console.warn('Rejected:', result.reason); // e.g. 'no_target' } ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:-----------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'explode')` | `Promise` | Enables Explode Mode. | | `disableMode('edit', 'explode')` | `Promise` | Disables Explode Mode. | | `toggleMode('edit', 'explode')` | `Promise` | Toggles Explode Mode. | | `isModeEnabled('edit', 'explode')` | `Boolean` | Returns `true` if Explode Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :----------- | :------ | :--------------------------------------------------- | :--------------------------- | | `gm:explode` | `event` | Fired when a multipolygon is exploded. | `map`, `features`, `shape` | | `gm:create` | `event` | Fired for each new single-polygon feature created. | `map`, `feature`, `shape` | | `gm:remove` | `event` | Fired when the original multipolygon is removed. | `map`, `feature`, `shape` | ```js map.on('gm:explode', (event) => { console.log('Exploded into:', event.features?.map((f) => f.id)); }); ``` ## Live Explode Example This feature is a two-part multipolygon. Click it to select it, then activate **Explode** to split it into two independent polygons. --- ## Lasso ⭐ > Select multiple features by drawing a freehand lasso selection area. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-lasso # Lasso Mode Lasso Mode allows you to select multiple features by drawing a polygon around them. The selection behavior can be configured to either select features that intersect with or are contained within the lasso polygon. You can handle Lasso Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'lasso'); map.gm.disableMode('edit', 'lasso'); map.gm.toggleMode('edit', 'lasso'); map.gm.isModeEnabled('edit', 'lasso'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:--------------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'lasso')` | - | Enables global Lasso Mode. | | `disableMode('edit', 'lasso')` | - | Disables global Lasso Mode. | | `toggleMode('edit', 'lasso')` | - | Toggles global Lasso Mode. | | `isModeEnabled('edit', 'lasso')` | `Boolean` | Returns `true` if global Lasso Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :-------------- | :----- | :------------------------------------------- | :-------------------| | `gm:lasso` | `event`| Fired during lasso selection. | `map`, `features` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globallassomodetoggled` | `event` | Fired when Lasso Mode is toggled. | `enabled`, `map` | You can also listen to specific Lasso Mode events on the map instance like this: ```js map.on('gm:globallassomodetoggled', (event) => { console.log(event); }); ``` ## Behavior Lasso mode operates in three selection modes: - **Append**: Add selected features to the current selection - **Subtract**: Remove selected features from the current selection - **Reset**: Clear current selection and create a new selection The selection type can be configured to either: - **Intersects**: Select features that intersect with the lasso polygon - **Contains**: Select only features that are completely contained within the lasso polygon ## Live Lasso Example --- ## Line Simplification ⭐ > Simplify polyline and polygon vertices to reduce complexity. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-line_simplification # Line Simplification Mode Line Simplification Mode allows you to simplify line geometry by removing vertices. This is useful for smoothing complex lines or reducing the number of points in a line. You can handle Line Simplification Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'line_simplification'); map.gm.disableMode('edit', 'line_simplification'); map.gm.toggleMode('edit', 'line_simplification'); map.gm.isModeEnabled('edit', 'line_simplification'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:-------------------------------------------------------|:----------|:-------------------------------------------------------------------------------------| | `enableMode('edit', 'line_simplification')` | - | Enables global Line Simplification Mode. | | `disableMode('edit', 'line_simplification')` | - | Disables global Line Simplification Mode. | | `toggleMode('edit', 'line_simplification')` | - | Toggles global Line Simplification Mode. | | `isModeEnabled('edit', 'line_simplification')` | `Boolean` | Returns `true` if global Line Simplification Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:line_simplification` | `event`| Fired during simplification. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------------------- | :------ | :--------------------------------------------- | :--------------------- | | `gm:globallinesimplificationmodetoggled` | `event` | Fired when Line Simplification Mode is toggled. | `enabled`, `map` | You can also listen to specific Line Simplification Mode events on the map instance like this: ```js map.on('gm:globalline_simplificationmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Line Simplification mode operates by: 1. Selecting a line feature to simplify 2. Clicking on vertices you want to remove from the line 3. The line geometry will be updated with the selected vertices removed ### Supported Shapes Line Simplification mode supports the following feature types: - Lines - Polygons ## Live Line Simplification Example --- ## Merge Parts ⭐ > Merge several selected polygon/multipolygon features into a single multipolygon. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-merge_parts # Merge Parts Mode `merge_parts` combines several polygon/multipolygon features into a single `MultiPolygon` feature. Unlike [Union](/edit-modes/edit-union), merge does **not** dissolve shared boundaries — each input becomes a part of the resulting multipolygon. It is a **selection-gated** mode that requires **two or more** selected features: select them first (see [Select Mode](/edit-modes/edit-select)), then activate Merge Parts. When features are merged, the originals are removed (`gm:remove`) and a single combined feature is created (`gm:create`). ```js map.gm.enableMode('edit', 'merge_parts'); map.gm.disableMode('edit', 'merge_parts'); map.gm.toggleMode('edit', 'merge_parts'); map.gm.isModeEnabled('edit', 'merge_parts'); ``` ## Programmatic API ```js // Merge two (or more) features by id into one multipolygon const result = await map.gm.edit.merge(['poly-a', 'poly-b']); if (result.ok) { console.log('Merged feature:', result.feature); } else { console.warn('Rejected:', result.reason); } ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:---------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'merge_parts')` | `Promise` | Enables Merge Parts Mode. | | `disableMode('edit', 'merge_parts')` | `Promise` | Disables Merge Parts Mode. | | `toggleMode('edit', 'merge_parts')` | `Promise` | Toggles Merge Parts Mode. | | `isModeEnabled('edit', 'merge_parts')` | `Boolean` | Returns `true` if Merge Parts Mode is enabled. `false` when disabled.| The following events are available on a map instance: | Event | Params | Description | Output | | :--------------- | :------ | :--------------------------------------------------- | :--------------------------- | | `gm:merge_parts` | `event` | Fired when features are merged. | `map`, `feature`, `shape` | | `gm:create` | `event` | Fired for the new combined multipolygon feature. | `map`, `feature`, `shape` | | `gm:remove` | `event` | Fired for each original feature that is removed. | `map`, `feature`, `shape` | ```js map.on('gm:merge_parts', (event) => { console.log('Merged into:', event.feature?.id); }); ``` ## Live Merge Parts Example Two separate polygons are loaded. With the **Select** tool, click the first polygon, then **Ctrl/Cmd-click** the second to add it to the selection. With both selected, activate **Merge Parts** to combine them into a single multipolygon. --- ## Remove Ring ⭐ > Remove a hole or a whole part from a selected polygon or multipolygon. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-remove_ring # Remove Ring Mode `remove_ring` removes a single ring from a polygon or multipolygon — either an interior ring (a **hole**) or a whole **part** of a multipolygon (a part is only removable when the feature has more than one). It is a **selection-gated** mode: select the feature first (see [Select Mode](/edit-modes/edit-select)), then activate Remove Ring and click the ring you want to remove. ```js map.gm.enableMode('edit', 'remove_ring'); map.gm.disableMode('edit', 'remove_ring'); map.gm.toggleMode('edit', 'remove_ring'); map.gm.isModeEnabled('edit', 'remove_ring'); ``` ## Programmatic API ```js // Remove the first hole of the first part const result = await map.gm.edit.removeRing('subedit-polygon', { kind: 'hole', partIndex: 0, ringIndex: 0, }); // Or remove a whole part of a multipolygon // await map.gm.edit.removeRing('subedit-polygon', { kind: 'part', partIndex: 1 }); if (result.ok) { console.log('Updated feature:', result.feature); } else { console.warn('Rejected:', result.reason); // e.g. 'no_target' } ``` The `ringTarget` is one of: ```ts type RemovableRingTarget = | { kind: 'hole'; partIndex: number; ringIndex: number } | { kind: 'part'; partIndex: number }; ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:---------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'remove_ring')` | `Promise` | Enables Remove Ring Mode. | | `disableMode('edit', 'remove_ring')` | `Promise` | Disables Remove Ring Mode. | | `toggleMode('edit', 'remove_ring')` | `Promise` | Toggles Remove Ring Mode. | | `isModeEnabled('edit', 'remove_ring')` | `Boolean` | Returns `true` if Remove Ring Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :---------------------- | :------ | :--------------------------------------------------- | :-------------------------- | | `gm:remove_ring` | `event` | Fired when a ring or part is removed from a feature. | `map`, `feature`, `shape` | | `gm:operation_rejected` | `event` | Fired when the operation is rejected. | `map`, `mode`, `reason` | ```js map.on('gm:remove_ring', (event) => { console.log('Ring removed from:', event.feature?.id); }); ``` ## Live Remove Ring Example This polygon already has a hole. Click the polygon to select it, activate **Remove Ring**, then click the inner ring to remove it. --- ## Rotate > Rotate features around their center point. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-rotate # Rotate Mode You can handle Rotate Mode for all layers on a map like this: ```js map.gm.enableGlobalRotateMode(); map.gm.disableGlobalRotateMode(); map.gm.toggleGlobalRotateMode(); map.gm.globalRotateModeEnabled(); // Or like this: map.gm.enableMode('edit', 'rotate'); map.gm.disableMode('edit', 'rotate'); map.gm.toggleMode('edit', 'rotate'); map.gm.isModeEnabled('edit', 'rotate'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:------------------------------------------------------------------| :-------- | :-----------------------------| | `enableGlobalRotateMode()` | - | Enables global Rotate Mode. | | `disableGlobalRotateMode()` | - | Disables global Rotate Mode. | | `toggleGlobalRotateMode()` | - | Toggles global Rotate Mode. | | `globalRotateModeEnabled()` | `Boolean` | Returns `true` if global Rotate Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :--------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:rotatestart` | `event`| Fired when a layer starts being rotated. | `map`, `feature`, `shape` | | `gm:rotate` | `event`| Fired when a layer is being rotated. | `map`, `feature`, `shape` | | `gm:rotateend` | `event`| Fired when a layer stops being rotated. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globalrotatemodetoggled` | `event` | Fired when Rotate Mode is toggled. | `enabled`, `map` | You can also listen to specific Rotate Mode events on the map instance like this: ```js map.on('gm:globalrotatemodetoggled', (event) => { console.log(event); }); ``` ## Live Rotate Example --- ## Scale ⭐ > Scale features to resize them proportionally. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-scale # Scale Mode You can handle Scale Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'scale'); map.gm.disableMode('edit', 'scale'); map.gm.toggleMode('edit', 'scale'); map.gm.isModeEnabled('edit', 'scale'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:--------------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'scale')` | - | Enables global Scale Mode. | | `disableMode('edit', 'scale')` | - | Disables global Scale Mode. | | `toggleMode('edit', 'scale')` | - | Toggles global Scale Mode. | | `isModeEnabled('edit', 'scale')` | `Boolean` | Returns `true` if global Scale Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :--------------- | :----- | :------------------------------------------- | :-------------------------| | `gm:scalestart` | `event`| Fired when a layer starts being scaled. | `map`, `feature`, `shape` | | `gm:scale` | `event`| Fired when a layer is being scaled. | `map`, `feature`, `shape` | | `gm:scaleend` | `event`| Fired when a layer stops being scaled. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globalscalemodetoggled` | `event` | Fired when Scale Mode is toggled. | `enabled`, `map` | You can also listen to specific Scale Mode events on the map instance like this: ```js map.on('gm:globalscalemodetoggled', (event) => { console.log(event); }); ``` ## Supported Features Scale mode supports the following feature types: - Polygons - Rectangles - Lines - Circles When scaling, the feature will maintain its proportions relative to its centroid point. For rectangles, the scaling operation preserves the rectangular shape while adjusting its size. ## Live Scale Example --- ## Select ⭐ > Click features to drive the global selection, which gates the sub-editing modes (add hole, add part, remove ring, explode, merge parts). **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-select # Select Mode `select` is the click-to-select tool. With it active, clicking a feature selects it and outlines it; **Ctrl/Cmd-click** toggles a feature in a multi-feature selection; clicking empty map space clears the selection. This **global selection** is what gates the sub-editing modes ([Add Hole](/edit-modes/edit-add_hole), [Add Part](/edit-modes/edit-add_part), [Remove Ring](/edit-modes/edit-remove_ring), [Explode](/edit-modes/edit-explode) and [Merge Parts](/edit-modes/edit-merge_parts)) — those controls stay disabled until the selection they require is in place. You can handle Select Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'select'); map.gm.disableMode('edit', 'select'); map.gm.toggleMode('edit', 'select'); map.gm.isModeEnabled('edit', 'select'); ``` ## Driving the selection programmatically The global selection also lives on the Features API, so a host application can set or clear it directly instead of relying on user clicks: ```js // Select one or more features by id map.gm.features.setSelection(['poly-a', 'poly-b']); // Read the current selection (a Set of feature ids) console.log(map.gm.features.selection); // Clear the selection map.gm.features.clearSelection(); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:----------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'select')` | `Promise` | Enables Select Mode. | | `disableMode('edit', 'select')` | `Promise` | Disables Select Mode. | | `toggleMode('edit', 'select')` | `Promise` | Toggles Select Mode. | | `isModeEnabled('edit', 'select')` | `Boolean` | Returns `true` if Select Mode is enabled. `false` when disabled. | The following event is fired whenever the global selection changes: | Event | Params | Description | Output | | :------------- | :------ | :----------------------------------- | :---------------- | | `gm:selection` | `event` | Fired when the selection changes. | `selection`, `map`| ```js map.on('gm:selection', (event) => { console.log('Selected feature ids:', event.selection); }); ``` ## Live Select Example Click a polygon to select it (it gets outlined); click the empty background to clear. --- ## Split ⭐ > Split features into multiple parts using a cutting line. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-split # Split Mode Split Mode allows you to draw a line that splits all underlying Polygons and Lines. When a feature is split, the original feature will be replaced with the resulting split features. You can handle Split Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'split'); map.gm.disableMode('edit', 'split'); map.gm.toggleMode('edit', 'split'); map.gm.isModeEnabled('edit', 'split'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:--------------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'split')` | - | Enables global Split Mode. | | `disableMode('edit', 'split')` | - | Disables global Split Mode. | | `toggleMode('edit', 'split')` | - | Toggles global Split Mode. | | `isModeEnabled('edit', 'split')` | `Boolean` | Returns `true` if global Split Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :-------------- | :----- | :------------------------------------------- | :------------------------------------------| | `gm:split` | `event`| Fired during the split operation. | `map`, `originalFeature`, `features` | | `gm:create` | `event`| Fired during the split operation. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globalsplitmodetoggled` | `event` | Fired when Split Mode is toggled. | `enabled`, `map` | You can also listen to specific Split Mode events on the map instance like this: ```js map.on('gm:globalsplitmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Split mode operates by: 1. Drawing a line across features you want to split 2. The line will split any intersecting polygons or lines into separate features 3. Original features are replaced with the resulting split features ## Live Split Example --- ## Union ⭐ > Merge overlapping features into a single combined geometry. **URL:** https://geoman.io/docs/maplibre/edit-modes/edit-union # Union Mode Union Mode allows you to merge multiple polygons into a single polygon. The original features will be replaced with the resulting union feature. You can handle Union Mode for all layers on a map like this: ```js map.gm.enableMode('edit', 'union'); map.gm.disableMode('edit', 'union'); map.gm.toggleMode('edit', 'union'); map.gm.isModeEnabled('edit', 'union'); ``` The following methods are available on `map.gm`: | Method | Returns | Description | |:--------------------------------------------|:----------|:-----------------------------------------------------------------------| | `enableMode('edit', 'union')` | - | Enables global Union Mode. | | `disableMode('edit', 'union')` | - | Disables global Union Mode. | | `toggleMode('edit', 'union')` | - | Toggles global Union Mode. | | `isModeEnabled('edit', 'union')` | `Boolean` | Returns `true` if global Union Mode is enabled. `false` when disabled. | The following events are available on a map instance: | Event | Params | Description | Output | | :-------------- | :----- | :------------------------------------------- | :------------------------------------------| | `gm:union` | `event`| Fired during the union operation. | `map`, `originalFeatures`, `feature` | | `gm:create` | `event`| Fired during the union operation. | `map`, `feature`, `shape` | The following events are available on a map instance: | Event | Params | Description | Output | | :------------------------- | :------ | :--------------------------------- | :--------------------- | | `gm:globalunionmodetoggled` | `event` | Fired when Union Mode is toggled. | `enabled`, `map` | You can also listen to specific Union Mode events on the map instance like this: ```js map.on('gm:globalunionmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Union mode operates by: 1. Selecting multiple polygons to merge 2. The selected polygons will be combined into a single polygon 3. Original features are replaced with the resulting union feature ### Supported Shapes Union mode supports the following feature types: - Polygons - Rectangles - Circles ## Live Union Example --- ## Geoman Events > Event system reference for listening to draw, edit, drag, cut, rotate, and other geometry interactions. **URL:** https://geoman.io/docs/maplibre/events Geoman provides a rich event system that allows you to listen to various interactions with the map. Events can be listened to either individually or globally. ## Event Listening Methods ### Individual Event Listening With MapLibre GL JS 6, subscribe through `geoman.mapAdapter` for typed `gm:*` events. MapLibre's native event-name overloads cover its own events. The raw map event API still works at runtime, but TypeScript callers should use this adapter rather than casting custom event names to native MapLibre names. The examples below assume an initialized `geoman` instance. You can listen to specific events using the Geoman map adapter's typed `on` method: ```typescript geoman.mapAdapter.on('gm:create', (event) => { console.log('Feature created:', event); }); ``` ### Global Event Listening To listen to all Geoman events, use the global events listener: ```typescript geoman.setGlobalEventsListener((event) => { console.log('Event:', event); }); // Remove the global listener geoman.setGlobalEventsListener(null); ``` ## Event Categories ### Mode Toggle Events These events fire when different modes are toggled on/off: ```typescript // Drawing mode geoman.mapAdapter.on('gm:globaldrawmodetoggled', (event) => { console.log('Draw mode toggled:', event.enabled); }); // Edit mode geoman.mapAdapter.on('gm:globaleditmodetoggled', (event) => { console.log('Edit mode toggled:', event.enabled); }); // Remove mode geoman.mapAdapter.on('gm:globaldeletemodetoggled', (event) => { console.log('Remove mode toggled:', event.enabled); }); // Rotate mode geoman.mapAdapter.on('gm:globalrotatemodetoggled', (event) => { console.log('Rotate mode toggled:', event.enabled); }); // Drag mode geoman.mapAdapter.on('gm:globaldragmodetoggled', (event) => { console.log('Drag mode toggled:', event.enabled); }); // Cut mode geoman.mapAdapter.on('gm:globalcutmodetoggled', (event) => { console.log('Cut mode toggled:', event.enabled); }); // Snapping mode geoman.mapAdapter.on('gm:globalsnappingmodetoggled', (event) => { console.log('Snapping mode toggled:', event.enabled); }); ``` ### Drawing Events Events related to drawing features: ```typescript // Listen to all draw events // Use setGlobalEventsListener() for internal drawing notifications. // Feature creation geoman.mapAdapter.on('gm:create', (event: FeatureCreatedFwdEvent) => { console.log('Feature created:', event); }); ``` ### Edit Events Events related to editing features: ```typescript // Listen to all edit events // Use setGlobalEventsListener() for internal editing notifications. // Edit start geoman.mapAdapter.on('gm:editstart', (event: FeatureEditStartFwdEvent) => { console.log('Edit started:', event); }); // Edit end geoman.mapAdapter.on('gm:editend', (event: FeatureEditEndFwdEvent) => { console.log('Edit ended:', event); }); ``` ### Remove Events Events for feature removal: ```typescript geoman.mapAdapter.on('gm:remove', (event: FeatureRemovedFwdEvent) => { console.log('Feature removed:', event); }); ``` ### Rotate Events Events for rotating features: ```typescript // Listen to all rotate events geoman.mapAdapter.on('gm:rotate', (event: FeatureUpdatedFwdEvent) => { console.log('Rotate event:', event); }); // Rotation start geoman.mapAdapter.on('gm:rotatestart', (event: FeatureEditStartFwdEvent) => { console.log('Rotation started:', event); }); // Rotation end geoman.mapAdapter.on('gm:rotateend', (event: FeatureEditEndFwdEvent) => { console.log('Rotation ended:', event); }); ``` ### Drag Events Events for dragging features: ```typescript // Listen to all drag events geoman.mapAdapter.on('gm:drag', (event: FeatureUpdatedFwdEvent) => { console.log('Drag event:', event); }); // Drag start geoman.mapAdapter.on('gm:dragstart', (event: FeatureEditStartFwdEvent) => { console.log('Drag started:', event); }); // Drag end geoman.mapAdapter.on('gm:dragend', (event: FeatureEditEndFwdEvent) => { console.log('Drag ended:', event); }); ``` ### Cut Events Events for cutting features: ```typescript geoman.mapAdapter.on('gm:cut', (event: FeatureUpdatedFwdEvent) => { console.log('Feature cut:', event); }); ``` ### Helper Events Use `gm:globalsnappingmodetoggled` for snapping and `gm:geofencing_violation` for rejected containment/keep-out edits. Internal helper notifications are available through the global listener; avoid relying on private `_gm:*` names. ### Control Events The global listener includes control notifications as well as draw, edit, and helper events. Register one global listener and route the notifications in your application; calling `setGlobalEventsListener()` again replaces it. ## Complete Example This Vite example bundles MapLibre 6's module worker. See [Basics](/basics) for other bundlers and self-hosted worker assets. ```typescript import * as maplibregl from 'maplibre-gl'; import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'; import { Geoman } from '@geoman-io/maplibre-geoman-pro'; import 'maplibre-gl/dist/maplibre-gl.css'; import '@geoman-io/maplibre-geoman-pro/dist/maplibre-geoman.css'; maplibregl.setWorkerUrl(workerUrl); const map = new maplibregl.Map({ container: 'map', style: 'your-style-url', }); const geoman = new Geoman(map); geoman.mapAdapter.once('gm:loaded', () => { console.log('Geoman loaded'); }); geoman.mapAdapter.on('gm:create', (event) => { console.log('Created feature:', event.feature); }); geoman.mapAdapter.on('gm:editend', (event) => { console.log('Committed edit:', event.feature); }); geoman.setGlobalEventsListener((notification) => { console.log('Global notification:', notification); }); ``` ### Pro 0.11 lifecycle and compound edits - `gm:unloaded` fires when Geoman is destroyed. - `gm:geofencing_violation` exposes containment and intersection violations; `event.action` identifies the violation. - In-place programmatic operations on `geoman.edit` emit `gm:edit`, then `gm:editend`, so the same persistence handler can cover interactive edits. - `gm:explode`, `gm:merge_parts`, and `gm:split` always carry `originalFeatures` and `features` arrays. They accompany granular create/remove events: choose one persistence channel to avoid counting an operation twice. Set `settings.compoundLineageEvents: false` for a granular-only stream. - Multi-feature events honor `awaitDataUpdatesOnEvents` for every affected source. Errors thrown by one host listener are isolated from sibling listeners. ## Event Handler Details The event handler demonstrates how to extract and store useful information from Geoman events: 1. **Event History**: Events are stored in an array (`gmEvents`) for tracking and analysis 2. **Feature Information**: - `id`: Extracts the feature ID if present - `shape`: Captures the type of shape involved - `geojson`: Safely extracts the GeoJSON representation of features 3. **Event Metadata**: - `timestamp`: Adds a timestamp to each event - `type`: Records the event type - `enabled`: Tracks mode state for toggle events The `getGeoJson` helper function safely extracts GeoJSON data from features: - Uses try/catch to handle potential errors - Formats the JSON for readability using `JSON.stringify` with spacing - Returns a fallback message if extraction fails Example event data stored using the above event handler: ```typescript const event = { id: "feature-123", enabled: true, timestamp: "14:30:45", type: "gm:create", shape: "polygon", geojson: { "type": "Feature", "properties": { "shape": "polygon" }, "geometry": { "type": "Polygon", "coordinates": [/* ... */] } } } ``` ## Event Types Here are some common event payload types: ```typescript interface FeatureCreatedFwdEvent { name: 'gm:create'; shape: DrawModeName; feature: FeatureData; map: AnyMapInstance; } interface FeatureUpdatedFwdEvent { name: `gm:${FwdEditModeName}`; map: AnyMapInstance; shape?: FeatureShape; feature?: FeatureData; // The feature being edited if a single feature is being edited (e.g., during edit mode) features?: Array; // The features being edited if multiple features are involved (e.g., during cut mode/split mode) originalFeature?: FeatureData; originalFeatures?: Array; } interface GlobalEditToggledFwdEvent { name: `gm:global${FwdEditModeName}modetoggled`; enabled: boolean; map: AnyMapInstance; } ``` ## Best Practices 1. **Event Handler Organization**: Keep event handlers organized by category ```typescript const drawHandlers = { onCreate: (event) => { /* ... */ }, onUpdate: (event) => { /* ... */ }, }; const editHandlers = { onEditStart: (event) => { /* ... */ }, onEditEnd: (event) => { /* ... */ }, }; ``` 2. **Error Handling**: Always include error handling in event listeners ```typescript geoman.mapAdapter.on('gm:create', (event) => { try { // Process event } catch (error) { console.error('Error processing create event:', error); } }); ``` 3. **Cleanup**: Remove event listeners when they're no longer needed ```typescript const handler = (event) => { /* ... */ }; geoman.mapAdapter.on('gm:create', handler); // Later... geoman.mapAdapter.off('gm:create', handler); ``` 4. **Performance**: Be mindful of performance in high-frequency events ```typescript import { debounce } from 'lodash'; const debouncedHandler = debounce((event) => { // Process event... }, 100); geoman.mapAdapter.on('gm:drag', debouncedHandler); ``` --- ## MapLibre-Geoman Examples for React, Vue, Next.js and Vite > Runnable code examples for Geoman, with MapLibre-focused demos that map directly to Mapbox packages. **URL:** https://geoman.io/docs/maplibre/examples # MapLibre-Geoman Examples We have prepared a few examples to help you get started with Maplibre-Geoman. The examples use MapLibre package names, but the same API works for Mapbox variants (`@geoman-io/mapbox-geoman-free` / `@geoman-io/mapbox-geoman-pro`). These are available on GitHub in the [Maplibre-Geoman Examples Repository](https://github.com/geoman-io/maplibre-geoman-examples). ## Choose a starter - **Vite:** start here for a small, framework-independent drawing app. The [Vite tutorial](https://geoman.io/blog/using-maplibre-geoman-with-vite) explains initialization and GeoJSON export. - **React, Vue, Preact or Svelte:** choose the framework your application already uses. Create one map for each mounted container and remove it when the component unmounts. - **Next.js:** load the interactive map in a browser-only component. The surrounding page can still render on the server; WebGL and DOM-dependent map code cannot. ## Runnable examples | Framework/Template | Demo URL | Code URL | Description | |-------------------|-----------|-----------|-------------| | maplibre-geoman-vite | [Demo](https://maplibre-geoman-vite.vercel.app) | [Code](https://github.com/geoman-io/maplibre-geoman-examples/tree/master/maplibre-geoman-vite) | Vanilla JavaScript implementation using Vite as the build tool | | maplibre-geoman-vue | [Demo](https://maplibre-geoman-vue.vercel.app) | [Code](https://github.com/geoman-io/maplibre-geoman-examples/tree/master/maplibre-geoman-vue) | Vue.js integration showcasing reactive map editing capabilities | | maplibre-geoman-react | [Demo](https://maplibre-geoman-react.vercel.app) | [Code](https://github.com/geoman-io/maplibre-geoman-examples/tree/master/maplibre-geoman-react) | React implementation with hooks and components for map editing | | maplibre-geoman-preact | [Demo](https://maplibre-geoman-preact.vercel.app) | [Code](https://github.com/geoman-io/maplibre-geoman-examples/tree/master/maplibre-geoman-preact) | Lightweight Preact alternative to the React implementation | | maplibre-geoman-nextjs | [Demo](https://maplibre-geoman-nextjs.vercel.app) | [Code](https://github.com/geoman-io/maplibre-geoman-examples/tree/master/maplibre-geoman-nextjs) | Next.js integration with a browser-only map inside an application that supports server rendering | | maplibre-geoman-svelte | [Demo](https://maplibre-geoman-svelte.vercel.app) | [Code](https://github.com/geoman-io/maplibre-geoman-examples/tree/master/maplibre-geoman-svelte) | Svelte implementation offering reactive map editing features | ## Before adapting an example Install the packages from that example's lockfile, load the matching Geoman and MapLibre CSS, and give the map container a height. Wait for `gm:loaded` before importing features or enabling draw modes. MapLibre GL JS 6 users should also follow the worker setup in the [installation guide](/docs/maplibre/basics). Start with [circle markers](/docs/maplibre/draw-modes/draw-circle_marker) for point data or [polygons](/docs/maplibre/draw-modes/draw-polygon) for areas. Use `geoman.features.exportGeoJson()` to read the finished data. The [full interactive demo](https://geoman.io/demo/maplibre) lets you explore tools before integrating them. --- ## Exporting GeoJSON > Export features as GeoJSON FeatureCollections with filtering by source, shape type, or specific features. **URL:** https://geoman.io/docs/maplibre/exporting-data Geoman provides several methods to export features as GeoJSON. You can export all features, features from specific sources, or filtered features based on shape types. ## Basic Export Methods ### Export All Features The simplest way to export all features is using `exportGeoJson()`: ```typescript const allFeatures = gm.features.exportGeoJson(); ``` This returns a GeoJSON FeatureCollection containing all features from Geoman's internal state. This method provides the latest feature data, even during event handlers before MapLibre has committed changes. ### Export from MapLibre Source If you need to export features directly from MapLibre's source (which may lag slightly behind during rapid updates or in event handlers), use `exportGeoJsonFromSource()`: ```typescript const featuresFromSource = gm.features.exportGeoJsonFromSource(); ``` ### Export with Options Both export methods support filtering by shape types and customizing the ID property name: ```typescript // Export only polygons and circles const polygonsAndCircles = gm.features.exportGeoJson({ allowedShapes: ['polygon', 'circle'] }); // Use a custom property name for feature IDs const withCustomId = gm.features.exportGeoJson({ idPropertyName: 'featureId' }); // Combine options const filtered = gm.features.exportGeoJsonFromSource({ allowedShapes: ['marker', 'line'], idPropertyName: 'customId' }); ``` ## Exported GeoJSON Structure The exported GeoJSON follows this structure: ```typescript interface GeoJsonShapeFeatureCollection { type: 'FeatureCollection'; features: Array<{ type: 'Feature'; geometry: { type: string; coordinates: any[]; }; properties: { __gm_id?: FeatureId; shape: FeatureShape; center?: LngLat; text?: string; }; }>; } ``` ## Complete Example Here's a complete example showing different export methods: ```typescript const gm = new Geoman(map); // Draw some features... // Export all features const allFeatures = gm.features.exportGeoJson(); console.log('All features:', allFeatures); // Save to file function downloadGeoJson(geojson: GeoJsonShapeFeatureCollection, filename: string) { const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } // Download different exports downloadGeoJson(allFeatures, 'all-features.geojson'); ``` --- ## Snapping to External Sources ⭐ > Snap drawing and editing to host-owned map sources and layers such as parcels, roads, and reference data. **URL:** https://geoman.io/docs/maplibre/external-snapping In addition to snapping to Geoman's own features (the [Snapping helper](/helper-modes/helper-snapping)), Geoman can snap against **host-owned** map sources and layers — parcels, roads, reference grids, and any other data your application already loads. This is configured through `settings.snapping`. Geoman's own features always take priority over external targets, and external snapping is disabled until you provide at least one target. ## Settings ```typescript interface SnappingSettings { // host sources/layers to snap against; empty disables external snapping targets: SnapTarget[]; // default: [] // 'rendered' (default) queries only painted features via queryRenderedFeatures // (respects zoom/visibility/filters; vector-tile geometry is clipped at tile // boundaries). 'source' queries all loaded tiles via querySourceFeatures // (more candidates, same clipping caveat). For pixel-perfect snapping, load // the reference data as a GeoJSON source. acquire: 'rendered' | 'source'; // default: 'rendered' // cap on external candidate features considered per snap (perf guard) maxCandidates: number; // default: 200 } interface SnapTarget { // Source id. Required for acquire: 'source'; for acquire: 'rendered' it filters // the rendered query to features from this source when `layers` is not given. source?: string; // vector-tile source-layer (used by acquire: 'source') sourceLayer?: string; // rendered layer ids to query (used by acquire: 'rendered') layers?: string[]; // snap to this target's vertices (default: true) vertices?: boolean; // snap to this target's edges/segments (default: true) edges?: boolean; // MapLibre/Mapbox filter expression applied to the query filter?: unknown; } ``` The snap tolerance in pixels is shared with the snapping helper and controlled by [`settings.snapDistance`](/configuring-geoman#settings-configuration) (default `18`). ## Example ```typescript const gm = new Geoman(map, { settings: { snapDistance: 18, snapping: { acquire: 'rendered', maxCandidates: 200, targets: [ { // snap to rendered parcel boundaries source: 'parcels', layers: ['parcels-outline'], vertices: true, edges: true, }, { // snap only to major roads, querying loaded vector tiles source: 'roads', sourceLayer: 'transportation', filter: ['==', ['get', 'class'], 'primary'], edges: true, vertices: false, }, ], }, }, }); ``` ## Notes - Vertices are preferred over edges; each candidate is checked against the snap tolerance. - `acquire: 'rendered'` respects the layer's zoom range, visibility and filters; vector-tile geometry is clipped at tile boundaries. - `acquire: 'source'` considers more candidates but has the same tile-clipping caveat. For pixel-perfect snapping, load the reference data as a GeoJSON source. - `maxCandidates` bounds how many external features are considered per snap to protect performance on dense datasets. --- ## Feature IDs > How Geoman assigns, tracks, and manages feature IDs during import, export, and editing. **URL:** https://geoman.io/docs/maplibre/feature-ids # Feature IDs in Geoman Geoman uses feature IDs to track and manage features on the map. Understanding how these IDs work is important for importing, exporting, and managing features. ## ID Handling on Import When importing GeoJSON features into Geoman, there are two ways IDs can be assigned: 1. Using existing IDs from the GeoJSON 2. Auto-generating new IDs if none exist ### Using Existing IDs If your GeoJSON feature has an `id` field, Geoman will use this as the feature's ID: ```typescript // GeoJSON with custom ID const feature = { type: 'Feature', id: 'custom-123', // This ID will be used by Geoman properties: { shape: 'polygon' }, geometry: { type: 'Polygon', coordinates: [/* ... */] } }; // Import the feature gm.features.importGeoJsonFeature(feature); ``` ### Auto-generated IDs If no `id` field is present, Geoman will automatically generate a new ID: ```typescript // GeoJSON without ID const feature = { type: 'Feature', properties: { shape: 'polygon' }, geometry: { type: 'Polygon', coordinates: [/* ... */] } }; // Import the feature - ID will be auto-generated gm.features.importGeoJsonFeature(feature); ``` ### Using a Property as the Feature ID You can specify a property name to use as the feature ID during import with the `idPropertyName` option: ```typescript // GeoJSON with ID stored in properties const featureCollection = { type: 'FeatureCollection', features: [ { type: 'Feature', properties: { shape: 'polygon', customId: 'my-custom-id-123', // Use this as the feature ID name: 'My Polygon' }, geometry: { type: 'Polygon', coordinates: [/* ... */] } } ] }; // Import using the 'customId' property as the feature ID const result = await gm.features.importGeoJson(featureCollection, { idPropertyName: 'customId' }); // The feature will have ID 'my-custom-id-123' const feature = gm.features.get('gm_main', 'my-custom-id-123'); ``` This is useful when your GeoJSON data stores IDs in a property rather than the top-level `id` field. ## The `__gm_id` Property When exporting features from Geoman, each feature will have a `__gm_id` property in its properties object. This is Geoman's internal identifier for the feature. ### Export Example ```typescript // Export features const exported = gm.features.exportGeoJson(); // Example exported feature structure const feature = { type: 'Feature', id: 'custom-123', // Original ID if it existed properties: { shape: 'polygon', __gm_id: 'custom-123', // Same as id or auto-generated if no id existed // ... other properties }, geometry: { type: 'Polygon', coordinates: [/* ... */] } } ``` ### Reimporting Exported Features When reimporting features that were previously exported from Geoman, you can use the `overwrite` option to replace existing features with matching IDs: ```typescript // Export features const exported = gm.features.exportGeoJson(); // Later, reimport the same features with overwrite to replace existing ones const result = await gm.features.importGeoJson(exported, { overwrite: true }); // Check how many features were replaced console.log(`Replaced ${result.stats.overwritten} existing features`); ``` Without the `overwrite` option, importing a feature with an existing ID is handled by the `onIdCollision` option (default `'skip'`), so colliding features are skipped and counted as failed. Set `onIdCollision: 'reassign'` to instead import them with a fresh unique ID. ## ID Generation If you don't provide an ID, Geoman generates one using an internal counter: ```typescript // Example of auto-generated IDs const feature1 = { type: 'Feature', properties: { shape: 'polygon' }, geometry: { /* ... */ } }; const feature2 = { type: 'Feature', properties: { shape: 'line' }, geometry: { /* ... */ } }; // Features will get sequential IDs (importGeoJsonFeature is async) const imported1 = await gm.features.importGeoJsonFeature(feature1); const imported2 = await gm.features.importGeoJsonFeature(feature2); console.log(imported1?.id); // e.g., "1" console.log(imported2?.id); // e.g., "2" ``` ## ID Types Geoman supports both string and number IDs: ```typescript type FeatureId = string | number; ``` ## Working with Feature IDs ### Checking if a Feature Exists ```typescript const hasFeature = gm.features.has('gm_main', 'custom-123'); ``` ### Getting a Feature by ID ```typescript const feature = gm.features.get('gm_main', 'custom-123'); ``` ### Features Store Access The features store can be accessed through iteration methods: ```typescript // Iterate over all features gm.features.forEach((feature, id) => { console.log('Feature ID:', id); console.log('Feature:', feature); }); ``` ## Best Practices 1. **Consistent ID Schema**: If providing custom IDs, use a consistent schema: ```typescript const features = [ { type: 'Feature', id: 'polygon-001', properties: { shape: 'polygon' }, geometry: { /* ... */ } }, { type: 'Feature', id: 'polygon-002', properties: { shape: 'polygon' }, geometry: { /* ... */ } } ]; ``` 2. **ID Preservation**: When exporting and reimporting, use the `overwrite` option to replace existing features: ```typescript // Export const exported = gm.features.exportGeoJson(); // Save somewhere... // Later, reimport with overwrite to replace existing features const reimported = loadSavedFeatures(); const result = await gm.features.importGeoJson(reimported, { overwrite: true }); console.log(`Imported ${result.stats.success} features, replaced ${result.stats.overwritten}`); ``` 3. **ID Uniqueness**: Ensure custom IDs are unique within your feature set: ```typescript const usedIds = new Set(); features.forEach(feature => { if (usedIds.has(feature.id)) { throw new Error(`Duplicate ID: ${feature.id}`); } usedIds.add(feature.id); gm.features.importGeoJsonFeature(feature); }); ``` 4. **ID Type Consistency**: Stick to either string or number IDs within your application: ```typescript // Either use string IDs const stringIdFeature = { type: 'Feature', id: 'feature-123', // ... }; // Or number IDs const numberIdFeature = { type: 'Feature', id: 123, // ... }; // But don't mix them unless necessary ``` ## Error Handling When working with feature IDs, handle potential errors: ```typescript try { // Attempt to get a feature const feature = gm.features.get('gm_main', 'custom-123'); if (!feature) { console.warn('Feature not found:', 'custom-123'); // Handle missing feature... } else { // Process feature... } } catch (error) { console.error('Error accessing feature:', error); // Handle error... } ``` --- ## Geoman Features API > API reference for the Features instance: importing, exporting, querying, and manipulating map features. **URL:** https://geoman.io/docs/maplibre/features-instance The Features API provides methods to manage and manipulate features (geometries) on the map. It is accessible through the `features` property on the Geoman instance. ```typescript const options: GmOptionsPartial = { // configuration options // see Configuring Geoman section for more details }; const gm = new Geoman(map,options); const features = gm.features; ``` ## Core Methods ### Iteration Methods #### `forEach` Iterates over all features in the main source. ```typescript features.forEach((featureData: FeatureData, id: FeatureId, store: FeatureStore) => void); ``` ### Feature Management #### `add` Adds a feature to the store. ```typescript features.add(featureData: FeatureData): void; ``` #### `delete` Removes a feature from the store. Accepts either a `FeatureData` instance or a feature ID. ```typescript features.delete(featureIdOrFeatureData: FeatureData | FeatureId): Promise; ``` #### `has` Checks if a feature exists in a specific source. ```typescript features.has(sourceName: FeatureSourceName, featureId: FeatureId): boolean; ``` #### `get` Retrieves a feature from a specific source by ID. ```typescript features.get(sourceName: FeatureSourceName, featureId: FeatureId): FeatureData | null; ``` ### GeoJSON Operations #### `importGeoJson` Imports GeoJSON data and creates features. ```typescript features.importGeoJson( geoJson: GeoJsonImportFeatureCollection | GeoJsonImportFeature, options?: { idPropertyName?: string; // Use a specific property as the feature ID overwrite?: boolean; // When true, replace existing features with matching IDs onIdCollision?: 'skip' | 'reassign'; // ID-collision handling (default: 'skip') } ): Promise<{ stats: { total: number; success: number; failed: number; overwritten: number; // Count of replaced features (when overwrite: true) }; addedFeatures: Array; }>; ``` When `overwrite: true` is set, existing features with matching IDs are deleted before importing the new features. #### `importGeoJsonFeature` Imports a single GeoJSON feature. ```typescript features.importGeoJsonFeature(shapeGeoJson: GeoJsonImportFeature): Promise; ``` #### `exportGeoJson` Exports all features as a GeoJSON FeatureCollection from Geoman's internal state. This provides the latest feature data, even during event handlers before MapLibre has committed changes. ```typescript features.exportGeoJson(options?: { allowedShapes?: Array; // Filter by shape types idPropertyName?: string; // Custom property name for IDs }): GeoJsonShapeFeatureCollection; ``` #### `exportGeoJsonFromSource` Exports all features as a GeoJSON FeatureCollection directly from MapLibre's source. This may lag slightly behind Geoman's internal state during rapid updates or in event handlers. ```typescript features.exportGeoJsonFromSource(options?: { allowedShapes?: Array; // Filter by shape types idPropertyName?: string; // Custom property name for IDs }): GeoJsonShapeFeatureCollection; ``` #### `deleteAll` Deletes all features from the store. ```typescript features.deleteAll(): Promise; ``` #### `getAll` Returns all features as a GeoJSON FeatureCollection. ```typescript features.getAll(): FeatureCollection; ``` ### Feature Creation and Management #### `createFeature` Creates a new feature from GeoJSON data. ```typescript features.createFeature({ featureId?: FeatureId; shapeGeoJson: GeoJsonShapeFeature; parent?: FeatureData; sourceName: FeatureSourceName; imported?: boolean; }): Promise; ``` #### `addGeoJsonFeature` Adds a single GeoJSON feature to the store. ```typescript features.addGeoJsonFeature({ shapeGeoJson: GeoJsonImportFeature; sourceName?: FeatureSourceName; defaultSource?: boolean; }): Promise; ``` ### Feature Queries #### `getFeatureByMouseEvent` Gets a feature at the mouse event location. ```typescript features.getFeatureByMouseEvent({ event: BaseMapPointerEvent; sourceNames: Array; }): FeatureData | null; ``` #### `getFeaturesByGeoJsonBounds` Gets features that intersect with a GeoJSON geometry. ```typescript features.getFeaturesByGeoJsonBounds({ geoJson: Feature; sourceNames: Array; }): Array; ``` #### `getFeaturesByScreenBounds` Gets features within screen coordinate bounds. ```typescript features.getFeaturesByScreenBounds({ bounds: [ScreenPoint, ScreenPoint]; sourceNames: Array; }): Array; ``` ### Source Management #### `setDefaultSourceName` Sets the default source for new features. ```typescript features.setDefaultSourceName(sourceName: FeatureSourceName): void; ``` ### Marker Feature Operations #### `createMarkerFeature` Creates a new marker feature. ```typescript features.createMarkerFeature({ type: MarkerData['type']; coordinate: LngLatTuple; parentFeature: FeatureData; sourceName: FeatureSourceName; properties?: Record; }): Promise; ``` #### `updateMarkerFeaturePosition` Updates a marker feature's position. ```typescript features.updateMarkerFeaturePosition( markerFeatureData: FeatureData, coordinates: LngLatTuple ): Promise; ``` ## Source Names Geoman has three built-in sources for features: - `gm_main`: The main source for permanent features - `gm_temporary`: For temporary features during editing/drawing - `gm_standby`: For standby features (Pro version only) ## FeatureData Instance Methods Once you have a `FeatureData` instance (from importing, creating, or querying features), you can call the following methods directly on the feature instance. ### `updateProperties` Updates custom properties on this feature. Properties are merged with existing ones. Set a property value to `undefined` to delete it. Internal Geoman properties (prefixed with `gm_`) are protected and cannot be modified through this method. ```typescript feature.updateProperties(properties: Record): Promise; ``` **Example:** ```typescript // Get a feature const feature = gm.features.get('gm_main', featureId); // Update properties (merged with existing) feature.updateProperties({ name: 'Updated Name', description: 'New description', oldProperty: undefined // This deletes the property }); ``` ### `setProperties` Replaces all custom properties on this feature. Removes existing custom properties and replaces them with the provided ones. Internal Geoman properties (prefixed with `gm_`) are preserved and cannot be removed. ```typescript feature.setProperties(properties: Record): Promise; ``` **Example:** ```typescript // Get a feature const feature = gm.features.get('gm_main', featureId); // Replace all custom properties feature.setProperties({ name: 'New Name', category: 'residential' }); // Previous custom properties are removed, only 'name' and 'category' remain ``` ### `updateGeometry` Updates the geometry of this feature with new coordinates. ```typescript feature.updateGeometry(geometry: BasicGeometry): Promise; ``` **Example:** ```typescript // Get a feature const feature = gm.features.get('gm_main', featureId); // Update the geometry feature.updateGeometry({ type: 'Polygon', coordinates: [ [ [-8.15, 49.44], [-8.10, 49.44], [-8.10, 49.40], [-8.15, 49.40], [-8.15, 49.44] ] ] }); ``` ### `delete` Removes this feature and its associated markers from the map. ```typescript feature.delete(): Promise; ``` ### `convertToPolygon` Converts circle, ellipse, or rectangle shapes to standard polygon form by removing shape-specific properties. Resolves to `true` if conversion was successful. ```typescript feature.convertToPolygon(): Promise; ``` ### `changeSource` Moves this feature to a different source. ```typescript feature.changeSource({ sourceName: FeatureSourceName; }): Promise; ``` ## Types ### FeatureData The main class representing a feature on the map. ```typescript class FeatureData { gm: Geoman; id: FeatureId; parent: FeatureData | null; markers: Map; source: BaseSource; // Getters get shape(): FeatureShape; get temporary(): boolean; get sourceName(): FeatureSourceName; // Instance methods getGeoJson(): GeoJsonShapeFeature; updateProperties(properties: Record): Promise; setProperties(properties: Record): Promise; updateGeometry(geometry: BasicGeometry): Promise; delete(): Promise; convertToPolygon(): Promise; changeSource(options: { sourceName: FeatureSourceName }): Promise; } ``` ### FeatureSourceName ```typescript type FeatureSourceName = 'gm_main' | 'gm_temporary' | 'gm_standby'; ``` ### Example Usage ```typescript // Import GeoJSON data const geoJson = { type: 'FeatureCollection', features: [ { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: { shape: 'marker' } } ] }; // Import features (importGeoJson is async) const result = await gm.features.importGeoJson(geoJson); // Import with overwrite option (replaces existing features with matching IDs) const resultWithOverwrite = await gm.features.importGeoJson(geoJson, { overwrite: true }); console.log(`Overwritten: ${resultWithOverwrite.stats.overwritten} features`); // Iterate over features gm.features.forEach((feature) => { console.log(feature.id, feature.shape); }); // Export features from Geoman's internal state const exported = gm.features.exportGeoJson(); // Export features directly from MapLibre's source const exportedFromSource = gm.features.exportGeoJsonFromSource(); // Get features in bounds const bounds: [ScreenPoint, ScreenPoint] = [[0, 0], [100, 100]]; const featuresInBounds = gm.features.getFeaturesByScreenBounds({ bounds, sourceNames: ['gm_main'] }); // Working with individual feature instances const feature = gm.features.get('gm_main', 'feature-id'); if (feature) { // Update properties (merges with existing) — these methods are async await feature.updateProperties({ name: 'Updated Feature', status: 'active' }); // Replace all properties await feature.setProperties({ name: 'New Name Only' }); // Update geometry await feature.updateGeometry({ type: 'Point', coordinates: [10, 20] }); // Delete the feature // await feature.delete(); } ``` --- ## Geoman Instance API > Complete API reference for the Geoman instance: initialization, mode management, global controls, and feature operations. **URL:** https://geoman.io/docs/maplibre/geoman-instance-api The Geoman instance provides the main interface for interacting with the map editing functionality. This documentation covers all public methods available on the Geoman instance. ## Installation & Initialization ```typescript import { Geoman, GmOptionsPartial } from '@geoman-io/maplibre-geoman-pro'; // or '@geoman-io/maplibre-geoman-free', '@geoman-io/mapbox-geoman-pro', '@geoman-io/mapbox-geoman-free' const options: GmOptionsPartial = { // configuration options }; const gm = new Geoman(map, options); ``` ## Core Properties ### `features` Access to the Features API for managing map features. ```typescript const features = gm.features; ``` ### `mapAdapter` Access to the underlying map adapter instance. ```typescript const mapAdapter = gm.mapAdapter; ``` ## Mode Management Methods ### Draw Modes Mode-changing methods are asynchronous and return a `Promise`. You can `await` them when you need to be sure the mode change has completed, or call them fire-and-forget. #### `enableDraw` Enables drawing mode for a specific shape. ```typescript gm.enableDraw(shape: DrawModeName): Promise; ``` #### `disableDraw` Disables any active drawing mode. ```typescript gm.disableDraw(): Promise; ``` #### `toggleDraw` Toggles drawing mode for a specific shape. ```typescript gm.toggleDraw(shape: DrawModeName): Promise; ``` #### `drawEnabled` Checks if drawing mode is enabled for a specific shape. ```typescript gm.drawEnabled(shape: DrawModeName): boolean; ``` ### Edit Modes #### Global Edit Mode ```typescript gm.enableGlobalEditMode(): Promise; gm.disableGlobalEditMode(): Promise; gm.toggleGlobalEditMode(): Promise; gm.globalEditModeEnabled(): boolean; ``` #### Global Drag Mode ```typescript gm.enableGlobalDragMode(): Promise; gm.disableGlobalDragMode(): Promise; gm.toggleGlobalDragMode(): Promise; gm.globalDragModeEnabled(): boolean; ``` #### Global Rotate Mode ```typescript gm.enableGlobalRotateMode(): Promise; gm.disableGlobalRotateMode(): Promise; gm.toggleGlobalRotateMode(): Promise; gm.globalRotateModeEnabled(): boolean; ``` #### Global Cut Mode ```typescript gm.enableGlobalCutMode(): Promise; gm.disableGlobalCutMode(): Promise; gm.toggleGlobalCutMode(): Promise; gm.globalCutModeEnabled(): boolean; ``` #### Global Removal Mode ```typescript gm.enableGlobalRemovalMode(): Promise; gm.disableGlobalRemovalMode(): Promise; gm.toggleGlobalRemovalMode(): Promise; gm.globalRemovalModeEnabled(): boolean; ``` #### Other Edit Modes For edit modes without dedicated helper methods (select, scale, copy, split, union, difference, line_simplification, lasso, add_hole, add_part, remove_ring, explode, merge_parts), use the generic mode management methods: ```typescript // Example: Scale mode gm.enableMode('edit', 'scale'); gm.disableMode('edit', 'scale'); gm.toggleMode('edit', 'scale'); gm.isModeEnabled('edit', 'scale'); // Example: Copy mode gm.enableMode('edit', 'copy'); // ... and so on for other edit modes ``` ### Generic Mode Management #### `enableMode` Enables a specific mode for a given mode type. ```typescript gm.enableMode(modeType: ModeType, modeName: ModeName): Promise; ``` #### `disableMode` Disables a specific mode for a given mode type. ```typescript gm.disableMode(modeType: ModeType, modeName: ModeName): Promise; ``` #### `toggleMode` Toggles a specific mode for a given mode type. ```typescript gm.toggleMode(modeType: ModeType, modeName: ModeName): Promise; ``` #### `isModeEnabled` Checks if a specific mode is enabled. ```typescript gm.isModeEnabled(modeType: ModeType, modeName: ModeName): boolean; ``` #### `disableAllModes` Disables all active modes. ```typescript gm.disableAllModes(): Promise; ``` ## Mode Status Methods #### `getActiveDrawModes` Gets array of currently active draw modes. ```typescript gm.getActiveDrawModes(): Array; ``` #### `getActiveEditModes` Gets array of currently active edit modes. ```typescript gm.getActiveEditModes(): Array; ``` #### `getActiveHelperModes` Gets array of currently active helper modes. ```typescript gm.getActiveHelperModes(): Array; ``` ## Control Management #### `addControls` Adds the Geoman controls to the map. ```typescript gm.addControls(controlsElement?: HTMLElement): Promise; ``` #### `removeControls` Removes the Geoman controls from the map. ```typescript gm.removeControls(): Promise; ``` ## Lifecycle Methods #### `waitForGeomanLoaded` Waits for Geoman to be fully loaded and ready. ```typescript gm.waitForGeomanLoaded(): Promise; ``` #### `destroy` Destroys the Geoman instance and cleans up resources. ```typescript gm.destroy(options?: { removeSources: boolean }): Promise; ``` ## Event Handling #### `setGlobalEventsListener` Sets a global event listener for all Geoman events. Pass `null` (or call with no argument) to remove the listener. ```typescript gm.setGlobalEventsListener(callback?: GlobalEventsListener | null): void; ``` ## Types ### ModeName Types ```typescript type DrawModeName = 'marker' | 'circle' | 'circle_marker' | 'ellipse' | 'text_marker' | 'line' | 'rectangle' | 'polygon' | 'freehand' | 'custom_shape'; type EditModeName = 'select' | 'drag' | 'change' | 'rotate' | 'scale' | 'copy' | 'cut' | 'split' | 'union' | 'difference' | 'line_simplification' | 'lasso' | 'add_hole' | 'add_part' | 'remove_ring' | 'explode' | 'merge_parts' | 'delete'; type HelperModeName = 'shape_markers' | 'pin' | 'snapping' | 'snap_guides' | 'measurements' | 'auto_trace' | 'geofencing' | 'zoom_to_features' | 'click_to_edit'; // The mode type accepted by enableMode / disableMode / toggleMode / isModeEnabled type ModeType = 'draw' | 'edit' | 'helper'; ``` ### Event Types ```typescript // The global events listener receives a single event object type GlobalEventsListener = (event: GmSystemEvent | GmEvent) => void; ``` ## Example Usage ```typescript // Initialize Geoman const map = new maplibregl.Map({ container: 'map', style: 'https://maps.geoman.io/styles/basic/style.json' }); const gm = new Geoman(map); // Add controls await gm.addControls(); // Enable drawing mode gm.enableDraw('polygon'); // Listen to events gm.setGlobalEventsListener((event) => { if (event.name === 'gm:create') { console.log('Feature created:', event); } }); // Enable various edit modes gm.enableGlobalEditMode(); gm.enableGlobalRotateMode(); // Check active modes const activeModes = gm.getActiveEditModes(); console.log('Active edit modes:', activeModes); // Disable all modes gm.disableAllModes(); ``` ## Available Draw Shapes - `marker`: Single point marker - `circle`: Circle with radius - `circle_marker`: Fixed-size circle marker - `ellipse`: Ellipse with x and y semi-axes - `text_marker`: Text label - `line`: Linear feature - `rectangle`: Rectangular polygon - `polygon`: Free-form polygon - `freehand`: Freehand drawing - `custom_shape`: Custom shape drawing ## Available Edit Modes - `select`: Select features - `drag`: Move features - `change`: Modify vertices - `rotate`: Rotate features - `scale`: Scale features - `copy`: Copy features - `cut`: Cut features - `split`: Split features - `union`: Combine features - `difference`: Subtract features - `line_simplification`: Simplify lines - `lasso`: Lasso selection - `add_hole`: Add a hole to a polygon - `add_part`: Add a part to a (multi)polygon - `remove_ring`: Remove a ring (hole or part) from a polygon - `explode`: Explode a multi-part feature into separate features - `merge_parts`: Merge selected features into a single multi-part feature - `delete`: Remove features ## Available Helper Modes - `shape_markers`: Show shape markers - `pin`: Pin mode - `snapping`: Enable snapping - `snap_guides`: Show snap guides - `measurements`: Show measurements - `auto_trace`: Auto-trace features - `geofencing`: Geofencing mode - `zoom_to_features`: Zoom to features - `click_to_edit`: Click to edit mode ## Editor-platform APIs (Pro 0.11) - `gm.dataLayers`: [named layers, styles and schemas](/data-layers). - `gm.edit`: [undoable programmatic editing and geometry utilities](/programmatic-editing). - `gm.geofencing`: [live containment and keep-out constraints](/helper-modes/helper-geofencing). - [Accessibility and localisation](/accessibility). - [0.11 migration and full release notes](/release-0.11). The lifecycle emits `gm:unloaded` when Geoman is destroyed. --- ## Geoman Options API > API reference for GmOptions: control configuration, mode state management, settings, and layer styles. **URL:** https://geoman.io/docs/maplibre/geoman-options-api The GmOptions instance manages the configuration and state of Geoman controls and modes. It handles settings, controls, and layer styles, and provides methods to manage mode states. ## Accessing GmOptions The GmOptions instance is available through the `options` property on the Geoman instance: ```typescript const options: GmOptionsPartial = { // configuration options // see Configuring Geoman section for more details }; const gm = new Geoman(map,options); const options = gm.options; ``` ## Methods ### Mode Management #### `enableMode` Enables a specific mode for a mode type. Returns a `Promise` that resolves once the mode change has completed. ```typescript options.enableMode(modeType: ModeType, modeName: ModeName): Promise; ``` Example: ```typescript options.enableMode('draw', 'polygon'); options.enableMode('edit', 'rotate'); ``` #### `disableMode` Disables a specific mode for a mode type. Returns a `Promise` that resolves once the mode change has completed. ```typescript options.disableMode(modeType: ModeType, modeName: ModeName): Promise; ``` Example: ```typescript options.disableMode('draw', 'polygon'); options.disableMode('edit', 'rotate'); ``` #### `toggleMode` Toggles a specific mode for a mode type. Returns a `Promise` that resolves once the mode change has completed. ```typescript options.toggleMode(modeType: ModeType, modeName: ModeName): Promise; ``` Example: ```typescript options.toggleMode('draw', 'polygon'); options.toggleMode('edit', 'rotate'); ``` ### Mode State Queries #### `isModeEnabled` Checks if a specific mode is currently enabled. ```typescript options.isModeEnabled(actionType: ActionType, modeName: ModeName): boolean; ``` Example: ```typescript const isDrawing = options.isModeEnabled('draw', 'polygon'); const isEditing = options.isModeEnabled('edit', 'rotate'); ``` #### `isModeAvailable` Checks if a specific mode is available for use. ```typescript options.isModeAvailable(actionType: ActionType, modeName: ModeName): boolean; ``` Example: ```typescript const canDraw = options.isModeAvailable('draw', 'polygon'); const canEdit = options.isModeAvailable('edit', 'rotate'); ``` ### Control Options #### `getControlOptions` Gets the options for a specific control. ```typescript options.getControlOptions({ modeType: ModeType, modeName: ModeName }): ControlOptions | null; ``` Example: ```typescript const polygonOptions = options.getControlOptions({ modeType: 'draw', modeName: 'polygon' }); ``` ## Dynamic Configuration Example ```typescript // Get current control options const polygonControl = gm.options.getControlOptions({ modeType: 'draw', modeName: 'polygon' }); // Check mode states const isPolygonEnabled = gm.options.isModeEnabled('draw', 'polygon'); const isRotateAvailable = gm.options.isModeAvailable('edit', 'rotate'); // Toggle modes if (isPolygonEnabled) { gm.options.disableMode('draw', 'polygon'); } else { gm.options.enableMode('draw', 'polygon'); } ``` --- ## Auto Trace ⭐ > Automatically trace along existing feature edges when drawing new shapes. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-auto_trace # Auto Trace Helper Mode The Auto Trace Helper Mode assists users in drawing and connecting shapes by automatically tracing the shortest path between selected points. This mode can be applied to create or complete shapes like lines and polygons by tracing along existing paths. You can enable Auto Trace Helper Mode with the following method: ```js map.gm.enableMode('helper', 'auto_trace'); map.gm.disableMode('helper', 'auto_trace'); map.gm.toggleMode('helper', 'auto_trace'); map.gm.isModeEnabled('helper', 'auto_trace'); ``` ### Events for Auto Trace Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:------------------------------| :-------| :------------------------------------- | :-----------------------| | `gm:globalauto_tracemodetoggled` | `event` | Fired when Auto Trace Mode is toggled. | `enabled`, `map` | ### Example Usage You can listen for specific Auto Trace Helper Mode events on the map instance like this: ```js map.on('gm:globalauto_tracemodetoggled', (event) => { console.log(event); }); ``` ## Behavior Auto tracing works with the following shape types: - Lines - Polygons - Rectangles - Circles The Auto Trace Helper: 1. Calculates the shortest path between two specified points. 2. Adds intermediate points along the traced path if needed to maintain shape accuracy. ## Live Auto Trace Example --- ## Click to Edit ⭐ > Let users start editing a feature simply by clicking it, without first selecting an edit tool. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-click_to_edit # Click to Edit Helper Mode The Click to Edit Helper Mode makes existing features directly interactive: clicking a feature on the map puts it straight into editing, so users don't have to enable a specific edit tool first. It is a convenience layer on top of Geoman's edit modes and is well suited to "select and tweak" workflows. You can enable Click to Edit Helper Mode with the following methods: ```js map.gm.enableMode('helper', 'click_to_edit'); map.gm.disableMode('helper', 'click_to_edit'); map.gm.toggleMode('helper', 'click_to_edit'); map.gm.isModeEnabled('helper', 'click_to_edit'); ``` ### Events for Click to Edit Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:------------------------------------| :-------| :------------------------------------------- | :-----------------| | `gm:globalclick_to_editmodetoggled` | `event` | Fired when Click to Edit Helper Mode is toggled. | `enabled`, `map` | ### Example Usage ```js map.on('gm:globalclick_to_editmodetoggled', (event) => { console.log(event); }); ``` The actual geometry changes a user makes while a feature is being edited are reported through the standard edit events (`gm:editstart`, `gm:edit`, `gm:editend`) and feature events — see [Events](/events). ## Behavior The click to edit helper: 1. Resolves the feature under the cursor across Geoman's persistent sources. 2. Activates editing for that feature so its vertices/handles become draggable. 3. Works together with the snapping and shape-marker helpers when they are enabled. ## Default Control Options | Option | Default | |:-------------|:------------------| | `title` | `"Click to edit"` | | `icon` | built-in click-to-edit icon | | `uiEnabled` | `true` | | `active` | `false` | ## Live Click to Edit Example --- ## Geofencing ⭐ > Constrain where features may be drawn or edited and react to containment and intersection violations. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-geofencing # Geofencing Helper Mode The Geofencing Helper Mode validates every feature that is drawn or edited against a set of reference features and emits a violation event when a feature breaks the configured rules. Use it to keep geometry inside an allowed area (containment) or to keep it out of restricted zones (intersection). Geofencing does **not** cancel the operation by itself — it reports violations through events so your application decides how to react (warn the user, revert the change, block saving, etc.). You can enable Geofencing Helper Mode with the following methods: ```js map.gm.enableMode('helper', 'geofencing'); map.gm.disableMode('helper', 'geofencing'); map.gm.toggleMode('helper', 'geofencing'); map.gm.isModeEnabled('helper', 'geofencing'); ``` ## Configuring the reference features The geofencing helper tracks two independent groups of reference features: | Group | Rule | Violation | |:------|:-----|:----------| | `containment` | Drawn/edited features must stay **inside** at least one containment feature. | `containment_violation` | | `intersection` | Drawn/edited features must **not** intersect any intersection feature. | `intersection_violation` | Reference features are registered on the helper instance, which is reachable through `gm.actionInstances`: ```ts import type { FeatureData } from '@geoman-io/maplibre-geoman-pro'; // the geofencing helper must be enabled before you access it map.gm.enableMode('helper', 'geofencing'); const geofencing = map.gm.actionInstances['helper__geofencing']; // keep drawing/editing inside these features geofencing.addFeatures('containment', allowedAreaFeatures); // disallow overlapping these features geofencing.addFeatures('intersection', restrictedZoneFeatures); // later, remove individual reference features… geofencing.removeFeatures('intersection', [someFeatureData]); ``` `addFeatures` / `removeFeatures` take an array of `FeatureData` (the wrapped features Geoman manages — see [Working with the Features Instance](/features-instance)). When the mode is disabled both groups are cleared. If no containment features are registered, every feature passes the containment check. If no intersection features are registered, the intersection check never triggers. ## Events for Geofencing Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:-------------------------------------| :-------| :------------------------------------------------------- | :-----------------| | `gm:globalgeofencingmodetoggled` | `event` | Fired when Geofencing Helper Mode is toggled. | `enabled`, `map` | | `gm:geofencing_violation` | `event` | Fired when a drawn/edited feature breaks a rule. | see below | The violation event carries the following fields: ```ts interface GmGeofencingViolationEvent { name: '_gm:helper:geofencing_violation'; mode: 'geofencing'; // which operation produced the feature under test actionType: 'draw' | 'edit'; // the rule that was broken action: 'containment_violation' | 'intersection_violation'; } ``` ### Example Usage ```js map.on('gm:geofencing_violation', (event) => { if (event.action === 'containment_violation') { console.warn('Feature left the allowed area'); } else if (event.action === 'intersection_violation') { console.warn('Feature overlaps a restricted zone'); } }); ``` ## Behavior The geofencing helper: 1. Listens for the `before_create` (draw) and `before_update` (edit) feature events. 2. Runs the new geometry through the containment and intersection checks. 3. Fires a `gm:geofencing_violation` event for each rule that is broken. ## Default Control Options | Option | Default | |:-------------|:---------------| | `title` | `"Geofencing"` | | `icon` | built-in geofencing icon | | `uiEnabled` | `true` | | `active` | `false` | ## Live Geofencing Example Draw or edit features with the geofencing helper enabled — register containment/intersection reference features (see above) and listen for `gm:geofencing_violation` to react. ## Public API (Pro 0.11) ```typescript gm.geofencing.setContainment(boundaryFeatureIds); gm.geofencing.setIntersectionRestriction(keepOutFeatureIds); gm.geofencing.getContainment(); gm.geofencing.getIntersectionRestriction(); gm.geofencing.clear(); ``` Setters replace the constraint set and enable the helper. IDs resolve to live features, so editing a boundary changes the constraint. Rejected edits emit `gm:geofencing_violation`; `event.action` identifies containment or intersection violation. Use this façade instead of reaching into `actionInstances`. --- ## Measurements ⭐ > Display live distance, area, and perimeter measurements during drawing and editing. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-measurements # Measurements Helper Mode The Measurements Helper Mode provides tools to display live measurements such as distance, area, and perimeter for various shapes during map editing. This mode works with different shape types to dynamically calculate and display measurements as users draw or modify features. You can enable Measurements Helper Mode with the following method: ```js map.gm.enableMode('helper', 'measurements'); map.gm.disableMode('helper', 'measurements'); map.gm.toggleMode('helper', 'measurements'); map.gm.isModeEnabled('helper', 'measurements'); ``` Measurements unit system could be a set by GmOptions like this: ```js const gmOptions: PartialDeep = { controls: { helper: { measurements: { options: { units: { // "metric", "imperial", undefined (autodetection, default value) value: 'imperial' } } } } } }; ``` ### Events for Measurements Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:----------------------------------| :-------| :------------------------------------- | :-------------------------| | `gm:globalmeasurementmodetoggled` | `event` | Fired when Measurements Helper Mode is toggled. | `enabled`, `map` | ### Example Usage You can listen for specific Measurements Helper Mode events on the map instance like this: ```js map.on('gm:globalmeasurementmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Measurements can be calculated for the following shape types: - Markers - Lines - Rectangles - Polygons - Circles The helper dynamically: 1. Calculates distances, areas, and perimeters for shapes. 2. Updates these measurements in real-time as the shape is drawn or modified. 3. Displays the information in an interactive popup. ## Live Measurements Example --- ## Pin ⭐ > Pin markers to specific positions that persist across interactions. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-pin # Pin Helper Mode The Pin Helper Mode allows you to "pin" markers or vertices together, enabling synchronized movement of all connected points when any one of them is dragged. This mode is useful for maintaining alignment across features that share the same coordinates. You can enable Pin Helper Mode with the following methods: ```js map.gm.enableMode('helper', 'pin'); map.gm.disableMode('helper', 'pin'); map.gm.toggleMode('helper', 'pin'); map.gm.isModeEnabled('helper', 'pin'); ``` ### Events for Pin Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:---------------------------| :-------| :------------------------------------- | :-----------------------------| | `gm:globalpinmodetoggled` | `event` | Fired when Pin Helper Mode is toggled. | `enabled`, `map` | ### Example Usage You can listen for specific Pin Helper Mode events on the map instance like this: ```js map.on('gm:globalpinmodetoggled', (event) => { console.log(event); }); ``` ## Behavior When Pin Mode is enabled: 1. **Synchronized Movement**: Dragging a marker or vertex will move all other markers/vertices with the same coordinates (`latLng`) in sync. 2. **Automatic Pinning**: This functionality is applied to any markers or vertices at identical coordinates, automatically linking them. ## Live Pinning Example --- ## Shape Markers ⭐ > The internal helper that renders and manages the vertex and edge markers used while editing a shape. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-shape_markers # Shape Markers Helper Mode The Shape Markers Helper Mode renders the draggable vertex and edge markers that appear on a feature while it is being edited (for example in `change` mode) and routes pointer interaction on those markers to the active edit mode. Unlike the other helper modes, Shape Markers is an **internal helper**: it has no toolbar button (`uiEnabled` defaults to `false`) and is activated automatically by the edit modes that need it. You normally do not enable it yourself — it is documented here for completeness and because it can be tuned through the control options. You can still toggle it programmatically with the standard mode API: ```js map.gm.enableMode('helper', 'shape_markers'); map.gm.disableMode('helper', 'shape_markers'); map.gm.toggleMode('helper', 'shape_markers'); map.gm.isModeEnabled('helper', 'shape_markers'); ``` ### Events for Shape Markers Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:-------------------------------------| :-------| :------------------------------------------------ | :-----------------| | `gm:globalshape_markersmodetoggled` | `event` | Fired when Shape Markers Helper Mode is toggled. | `enabled`, `map` | Marker-level interactions (capturing, moving and releasing vertex/edge markers) are surfaced through the edit events `gm:edit:marker`, `gm:edit:marker_move` and the feature update events — see [Events](/events). ## Behavior The shape markers helper: 1. Builds vertex markers (and, where allowed, midpoint/edge markers) for the feature being edited. 2. Shares markers between linked features so coincident vertices move together. 3. Forwards pointer events on markers (`mousedown`, `mousemove`, `mouseup`, `touch*`, `contextmenu`) to the active edit mode. 4. Integrates with the [Pin](/helper-modes/helper-pin) helper so dragging one shared vertex updates every feature that shares it. ## Default Control Options | Option | Default | |:-------------|:-------------------| | `title` | `"Shape markers"` | | `icon` | `null` (no button) | | `uiEnabled` | `false` | | `active` | `false` | --- ## Snap Guides ⭐ > Display alignment guides when drawing or editing features. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-snap_guides # Snap Guides Helper Mode The Snap Guides Helper Mode adds visual guidelines that assist in snapping features to specific angles or points during map editing. This mode is useful when precise alignment is necessary while creating or modifying shapes. You can enable Snap Guides Helper Mode with the following methods: ```js map.gm.enableMode('helper', 'snap_guides'); map.gm.disableMode('helper', 'snap_guides'); map.gm.toggleMode('helper', 'snap_guides'); map.gm.isModeEnabled('helper', 'snap_guides'); ``` ### Events for Snap Guides Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:-------------------------------| :-------| :------------------------------------- | :-----------------------------| | `gm:globalsnappingmodetoggled` | `event` | Fired when Snap Guides Helper Mode is toggled. | `enabled`, `map` | ### Example Usage You can listen for specific Snap Guides Helper Mode events on the map instance like this: ```js map.on('gm:globalsnappingmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Snap guides work with the following shape types: - Lines - Polygons - Rectangles - Circles - Multi-polygons The Snap Guides helper: 1. Calculates and displays guidelines at specific angles or along existing features. 2. Helps users to snap to key points like intersections or perpendicular lines. 3. Snaps features to the closest guide point, with priority given to perpendicular or aligned snapping guides. 4. Custom snapping tolerance can be configured for guides. ## Live Snap Guides Example --- ## Snapping > Snap features to nearby vertices and edges during drawing and editing. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-snapping # Snapping Helper Mode The Snapping Helper Mode enables alignment of features by "snapping" them to nearby elements on the map. This mode provides tools to control, activate, or deactivate snapping for layers or shapes during editing. You can handle Snapping Helper Mode with the following methods: ```js map.gm.enableMode('helper', 'snapping'); map.gm.disableMode('helper', 'snapping'); map.gm.toggleMode('helper', 'snapping'); map.gm.isModeEnabled('helper', 'snapping'); ``` ### Events for Snapping Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:---------------------------| :-------| :------------------------------------- | :-----------------------------| | `gm:globalsnappingmodetoggled` | `event` | Fired when Snapping Helper Mode is toggled. | `enabled`, `map` | ### Example Usage You can listen for specific Snapping Helper Mode events on the map instance like this: ```js map.on('gm:globalsnappingmodetoggled', (event) => { console.log(event); }); ``` ## Behavior Snapping works with the following shape types: - Markers - Circle markers - Text markers - Lines - Rectangles - Polygons - Circles - Guide lines The snapping helper: 1. Calculates distances to nearby vertices and lines 2. Snaps to the closest point within tolerance 3. Prioritizes vertex snapping over line snapping 4. Uses a default tolerance of 18 pixels[1] ## Configuration ```js // Enable snapping map.gm.markerPointer.setSnapping(true); // Disable snapping map.gm.markerPointer.setSnapping(false); // Pause snapping temporarily map.gm.markerPointer.pauseSnapping(); // Resume snapping map.gm.markerPointer.resumeSnapping(); ``` ## Live Snapping Example --- ## Zoom to Features > Zoom the map viewport to fit selected features. **URL:** https://geoman.io/docs/maplibre/helper-modes/helper-zoom_to_features # Zoom to Features Helper Mode The Zoom to Features Helper Mode allows users to zoom the map to fit all displayed features within the viewport, making it easier to locate and view all active map features at once. You can enable Zoom to Features Helper Mode with the following method: ```js map.gm.enableMode('helper', 'zoom_to_features'); map.gm.disableMode('helper', 'zoom_to_features'); map.gm.toggleMode('helper', 'zoom_to_features'); map.gm.isModeEnabled('helper', 'zoom_to_features'); ``` ### Events for Zoom to Features Helper Mode The following events are available on a map instance: | Event | Params | Description | Output | |:---------------------------------------| :-------| :------------------------------------------- | :-----------------| | `gm:globalzoom_to_featuresmodetoggled` | `event` | Fired when Zoom to Features Mode is toggled. | `enabled`, `map` | ### Example Usage You can listen for specific Zoom to Features Helper Mode events on the map instance like this: ```js map.on('gm:globalzoom_to_featuresmodetoggled', (event) => { console.log(event); }); ``` ## Behavior When Zoom to Features Mode is activated: 1. **Map Adjustment**: Automatically calculates the bounding box to encompass all visible features. 2. **Padding**: Adds a 20-pixel padding around the features to ensure they fit well within the view. ## Live Zoom to Features Example --- ## History & Undo/Redo ⭐ > Built-in undo/redo, batched transactions, and host-controlled history through ChangeSets. **URL:** https://geoman.io/docs/maplibre/history Geoman includes an undo/redo and transaction system, exposed on the instance as `gm.history` and configured through `settings.history`. It works out of the box in `internal` mode and can be handed over to your application in `controlled` mode. ## Settings ```typescript interface HistorySettings { // master switch; when false nothing is recorded and the API is inert enabled: boolean; // default: true // 'internal' keeps bounded undo/redo stacks in memory (batteries included). // 'controlled' keeps no stack — the host stores emitted ChangeSets and drives // undo/redo by replaying inverses via features.applyChangeSet(). mode: 'internal' | 'controlled'; // default: 'internal' // max ChangeSets kept on the internal undo stack limit: number; // default: 50 // restore the prior selection when undoing (and clear it when undoing a create) trackSelection: boolean; // default: true // render the built-in Undo/Redo toolbar buttons (internal mode + useControlsUi) showControls: boolean; // default: true } ``` ```typescript const gm = new Geoman(map, { settings: { history: { enabled: true, mode: 'internal', limit: 50, trackSelection: true, showControls: true, }, }, }); ``` ## The `gm.history` API | Member | Type | Description | |:-------|:-----|:------------| | `undo()` | `Promise` | Undo the last transaction. Resolves `false` when there is nothing to undo. | | `redo()` | `Promise` | Redo the last undone transaction. | | `canUndo` | `boolean` | Whether an undo step is available. | | `canRedo` | `boolean` | Whether a redo step is available. | | `enabled` | `boolean` | Whether history is currently recording. | | `clear()` | `void` | Empty both stacks. | | `batch(label, fn)` | `Promise` | Run `fn` and commit all of its mutations as one undo step. | | `beginTransaction(label?)` / `endTransaction()` | `void` | Open/close form of `batch` for method bodies that early-return. Nestable. | | `commit(label?)` | `void` | Close the open transaction and push it to the stack. | | `settings` | `HistorySettings` | The resolved history settings. | ```js // undo / redo programmatically await map.gm.history.undo(); await map.gm.history.redo(); // group several edits into a single undo step await map.gm.history.batch('move group', async () => { // ...mutations that should undo together... }); ``` ## History events Two forwarded events let you build host UI and host persistence: | Event | Fired | Output | |:------|:------|:-------| | `gm:history` | On every stack change (commit, undo, redo, clear) — even keyboard undo/redo. | `canUndo`, `canRedo`, `map` | | `gm:transaction` | Once per committed transaction, only for `origin: 'user'` edits. | `changeSet`, `map` | ```js // keep undo/redo buttons in sync map.on('gm:history', (event) => { undoButton.disabled = !event.canUndo; redoButton.disabled = !event.canRedo; }); // persist every user edit (host-driven store) map.on('gm:transaction', (event) => { myStore.append(event.changeSet); }); ``` ## Controlled mode & ChangeSets In `controlled` mode Geoman keeps **no** internal stack. Instead it emits a `ChangeSet` on every user transaction via `gm:transaction`, and your application drives undo/redo by replaying inverses with `gm.features.applyChangeSet()`. ```typescript // One feature-level mutation, stored with enough info to apply and invert it. type FeatureChange = | { op: 'create'; featureId: FeatureId; after: GeoJsonShapeFeature } | { op: 'delete'; featureId: FeatureId; before: GeoJsonShapeFeature } | { op: 'update'; featureId: FeatureId; before: GeoJsonShapeFeature; after: GeoJsonShapeFeature }; // An atomic, named, invertible unit of history — one undo/redo step. interface ChangeSet { id: string; label: string; origin: 'user' | 'undo' | 'redo' | 'apply'; changes: FeatureChange[]; selectionBefore?: FeatureId[]; selectionAfter?: FeatureId[]; ts: number; // epoch ms, stamped at commit time } ``` ```js // apply a ChangeSet (e.g. your own undo, or syncing remote edits) const result = await map.gm.features.applyChangeSet(changeSet, { origin: 'apply' }); // result: { applied, stale, missing } ``` `applyChangeSet` returns an `ApplyResult` describing the per-change outcome so your host can decide how to handle conflicts: | Field | Meaning | |:------|:--------| | `applied` | Changes applied successfully. | | `stale` | Target already in the expected post-state (e.g. create of an existing id). | | `missing` | Target feature not found (e.g. update/delete of a missing id). | --- ## Importing Geojson > Import existing GeoJSON data into the map using importGeoJsonFeature and importGeoJson methods. **URL:** https://geoman.io/docs/maplibre/importing-data # Importing existing Geojson data Often you will want to import existing Geojson data into your map. This can be done with the `gm.features.importGeoJsonFeature`. This method accepts a Geojson object and adds it to the map. For FeatureCollections, you can use the `gm.features.importGeoJson` method. This will add all features in the collection to the Geoman instance and to the map. ## Example usage In this case we have hardcoded a Geojson feature in the `export const demoFeature: Array = [ ` const. This could be fetched from an API or a file or a database. ```js export const demoFeature: GeoJsonImportFeature = { type: 'Feature', properties: { shape: 'polygon', }, geometry: { type: 'MultiPolygon', coordinates: [ [ [ [ -8.151855468751137, 49.446665467090696, ], // ... ] ] ] } } // ... more features ``` The `gm.features.importGeoJsonFeature` method is then called to add the feature to the map: ```js gm.features.importGeoJsonFeature(demoFeature); ``` ### Adding a Geojson FeatureCollection If your data was formatted as a FeatureCollection, you need either import the entire collection using `importGeoJson`, or you need to extract the individual features and call `importGeoJsonFeature` on each feature: ```js const fc = { type: 'FeatureCollection', features: [ { type: 'Feature', properties: { shape: 'polygon', }, geometry: { type: 'MultiPolygon', coordinates: [ [ [ [ -8.151855468751137, 49.446665467090696, ], // ... ] ] ] } }, // ... more features ] } // Add each feature from the collection individually fc.features.forEach((feature) => { gm.features.importGeoJsonFeature(feature); }); // Or add the entire collection at once (recommended) gm.features.importGeoJson(fc); ``` ### Using the overwrite option When importing GeoJSON features that may have IDs matching existing features, you can use the `overwrite` option to replace them: ```js // Import with overwrite enabled - existing features with matching IDs will be replaced const result = await gm.features.importGeoJson(fc, { overwrite: true }); console.log(result.stats.overwritten); // Number of features that were replaced ``` When `overwrite: true` is set, existing features with matching IDs are deleted before importing the new features. The `stats.overwritten` count in the result tracks how many features were replaced. ### Using a custom ID property You can specify which property to use as the feature ID during import: ```js // Use the 'customId' property from feature.properties as the feature ID const result = await gm.features.importGeoJson(fc, { idPropertyName: 'customId' }); ``` ### Handling ID collisions When `overwrite` is not set, you can control what happens to an imported feature whose ID collides with one already present using the `onIdCollision` option: ```js // 'skip' (default): the colliding feature is not imported (counted as failed) const skipped = await gm.features.importGeoJson(fc, { onIdCollision: 'skip' }); // 'reassign': the colliding feature is imported with a fresh, unique ID so that // no feature is ever silently dropped. The assigned ID is reported in addedFeatures. const reassigned = await gm.features.importGeoJson(fc, { onIdCollision: 'reassign' }); ``` ## Full Demo example We have put together a list of examples from the Maplibre-Geoman Examples Repository that showcase how to import Geojson data into the map. All the examples import features from a fixtures file and add them to the map using the `gm.features.importGeoJsonFeature` method. See the [Examples](/examples) page for more information. ## API Reference ### ImportGeoJsonOptions ```ts interface ImportGeoJsonOptions { idPropertyName?: string; // Use a specific property as the feature ID overwrite?: boolean; // When true, replace existing features with matching IDs // How to handle a feature whose ID collides with an existing one (when overwrite is not set): // - 'skip' (default): the colliding feature is not imported (counted as failed) // - 'reassign': the colliding feature is imported with a fresh unique ID onIdCollision?: 'skip' | 'reassign'; } ``` > **Note:** `importGeoJson` and `importGeoJsonFeature` are asynchronous and return a > `Promise`. `await` them (or use `.then()`) when you need the import result or want to > ensure the features are added before continuing. ### GeoJsonImportFeature ```ts interface GeoJsonImportFeature { type: 'Feature'; properties: { shape: string; [key: string]: any; }; geometry: { type: string; coordinates: any[]; }; } ``` ### Import Result ```ts interface ImportGeoJsonResult { stats: { total: number; // Total features processed success: number; // Successfully imported features failed: number; // Features that failed to import overwritten: number; // Features replaced (when overwrite: true) }; addedFeatures: Array; // The imported FeatureData instances } ``` --- ## Keyboard Shortcuts ⭐ > Container-scoped keyboard shortcuts for undo, redo, delete-selection, and cancel. **URL:** https://geoman.io/docs/maplibre/keyboard-shortcuts Geoman ships with container-scoped keyboard shortcuts for the most common editing actions. They are configured through `settings.keyboard` and exposed on the instance as `gm.keyboardControls`. Handlers are scoped to the map container and ignore events that originate from form inputs, so they never hijack your application's own shortcuts. ## Settings ```typescript // Editor keyboard actions that can be bound to key combinations. type KeyboardAction = 'undo' | 'redo' | 'deleteSelected' | 'cancel' | 'nudge' | 'finishShape'; interface KeyboardSettings { enabled: boolean; // default: true bindings: Partial>; nudgeStepPx: number; // default: 2 } ``` ### Default bindings | Action | Default bindings | Description | |:-------|:-----------------|:------------| | `undo` | `['mod+z']` | Undo the last transaction (see [History](/history)). | | `redo` | `['mod+shift+z', 'mod+y']` | Redo the last undone transaction. | | `deleteSelected` | `['Delete', 'Backspace']` | Delete the currently selected features. | | `cancel` | `['Escape']` | Cancel the current draw/edit operation. | | `nudge` | Arrow keys, with optional Shift | Move selection 2 pixels; Shift multiplies by 10. | | `finishShape` | `['Enter']` | Complete a valid in-progress line/polygon. | ## Binding syntax Bindings are combo strings like `'mod+shift+z'`: - `mod` resolves to **⌘ (Cmd)** on macOS and **Ctrl** elsewhere. - The final token is the `KeyboardEvent.key` value (case-insensitive), e.g. `'Escape'`, `'Delete'`, `'z'`. - Each action accepts an **array** of bindings, so you can map several key combos to the same action. ## Example ```typescript const gm = new Geoman(map, { settings: { keyboard: { enabled: true, bindings: { undo: ['mod+z'], redo: ['mod+shift+z', 'mod+y'], deleteSelected: ['Delete', 'Backspace'], cancel: ['Escape'], }, }, }, }); ``` To disable all shortcuts, set `settings.keyboard.enabled` to `false`. To remove a single action, set its bindings to an empty array, e.g. `cancel: []`. --- ## Modes Handling > Enable, disable, and toggle draw, edit, and helper modes using the mode handling API. **URL:** https://geoman.io/docs/maplibre/mode-switching # Mode Handling All available modes can be enabled, disabled, or toggled using the following methods: For example, to handle the `edit:drag` mode: ```typescript // enable mode map.gm.options.enableMode('edit', 'drag'); // disable mode map.gm.options.disableMode('edit', 'drag'); // toggle mode map.gm.options.toggleMode('edit', 'drag'); // check mode state map.gm.options.isModeEnabled('edit', 'drag'); ``` TypeScript support is available, so you can see all available options for each method in Geoman. See also the [Geoman Instance API](/geoman-instance-api) page for more information on the available methods. --- ## Programmatic Editing ⭐ > Undoable geometry and attribute operations, validation, grid snapping, buffering, and WKT. **URL:** https://geoman.io/docs/maplibre/programmatic-editing Pro 0.11 lets your own UI drive `gm.edit`. In-place edits record history and emit `gm:edit`, then `gm:editend`, so the same persistence handler can cover interactive and programmatic changes. ```typescript gm.edit.setGeometry(featureId, geometry); gm.edit.setProperties(featureId, properties); gm.edit.repairGeometry(featureId); gm.edit.snapToGrid(featureId, 0.00001); gm.edit.buffer(featureId, 10, { units: 'meters' }); ``` Inspect operation results and listen for rejection events before presenting an edit as successful. Repair/snap operations that change nothing do not add an unnecessary history entry. Buffering in place requires a polygon and rejects collapsed results. ## Pure geometry utilities Import these from `@geoman-io/maplibre-geoman-pro`: | Utility | Purpose | | --- | --- | | `validateFeatureGeometry(geometry)` | Report too few vertices and polygon self-intersections | | `repairFeatureGeometry(geometry)` | Remove duplicate vertices and split self-intersections into valid parts | | `snapGeometryToGrid(geometry, size, origin?)` | Round geometry coordinates | | `snapLngLatToGrid(lngLat, size, origin?)` | Round one coordinate | | `bufferGeometry(geometry, distance, options?)` | Produce a Polygon/MultiPolygon area, or null if it collapses | | `geometryToWkt(geometry)` | Serialize geometry as WKT | | `wktToGeometry(wkt)` | Parse WKT, returning null for malformed input | Grid size and origin use coordinate units (degrees), not metres. Interactive grid snapping is enabled with `settings.gridSnap = { size, origin? }`. Buffer distance defaults to metres; negative values shrink. Use the pure buffer utility for points/lines, then explicitly add its resulting area to your app. WKT uses `@terraformer/wkt`, including its supported empty geometry behavior. ## Merge and explode attributes ```typescript const gm = new Geoman(map, { settings: { mergeAttributeStrategy: 'union' }, }); gm.edit.merge(featureIds, { attributes: 'primary' }); ``` `primary` (default) keeps the first feature's properties. `union` combines properties with the primary feature winning collisions. `none` starts with no custom properties. Explode copies source attributes onto every part; generated features always receive fresh identities. ## Persist compound operations once Explode, merge-parts, and split emit granular create/remove events plus lineage events. Lineage payloads always use `originalFeatures` and `features` arrays. Choose one persistence channel to avoid double-counting; set `settings.compoundLineageEvents: false` for granular events only. --- ## Upgrading to Pro 0.11 > Stable release notes and migration guide for Geoman Pro 0.11. **URL:** https://geoman.io/docs/maplibre/release-0.11 # Geoman Pro 0.11.0 This stable release promotes the editor-platform work from the 0.9.2 and 0.10.0 prereleases, including all changes through 0.10.0-alpha.2. The summary below covers the upgrade from the previous stable release, 0.9.1; the [repository changelog](https://github.com/geoman-io/maplibre-geoman-pro/blob/master/CHANGELOG.md) retains the detailed prerelease history. ### Breaking changes and migration - **MapLibre GL JS 6 is required** (`>=6.0.0 <7.0.0`); this release is tested with 6.7.0. Configure `setWorkerUrl()` before constructing a map. With Vite: ```typescript import * as maplibregl from 'maplibre-gl'; import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'; maplibregl.setWorkerUrl(workerUrl); ``` - **MapLibre Pro is ESM-only.** Replace CommonJS/UMD loading with ESM imports. MapLibre 6 has no default export; replace default imports with namespace or named imports. See [Basics](/basics) for worker setup. The Mapbox Pro variant continues to ship ESM and UMD. - Compound lineage events `gm:explode`, `gm:merge_parts`, and `gm:split` always use `originalFeatures` / `features` arrays. Migrate handlers that read the singular payload. These events accompany granular create/remove events; subscribe to one persistence channel, or set `compoundLineageEvents: false`. - For prerelease consumers: data layers support editable, display, and adopted external layers; the experimental `interactive` kind was removed. `dataLayers.setData()` is awaitable. Layer specs are shallow copies with host-owned metadata retained by reference. The unused schema `default` field was removed; presentation metadata does not generate UI or styling. ### Added - **Named data layers** through `geoman.dataLayers`: add/remove/list/get, replace data, update styles and visibility, select the active draw target, switch editable/display state, adopt host-owned map layers, and reorder owned layers. Each layer can restrict geometry types and define a feature schema. - **Thematic styling** with `get`, `categorical`, `step`, and `interpolate`, plus raw renderer-expression arrays. Fill, line, point opacity, and optional attribute labels update at runtime. Labels require glyphs in the map style; raw expressions are renderer-specific. - **Programmatic undoable editing**: `geoman.edit.setGeometry()`, `setProperties()`, `repairGeometry()`, `snapToGrid()`, and `buffer()`. In-place edits emit `gm:edit` followed by `gm:editend`. - **Geometry utilities**: validation, repair of duplicate vertices and self-intersecting polygons, coordinate-grid snapping, positive/negative buffering, and WKT import/export backed by `@terraformer/wkt`. Geometry and schema enforcement are opt-in via `validateGeometry` and `validateSchema`. Grid size/origin use coordinate units (degrees). - **Public geofencing API**: containment and intersection-restriction setters, getters, and clear. Constraints reference live features; rejected operations emit `gm:geofencing_violation`. - **Keyboard editing**: arrow-key nudging (2 pixels by default, tenfold with Shift) and Enter to finish line/polygon drawing. Shortcuts are map-container scoped and guard form inputs; bindings can be configured partially. - **Accessibility and localisation**: polite screen-reader announcements for selection changes and rejected operations, `announcementResolver`, and `selectionMessageResolver` for disabled-control guidance. Announcements can be disabled with `announceActions: false`. - **Merge attribute policy**: `mergeAttributeStrategy` supports `primary` (default), `union`, and `none`; override per call with `geoman.edit.merge(ids, { attributes })`. Exported policy types, constants, and `mergeFeatureProperties` support host integrations. - **Lifecycle event** `gm:unloaded` on destruction. - Source-only draft Mapbox React bindings mirror the MapLibre hooks and controls, with generated-source drift checks. These wrappers are not newly published packages in this release. ### Fixed - Features retain their home data layer across draw, drag, rotate, scale, vertex editing, copy, split, union/difference, merge/explode, and undo/redo. Removing a layer during editing no longer lets its features escape to main. - Copy creates a fresh feature identity. Explode preserves source attributes on every part; merge preserves attributes according to the selected policy. Compound history remains atomic. - Data replacement and display-to-editable conversion validate before mutation. Geometry restrictions and opt-in schema checks apply across API entry points. Undo/redo can restore historical data after constraints change. - Runtime styles reset omitted properties, reconcile label layers, filter paint by geometry type, sort numeric stops, and handle empty thematic expressions. Categorical styling correctly matches numeric values and numeric-looking keys. - Source teardown cleans pending updates and timers; transactions cover dynamic data-layer sources; disabling the active editable layer clears the draw target. - Multi-feature events await all affected data sources. Host listener exceptions are isolated; right-click vertex deletion emits a complete edit lifecycle. - Keyboard nudges account for each feature's Mercator location. Tiny grid sizes retain precision, Enter reports invalid incomplete shapes as a no-op, and repeated announcer startup does not duplicate live regions. - MapLibre 6 source updates use the current promise API. Layer property setters and the event bridge support stricter types while retaining custom event payloads and listener identity. ### Dependencies, builds, and release tooling - Update MapLibre GL JS to 6.7.0, Mapbox GL JS to 3.30.0, Turf to 7.4.0, and compatible build/test dependencies under the seven-day dependency cooldown. - Retain TypeScript 6.0.3 and tsc-alias 1.8.17 for working preprocessing and declaration output. Refresh security fixes and transitive resolutions. - Build/typecheck cache keys include shared core sources and root TypeScript configuration. CI runs on master pushes and verifies variant-specific bundle formats; MapLibre artifact validation no longer expects a UMD bundle. - React binding synchronisation detects nested changes and stale generated files. Browser tests wait for rendered features before dragging data layers. [Full comparison with 0.9.1](https://github.com/geoman-io/maplibre-geoman-pro/compare/v0.9.1...v0.11.0) --- ## Selection ⭐ > The always-on global selection, its overlay rendering, and how to read and drive it programmatically. **URL:** https://geoman.io/docs/maplibre/selection Geoman maintains a **global selection** of features that stays visible regardless of the active mode. The selection drives selection-gated controls (`add_hole`, `add_part`, `merge_parts`), the always-on highlight overlay, and the `deleteSelected` keyboard action. ## Settings | Setting | Default | Description | |:--------|:--------|:------------| | `renderSelection` | `true` | Render the always-on overlay that outlines the globally-selected features in any mode (and, for polygons, optionally tints them) via the `SelectionRenderer`. Set to `false` if your app draws its own selection cue. | | `clearSelectionOnBackgroundClick` | `true` | Clear the selection when the user clicks the empty map background (no Geoman feature/marker/overlay under the cursor). Set to `false` to keep the selection until explicitly changed. | | `disableSelectionGating` | `false` | When `true`, controls that require a selection are never shown disabled — for hosts that manage availability themselves. The modes still validate at run time. | ```typescript const gm = new Geoman(map, { settings: { renderSelection: true, clearSelectionOnBackgroundClick: true, disableSelectionGating: false, }, }); ``` The overlay colours and widths are configurable through the `highlightSelected*` style variables — see [Style Variables](/configuring-geoman#default-style-variables). ## Reading and changing the selection The selection lives on the features instance as a `Set` of feature IDs: ```js // current selection (Set) map.gm.features.selection; // set the selection programmatically map.gm.features.setSelection(['feature-1', 'feature-2']); // clear the selection map.gm.features.clearSelection(); ``` ## Selection events | Event | Fired | Output | |:------|:------|:-------| | `gm:selection` | Whenever the global selection changes. | `selection` (array of `FeatureId`), plus the originating system event | ```js map.on('gm:selection', (event) => { console.log('selected feature ids:', event.selection); }); ``` See [Events](/events) for the full event reference and the [Select edit mode](/edit-modes/edit-select) for the interactive selection tool. ---