Documentation

Pro handbook

Changelog

Unreleased#

No version has been released, and the packages are not yet published on npm. All seven packages carry the placeholder version 0.0.0.

Until the first release, the source of truth is this handbook together with the API reference, both describing the code as it is in this repository. Breaking changes can happen at any time before the first release.

Recent changes affecting the public API#

  • The packages are built for release, and the license key exists. Each package now builds to dist/ (ES modules, type declarations, source maps; npm run build), and its exports carry the conditions source, types and import. The packages still carry "private": true and the version 0.0.0; nothing is published. @vectorstream/pro checks a signed license key offline and shows a watermark without a valid one; no function depends on the key. See Free and Pro and Extensions; the key is described in Setting up the license key.

  • Free and Pro are separate packages. Everything the free package (@vectorstream/editor, MIT) does not do on its own now lives in @vectorstream/pro: the ten rule kinds beyond 'innerhalb' and 'ueberlappungsfrei', attribute conditions, rules in meters, the red preview, snapping onto target layers, the incremental check of rules, the circle as a cutting shape (CutCircleMode, DifferenceCircleMode) and the list of violations (@vectorstream/pro/ui). The editor takes extensions through EditorOptions.erweiterungen and erweiterungGlobalAnmelden; OHNE_GLOBALE builds an editor that ignores the announced ones; editor.regelarten() lists the kinds of an editor. Removed from the free package: ARTEN, REGELARTEN, EingebauteArt and bedingungErfuellt (now in @vectorstream/pro), CutCircleMode and DifferenceCircleMode, Vorschaupruefung and the two circle entries of defaultToolbarEntries() (21 entries; @vectorstream/pro brings mitProWerkzeugen). The pure check functions (pruefeRegel, regelRoh …) know the two free kinds when no registry is handed in. Documents with rules of the other kinds lose nothing in the free editor (see Compatibility).

  • Rules of a kind the editor does not know are kept. A stored rule is read in two stages (envelope, then kind). One whose kind is not registered, or that has a condition no extension can evaluate, is listed by editor.regeln() with status: 'benoetigtErweiterung' and its stored text unchanged; it is never checked, can be removed but not changed (ruleNeedsExtension), and the import of the application no longer drops it. New exports: huelleRoh, ersetzeZiel, PRO_ARTEN, the types Fremdregel, Regeleintrag, Regelhuelle. editor.regeln() now returns Regeleintrag[]. See Compatibility.

  • Minimum width finds long convex slivers. When the cell search of 'mindestbreite' runs out of its 20 000 cells, the convex hull of the outer ring decides: a hull narrower than the circle is reported. Rules that used to let a long thin strip or triangle pass now report it; nothing new is reported that the circle would fit into. See Minimum width.

  • Violations box above the layer tree in the application: always in view, the tree scrolls in itself. See The list of violations.

  • Attribute keys named like Object.prototype members. A GeoJSON property called __proto__ used to be dropped silently on import; it and constructor, prototype, toString and the like are now ordinary attributes (import, SETATTR, undo/redo, export, saved documents). Attribute sets are objects with a null prototype, so attributeVon(p, id) has no inherited members ('constructor' in set is false) and Object.prototype cannot be reached through an attribute. New helpers neuerAttributsatz, setzeSchluessel, hatSchluessel for code that builds attribute records. See Labels and attributes. Breaking for code that treats a set like a plain object: set.hasOwnProperty(...), String(set) and ${set} now throw a TypeError; use Object.hasOwn(set, key) or hatSchluessel(set, key). Object.assign({}, set) loses a __proto__ key (it uses the setter); spread, Object.entries / Object.fromEntries, structuredClone and JSON keep it.

  • Minimum distance near the poles. 'mindestabstand' no longer misses a pair at a large longitude difference close to a pole (see Meters).

  • importGeoJSON checks the geometry. New option ImportOptions.geometriePruefen (default true): the result is run through checkGeometry, and findings (overlapping areas, crossing edges, a node on a foreign edge) throw an ImportError with code 'core.importInvalidGeometry' and the findings in violations. Pass geometriePruefen: false for trusted documents or to open legacy data. Behaviour change: data that used to import (and later broke an edit) is now rejected. See GeoJSON.

  • Red preview. With an active rule on the active layer, the preview of the drawing and editing tools turns red when the next action would be refused; violating legacy is drawn as invalid. See The red preview.

  • Showing an element on the map. Adapter.bereichZeigen(bereich, randPx?) (all three map adapters and NullAdapter.gezeigteBereiche), the type Bereich, and editor.zeigeElement(layer, element). A custom Adapter must implement the new method. See Rules between layers.

  • More kinds of rule between layers. 'beruehrt', 'beruehrungsfrei', 'schneidend', 'abdeckung', 'lueckenlos', 'aufLinie', 'aufRand', 'mindestabstand', 'mindestflaeche', 'mindestbreite'; a measure mass and an attribute condition bedingung per rule; Regel.ziel can be null; Regelverstoss.elementEbene; ARTEN, Bedingung, bedingungErfuellt, Laengenmass, laengenmassAus. Older builds read the new kinds as unreadable. See Rules between layers.

  • Package names. The packages are named @vectorstream/core, @vectorstream/adapter, @vectorstream/editor, @vectorstream/toolbar-ui, @vectorstream/leaflet, @vectorstream/openlayers and @vectorstream/maplibre.

  • @vectorstream/toolbar-ui is new: ready-made toolbar buttons built on Toolbar — groups, separators, the tool icons, and on/off toggles for the snapping switches — for an application that would rather not draw its own (see Toolbar). Optional and separate from @vectorstream/editor; Toolbar itself still draws nothing.

  • Language. User-visible texts come from message tables; English is built in, German is opt-in through registerLocale (see Language). Rejections carry a namespaced code and params; the former MELDUNG_* text exports were removed.

  • Transaction labels are stable English identifiers in kebab case ('draw-polygon', 'merge-faces', …), see Events.

  • Lines are inserted exactly. insertLine accepts only tolerance 0 (or none) and throws a RangeError otherwise; inserting the same line again applies no operation.

  • Rotation pivot. TransformMode.setzeDrehpunkt(point | null) fixes the center the next rotation drag turns around, in place of the selection's bounding-box center. Programmatic only — no new handle; the pivot is consumed by the next rotation grab, and the one after that falls back to the center again (see Modes, section transform).

  • Utilities page. A new Utilities page collects the pure, standalone helper functions each package already exports. New export: kreisPolygon (@vectorstream/core) — the circle/ellipse n-gon DrawCircleMode draws with, now a function of its own instead of living only inline in that mode.

  • Circle as a cutting shape. Two new modes, CutCircleMode (key H) and DifferenceCircleMode (key J), the circle variants of cut/difference (see Modes): drag a circle instead of placing a ring, same carve otherwise. New transaction labels 'cut-circle' and 'difference-circle'. The semi-axis computation, drag state, and measurement display DrawCircleMode already had are now shared with the two new modes instead of duplicated, via an internal module — not a public export of @vectorstream/editor.

  • Image export. New Adapter.exportImage(): Promise<Blob>, the same call on every renderer — a PNG of the current view at screen resolution, background tiles included, with the same CSS tile darkening the screen shows and the map's attribution line drawn into the bottom-right corner (see Exporting the map view as an image). Breaking for a custom implementation of Adapter (review finding M7): exportImage is a required method, same as every other one on the interface — before the first release, adding one without a minor/major bump is accepted the same way earlier Adapter additions were. Leaflet redraws faces/edges/points/labels from the current Scene; MapLibre does the same for points and labels only (faces/edges stay native); OpenLayers composites its own layer canvases (new exports of @vectorstream/adapter: zeichneFlaechenAufCanvas, zeichneKantenAufCanvas, ladePunktBilder/zeichnePunkteAufCanvas — now split into an async preload step and a synchronous draw step, review findings N3/N4 — zeichneBeschriftungenAufCanvas, zeichneAttributionAufCanvas, pngBlobVonCanvas, alsPngExport). Handles, snap markers and guide lines never appear in the image on any of the three: on OpenLayers/MapLibre, the layer they'd otherwise share with free-standing points is hidden, redrawn, read and restored in one synchronous sequence (no await anywhere in it) and only then is the canvas handed back — nothing can observe the hidden frame, and two overlapping exportImage() calls on the same adapter can no longer leave that layer hidden for good. A tainted background (no CORS permission on the tile server) rejects with the new ExportError — adapter.exportCorsBlocked at toBlob/toDataURL/getImageData, where tainting actually throws (not at drawImage, which only marks the canvas, silently); adapter.exportFailed for every other encode failure. The app's own Leaflet map now sets crossOrigin: true on its tile layer accordingly.

  • Customizing the look. New darstellung option on LeafletAdapterOptions, OpenLayersAdapterOptions and MapLibreAdapterOptions (see Customizing the look): a partial, per-field override of the editor's colors and line styles, merged with the built-in defaults once, at construction. New exports of @vectorstream/adapter: baueDarstellung, mitUeberschreibung, Darstellung, DarstellungUeberschreibung, TeilUeberschreibung, STANDARD_DARSTELLUNG, DarstellungFehler (code adapter.darstellungUnbekannterSchluessel for an unknown state/field, thrown at construction) — plus BESCHRIFTUNG_FARBE/BESCHRIFTUNG_GROESSE, now exported for the same reason every other style table already was. griffDarstellung, punktDarstellung, hilfslinienStil, beschriftungsStil, schildRahmenAus, zeichneFlaechenAufCanvas, zeichneKantenAufCanvas and zeichnePunkteAufCanvas/zeichneBeschriftungenAufCanvas all take the resolved table set as a new, optional trailing parameter (default: the built-in appearance) — existing callers that never pass one see no change. exportImage draws from the same override the map itself uses. Not overridable, by design: the icon-based symbols (snap markers, the rotate/scale handles' own icon, the pin shape's color) — already distinguished by shape rather than color alone. New type export: Weiten (widens the two as const single-object tables' literal types for the override). Fix round 1 (review findings I1/I2/M1/M2/M3/M5/M7/M9): a dash pattern (strich) is now overridable on every state the style type allows, not only states whose default already has one; an explicit override of flaechen.normal/kanten.normal now always wins over a per-layer color (ebenenFarbe) for the fields it names, documented and tested; an explicitly undefined field no longer blanks out the default; validation rejects inherited/prototype keys and non-object values instead of silently accepting or ignoring them; Darstellung's keyed fields are now typeof <their default table> instead of a second, hand-written key union; mitUeberschreibung copies an override's array fields instead of keeping them by reference, and every default table (plus STANDARD_DARSTELLUNG) is deep-frozen; the exported image's attribution line now follows an overridden font family; an enlarged circle handle radius now enlarges its own hit area too (never shrinks it below the ordinary tolerance). Fix round 2 (re-review finding N1, the real bug, plus M3/M5/M10): strich: [] — the documented way to force a solid line on a state whose default is dashed — made the line disappear entirely on MapLibre (linienWerte's coalesce kept the feature's own empty array instead of falling back to the solid pattern); fixed, with a pixel test, and confirmed (new tests) that Leaflet, OpenLayers and the canvas export were never affected. ladePunktBilder lost its darstellung parameter — it never actually consulted it (pin selection depends only on ScenePoint.form, and the pin shape isn't themable) — removed rather than kept "for symmetry" with no effect. Handbook: the hilfslinien.invalid "becomes overridable automatically" claim is corrected — SceneGuide has no state field, so that one needs a design decision and code change, unlike kanten.invalid/punkte.invalid; and a new note that override values reach cssText unchecked (familie, hintergrund), so an application should never pass through untrusted input there.

  • Large data sets (see Large data sets). Measured first: 50,000 points plus a 150 × 150 parcel grid cost 52–64 ms per scene without handles, three to four 60-Hz frames. Two measures, both automatic: the editor builds the scene only from what lies in the visible map area (plus half a view of margin, selected elements always included), and below zoom 15 it leaves node and midpoint handles out (selected ones excepted) — free-standing points are unaffected, see the fix-round note below. With a 20 × 20-parcel window the scene costs 1.2 ms. New, all additive: optional Adapter.viewport?() and the Viewport type, implemented by all three adapters and settable on NullAdapter (viewport option, setViewport()); sichtfeldAusEcken (@vectorstream/adapter); SpatialIndex.facesInBox() and SpatialIndex.extent() (@vectorstream/core); SceneOptions.viewport/index, EbenenSzeneOptions.viewport/indexFuer and the constant GRIFFE_AB_ZOOM (@vectorstream/editor, renamed from PUNKTE_AB_ZOOM in fix round 1, see below). Behavior change: on a Web Mercator map below zoom 15 only selected nodes can be grabbed, because their handles are the only ones in the scene. Fix round 1 (review findings I1–I5): MapLibreAdapter.viewport() now reports map.getBounds() instead of projecting the four container corners by hand — past about 72° pitch (reachable once an application raises maxPitch beyond the default 60) a corner above the horizon projected to a point behind the camera, and the box built from it excluded most of what was actually on screen; OpenLayersAdapter.viewport() now derives its zoom from the view's resolution (Math.log2(156543.03392804097 / view.getResolution())) instead of trusting View.getZoom(), which is only the Web Mercator tile level for OpenLayers' own default resolution ladder and becomes an index into a custom resolutions array (the WMTS-typical case) otherwise. Below GRIFFE_AB_ZOOM, EditMode now suppresses the edge grab a click near an unselected, hidden corner's position used to fall through to — such a click does nothing now, matching what the handbook always claimed, instead of silently moving the whole straight line through that corner. Scope correction: free-standing points are no longer part of this threshold at all — the renamed constant now hides handles only; points were hidden in the first version of this feature despite the feature's own benchmark showing they are cheap (user ruling of 13.10.2026, see Large data sets).

  • Three more languages: French, Spanish, Dutch. Short draft gap 7, "Weitere Sprachen", user decision of 12.10.2026: complete tables for @vectorstream/core, @vectorstream/editor, @vectorstream/adapter and the app, the same four packages German already covers — new exports coreFr/editorFr/adapterFr, coreEs/editorEs/adapterEs, coreNl/editorNl/adapterNl (app's own appFr/appEs/appNl are not published, same as appDe), registered the same way (registerLocale(tag, messages, table)) and checked the same way (defineTranslation) as German — see Language. Machine-translated, not reviewed by a native speaker (the user's own explicit, informed risk acceptance, not an oversight): every key, placeholder and plural form is still checked by the compiler and by tests the same way German's is, but wording may read oddly until someone who speaks French, Spanish or Dutch looks it over. French's own plural rule is the one surprise worth knowing before extending any of the three further: Intl.PluralRules('fr') selects "one" for both n = 0 and n = 1, not only n = 1 as German, English, Spanish and Dutch all do; French (and, separately, Spanish) also has a "many" category for round millions that every French/Spanish plural entry here leaves out on purpose, since render() already falls back to "other", the correct text for that case too. The handbook's "your own translation" example no longer uses French for its hypothetical custom, partial table (confusing now that French is a real, complete, built-in one) — it uses Italian instead.

  • Switchable snap-target kinds. New editor.rastArten: readonly Rastart[] (default all four, in rank order: corner, edge midpoint, quarter point, edge), the global setting every tool falls back to when it leaves the choice open on its own ctx.rasten(...) call (see Snapping). An empty array switches snapping off kind by kind, the same effect rastenAktiv = false has all at once. Short draft gap 5, "Einrast-Ziele einzeln schaltbar", user decision of 12.10.2026: one global setting for every tool, all four kinds individually switchable — not three as in Geoman, which has no quarter point.

See it on a map.