GeoJSON import and export
Both functions live in @vectorstream/core and work on one partition — one layer.
Importing#
importGeoJSON(featureCollection, options?) builds a new partition. To edit it, pass it to a
new editor (or into a layer of one); an existing editor's partition is not replaced.
What it accepts: a FeatureCollection of Polygon (with holes), LineString and Point
features. A feature with geometry: null carries the attributes of the outer face. Other geometry
types (MultiPolygon, MultiPoint, …) are rejected. Positions need at least two finite numbers;
further numbers (elevation) are ignored.
What it expects: cleanly noded data. Shared boundaries must have bit-identical coordinates,
and edges must not cross. Identical coordinates become one node, identical segments one edge — so
two parcels that share a boundary share an edge after import. The import checks the structure
(Euler's formula and similar) and, by default, the geometry: it runs checkGeometry on the result
and throws an ImportError ('core.importInvalidGeometry', findings in violations) for overlapping
areas, crossing edges, or a line ending in the middle of a foreign edge. For trusted documents (for
example your own exportGeoJSON output) the check can be switched off with geometriePruefen: false;
see Validity and rejections.
What happens on the way in:
- Coordinates are rounded onto the partition's grid.
- Properties become attributes of the element the feature describes: a polygon's go to its face,
a line's to every one of its edges, a point's to the node at that position (also a vertex in the
middle of a boundary). Values that are not
string,number,booleanornullare skipped. - A property named
idis skipped: IDs are assigned fresh on every import. - If two features describe the same element, their properties are merged. If they give one key different values, the first one stays and a warning is reported.
| Option | Type | Default | Meaning |
|---|---|---|---|
gridExponent |
number |
26 |
Grid mesh size 2−gridExponent. |
geometriePruefen |
boolean |
true |
Run checkGeometry on the result and throw an ImportError with the findings if there are any. false skips the check (trusted data); the partition may then carry geometry problems, which the editor tolerates as legacy. Cost: about +50% of the import time (2025 areas: 20 → 31 ms). |
onWarnung |
(warning: Importwarnung) => void |
— | Called for properties that could not be placed (art: 'nicht-zugeordnet') for conflicting values (art: 'widerspruch'), and for internal vs: keys on a feature with geometry, which are dropped (art: 'intern'; such keys belong on the layer's geometry: null feature only). Each warning has detail, code and params. Without it, warnings are dropped. |
Errors are thrown as ImportError with a stable code — 'core.importNoGeometry',
'core.importUnsupportedGeometry', 'core.importPositionNotFinite', 'core.importRingTooFewNodes',
'core.importInvalidPartition', 'core.importInvalidGeometry' and others — plus params and a violations list. The message
is in the current language.
import {
ImportError,
exportGeoJSON,
importGeoJSON,
type FeatureCollection,
type Importwarnung,
type Partition,
} from '@vectorstream/core';
import type { Editor } from '@vectorstream/editor';
export type LoadResult =
| { ok: true; partition: Partition; warnings: Importwarnung[] }
| { ok: false; code: string; message: string };
/** Builds a partition from GeoJSON that is already cleanly noded (shared boundaries bit-identical). */
export function loadGeoJSON(text: string): LoadResult {
const warnings: Importwarnung[] = [];
try {
const data = JSON.parse(text) as FeatureCollection;
const partition = importGeoJSON(data, { onWarnung: (w) => warnings.push(w) });
return { ok: true, partition, warnings };
} catch (error) {
// `code` is stable ('core.importNoGeometry', …); `message` is in the current language.
if (error instanceof ImportError) return { ok: false, code: error.code, message: error.message };
throw error; // e.g. a SyntaxError from JSON.parse
}
}
/** One FeatureCollection per layer: polygons, then lines, then points, each with an `id`. */
export function saveLayers(editor: Editor): Record<string, FeatureCollection> {
return Object.fromEntries(editor.ebenen.map((layer) => [layer.id, exportGeoJSON(layer.partition)]));
}
Its test loads the sample parcels (6 nodes, 7 edges — the shared boundary once), checks the error
codes for a feature without geometry and for a MultiPoint, the warning for one polygon given
twice with different values, and the export below.
Exporting#
exportGeoJSON(partition) returns a FeatureCollection:
- one
Polygonper bounded face — outer ring first, then holes; - one
LineStringper line: a chain of edges with the same face on both sides and the same attributes is one feature, not one per segment; - one
Pointper free-standing node, and per vertex that carries attributes.
Each group is ordered by ID. Every feature's properties are its attributes plus id — the
element ID ('f2', 'h9', 'n8'), set last so an attribute named id cannot override it. The
attributes of the outer face, if any, are exported as a feature with geometry: null.
Coordinates are the grid-rounded values (51.51 comes out as 51.51000000536442). Exporting,
importing and exporting again gives the same geometry and attributes, with IDs that may differ —
the example's test checks this for a polygon, a line and a point.
For several layers, export each layer's partition — saveLayers in the example above returns one
collection per layer ID.
API: importGeoJSON, ImportOptions,
ImportError, exportGeoJSON.