Getting started with Leaflet
Installation#
Not yet published on npm. The command below is what installation will look like once the packages are released. Until then, work inside this repository (a pnpm workspace) or link the packages from it. Their entry points are TypeScript sources, so your bundler must compile TypeScript (Vite does).
npm install @vectorstream/core @vectorstream/editor @vectorstream/leaflet leaflet
leaflet is a peer dependency (^1.9.4); @vectorstream/adapter comes along with
@vectorstream/leaflet.
The complete example#
The example below builds everything an editing application needs. It is a real file of this
repository (docs/handbuch/beispiele/leaflet-quickstart.ts); a test draws a triangle through it,
exports, undoes and switches tools by key.
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { exportGeoJSON, importGeoJSON, type FeatureCollection } from '@vectorstream/core';
import { Editor, Toolbar, defaultToolbarEntries } from '@vectorstream/editor';
import { LeafletAdapter } from '@vectorstream/leaflet';
export function startEditor(container: HTMLElement, data: FeatureCollection) {
// 1. The map. x is longitude, y is latitude; Leaflet takes [lat, lng].
const map = L.map(container).setView([51.5112, 7.4555], 17);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap contributors',
}).addTo(map);
// 2. The adapter draws the editor's scene on the map and reports pointer and key events.
const adapter = new LeafletAdapter(map);
// 3. The editor works on a partition — here one built from GeoJSON.
const editor = new Editor(importGeoJSON(data), adapter);
// 4. The toolbar is state, not UI: it knows the tools and their shortcut keys.
const toolbar = new Toolbar(editor, defaultToolbarEntries());
toolbar.select('draw');
// 5. Every transaction ends in exactly one of three events.
editor.on('change', ({ label }) => console.log('changed:', label));
editor.on('unchanged', ({ label }) => console.log('nothing to do:', label));
editor.on('rejected', ({ label, violations }) => {
console.warn('rejected:', label, violations.map((v) => v.detail));
});
return {
map,
editor,
toolbar,
// 6. Export the active layer as GeoJSON.
save: (): FeatureCollection => exportGeoJSON(editor.partition),
// 7. Undo and redo; `false` means there was nothing to do, or the step was rejected.
undo: (): boolean => editor.undo(),
redo: (): boolean => editor.redo(),
destroy(): void {
toolbar.destroy();
editor.destroy();
adapter.destroy();
map.remove();
},
};
}
The sample data it is tested with — two adjacent parcels:
import type { FeatureCollection } from '@vectorstream/core';
/**
* Two adjacent parcels in longitude/latitude (x = longitude, y = latitude). They share the
* boundary at longitude 7.4555 — after import that boundary is one edge, not two.
*/
export const parcels: FeatureCollection = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { parcel: '101', use: 'field' },
geometry: {
type: 'Polygon',
coordinates: [[[7.4525, 51.5105], [7.4555, 51.5105], [7.4555, 51.5119], [7.4525, 51.5119], [7.4525, 51.5105]]],
},
},
{
type: 'Feature',
properties: { parcel: '102', use: 'meadow' },
geometry: {
type: 'Polygon',
coordinates: [[[7.4555, 51.5105], [7.4585, 51.5105], [7.4585, 51.5119], [7.4555, 51.5119], [7.4555, 51.5105]]],
},
},
],
};
/** A building across the boundary between parcel 101 and parcel 102. */
export const buildings: FeatureCollection = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { use: 'house', built: 1965, text: 'House 7' },
geometry: {
type: 'Polygon',
coordinates: [[[7.4543, 51.5109], [7.4567, 51.5109], [7.4567, 51.5115], [7.4543, 51.5115], [7.4543, 51.5109]]],
},
},
],
};
Step by step#
1. The map. A normal Leaflet map with its default projection. The editor computes in the
coordinates the adapter supplies: x is longitude and y is latitude. Leaflet itself takes
[lat, lng], which is why setView reads the other way round. The container must have a size,
as for any Leaflet map.
2. The adapter. new LeafletAdapter(map, options?) adds its own layer groups to the map,
listens to mouse events on the map and to keydown on the document. Keys typed into an input
field, a text area, a select box or a contenteditable element are ignored. The only option is
ebenenFarbe: (layerId) => string | undefined, the color of a layer; without it, layers get
colors from a fixed palette by their ID.
3. The editor. new Editor(partitionOrLayers, adapter, options?) takes either one partition
(as here, from importGeoJSON) or a list of layers. Options are
tolerancePx (snap radius in pixels, default 8), selection (an initial selection) and
geofencing (default true; false leaves rules between layers unchecked) and
erweiterungen (a list of extensions plugged into this editor; none by default). The editor draws
immediately.
4. The toolbar. new Toolbar(editor, defaultToolbarEntries()) knows the 23 default tools and
their shortcut keys. toolbar.select('draw') activates the polygon tool. Until a mode is set, the
editor has none and clicks do nothing. See Toolbar for drawing buttons and
Modes for the tools.
5. Events. Every edit ends in exactly one of change, unchanged or rejected. See
Events.
6. Export. exportGeoJSON(editor.partition) returns the active layer as a GeoJSON
FeatureCollection. See GeoJSON.
7. Undo. editor.undo() and editor.redo() return false when there is nothing to do or
the step was rejected. The
editor binds no keys for them; Undo and redo shows how to bind
Ctrl+Z.
Tearing down. Destroy the toolbar, then the editor, then the adapter, then the map.
editor.destroy() switches map panning back on if a mode had switched it off and resets the
cursor. adapter.destroy() removes its layers and listeners.
Leaflet specifics#
- Box zoom. Leaflet zooms to a box on Shift+drag. The cut and difference tools use Shift themselves and switch box zoom off while they are active.
- Container size changes. Leaflet only notices window resizes. If your layout changes the
map container's size, call
map.invalidateSize(); Leaflet then reports a move, the adapter a view change, and the editor redraws. - More than one editor per page. Each adapter listens to
keydownon the whole document, so two editors on one page both receive every key.
Next: Faces and edges, Modes, API reference: LeafletAdapter.