Skip to content

How to use the CLI

Getting started

Running the script with the help option diplays CLI commands and options:

xplan-tools --help
The help option can also be called for commands and sub-commands:
xplan-tools convert --help

Conversion between different encodings

XPlanung data can be converted between different encodings using the convert command, e.g. from GML to JSON-FG

xplan-tools convert input.gml output.json
The encoding is derived from the file path or DB connection string (URI format). To convert, for instance, a GML file to a GeoPackage, we run
xplan-tools convert input.gml gpkg:///output.gpkg
XTrasse data can be converted between different encodings using the convert command, together with the option --xtrasse:
xplan-tools convert input.gml output.json --xtrasse

If no output is provided, the data is written to stdout in the internal encoding.

Reading from a URL or a WFS

The input may be an http(s) URL — a plain GML or JSON-FG file, or a WFS GetFeature request. This is off by default, since a service that passes a datasource it received from elsewhere would otherwise be able to reach hosts on its own network. Turn it on per run:

xplan-tools convert --allow-remote https://example.org/plan.gml output.gml
or for the environment:
XMAS_DS_ALLOW_REMOTE=1 xplan-tools convert "https://example.org/wfs?service=WFS&request=GetFeature" output.gml
The format is identified from the response content, so a request URL without a file extension works. XMAS_DS_REMOTE_TIMEOUT (default 60 seconds) and XMAS_DS_REMOTE_MAX_BYTES (default 256 MiB) bound the request. Only http and https are ever retrieved; GDAL virtual filesystem paths (/vsicurl/, /vsizip/, /vsis3/, …) are rejected whether or not remote access is enabled.

The output target is never fetched — it is always written locally. It is also identified by its name rather than by its content: whatever is at the path is about to be replaced, so it has no say in which format is written there. Where the name does not settle it, pass the format:

Datasource("out.dat", format="gml", for_write=True).repo().save_all(collection)

In library use, allow_remote is what opts in, so the decision is visible at the call site:

from xplan_tools.interface import Datasource

ds = Datasource("https://example.org/wfs?service=WFS&request=GetFeature", allow_remote=True)
collection = ds.repo().get_all()
Nothing is sent while the Datasource is built — it only classifies the URL, and refuses it outright when remote access is off. The request happens on first read of ds.buffer or ds.format. To retrieve a URL yourself and hand the bytes on, fetch_remote does just that:
from xplan_tools.interface import fetch_remote
from xplan_tools.interface.gml import GMLRepository

buffer = fetch_remote("https://example.org/plan.gml", allow_remote=True)
collection = GMLRepository(buffer).get_all()

Documents that are refused

A GML document is checked at its root element before it is read, so an unusable file fails in milliseconds rather than after a full parse:

  • A document declaring a DOCTYPE is rejected (ForbiddenDoctypeError). XPlanung and INSPIRE never use one, and entity references are left unexpanded, so accepting a DTD would mean silently dropping element text.
  • A document whose root element is not XPlanAuszug or a WFS/OGC API Features FeatureCollection is rejected (UnsupportedRootElementError).
  • A document using the same gml:id for more than one feature is rejected (DuplicateGmlIdError). A gml:id is an xsd:ID and so unique within a document; where it is not, every #GML_<id> and urn:uuid:<id> reference to it names two features at once. Two distinct ids carrying the same UUID (GML_<uuid> and GML_<uuid>_Bereich) are fine — the second feature is given a fresh id, and references keep resolving to the one that kept its own.

Both exit with code 2 and a one-line message. Every error raised by this package derives from BaseError and carries a machine-readable code; those replacing an error from an earlier release also remain a ValueError.

Feature references

Associations between features are intra-document by design. An xlink:href is read as a reference to another feature in the same file when it is a local fragment or a UUID URN:

<xplan:gehoertZuPlan xlink:href="#GML_d83b1cb7-c702-478e-8471-adf973780873" />
<xplan:gehoertZuPlan xlink:href="urn:uuid:d83b1cb7-c702-478e-8471-adf973780873" />
Both forms are written back out as the #GML_<uuid> shorthand.

Anything else is rejected, in one of two ways:

  • An absolute URI (https://example.com/plan) is a well-formed but external reference. It is read, and the collection then reports it as unsupported, together with any reference whose UUID names no feature in the file.
  • Any other href - a relative path, a cross-document reference such as other.gml#GML_<uuid>, or a bare GML_<uuid> - is malformed and fails while the feature is being read, naming the class and role.

To read a file with unresolvable or external references anyway, drop them:

xplan-tools convert input.gml output.gml --drop-invalid-refs
Every dropped reference is logged, followed by a count of how many were dropped. A mandatory role is never left empty: a multi-valued one may lose entries as long as one resolvable entry remains, but a role that would end up with nothing still fails. Malformed hrefs are not covered by this option either, since they never get far enough to be dropped.

Note that a reference nested inside a data type is not read as a feature reference at all. Data types are stored inside their parent feature rather than as features of their own, so they cannot participate in the reference graph.

Writing one file per feature type

JSON-FG output is a single heterogenous FeatureCollection by default. It can instead be fanned out into one type-homogenous collection per feature type:

xplan-tools convert input.gml output.json --no-single-collection
Each collection is a document of its own, written next to the target and named after it: output_BP_Plan.json, output_BP_Bereich.json, and so on. The target itself is not written. Since this names a family of files rather than one, it needs a file path — it cannot fan out into a buffer.

Writing one file per plan

A file holding several plans can be split into one output file per plan:

xplan-tools convert input.gml output.gml --single-plans
Each output file is named after its plan and holds the containment closure of that plan, derived from the application schema's ownership metadata. A plan is self-contained, so a reference from inside a plan to a feature belonging to another one is invalid data and reported as an unresolvable reference. INSPIRE PLU declares no ownership metadata, so --single-plans cannot be combined with --to-version plu. The option applies to file output only; writing to a database always writes the whole collection.

Utilizing style rules

XPlanung presentational objects can be augmented with style properties (stylesheetId, schriftinhalt) via the convert command with the option --add-style-properties:

xplan-tools convert input.gml output.gml --add-style-properties
If the option --ppo-to-pto is added, XP_PPO are converted to XP_PTO objects in order to populate schriftinhalt.

The underlying rules can e.g. be exported like this:

xplan-tools serialize-style styles.yaml --format yaml

Version migration

Migrating from Xplanung version 5.x to 6.0 is done with the CLI command

xplan-tools convert xplan5x.gml --to-version 6.0
for a .gml data file xplan5x.gml.

INSPIRE transformation

Using the CLI tool to convert XPlanung data from version 6.0 to INSPIRE PLU 4.0 is straightforward. Given a dataset xplan60.gml we run

xplan-tools convert xplan60.gml --to-version plu

Chaining version migration and INSPIRE transformation

Applying the transformation to INSPIRE PLU 4.0 from XPlanung data version 5.* is done with same command as before. Given a dataset xplan5x.gml we run

xplan-tools convert xplan5x.gml --to-version plu

Initializing a database

An XPlanung database with GeoPackage backend can be initialized like this:

xplan-tools manage-db create-schema gpkg:///example.gpkg
libpq env vars are supported for Postgres connections. Assuming all required parameters (PGHOST etc.) are provided this way, an empty connection string can be used:
xplan-tools manage-db create-schema postgresql://

Use custom schema in postgres database

By default, the necessary tables in the database are created in the public namespace. For postgres, custom schemas are supported, via:

xplan-tools manage-db create-schema postgresql://postgres:postgres@localhost:5432/postgres --schema my_schema
In the above example, it is assumed that the database user executing this command has sufficient privileges on the target schema (e.g., USAGE, CREATE, INSERT, DELETE, UPDATE) and that their search_path includes the specified schema. This is required for correct table creation and data manipulation.

Database configuration via settings

Database behaviour is configured through settings, read from environment variables prefixed with XMAS_ or from a .env file in the working directory. For example, db_srid is set via XMAS_DB_SRID:

XMAS_DB_SRID=25833 xplan-tools manage-db create-schema postgresql://

The settings that affect schema creation are also exposed directly as options on create-schema, which take precedence when given:

Setting (XMAS_…) create-schema option Purpose
XMAS_DB_SRID --srid EPSG SRID of the geometry columns.
XMAS_DB_SCHEMA --schema PostgreSQL schema for the tables (see above); public if unset.
XMAS_DB_VIEWS --views Whether per-geometry-type views are created.

The remaining settings tune PostgreSQL data access rather than schema creation: XMAS_DB_INSERT_BATCH_SIZE (features per bulk-write statement, bounding how large a single statement's payload gets) and the connection-pool settings XMAS_DB_POOL_SIZE, XMAS_DB_POOL_MAX_OVERFLOW, XMAS_DB_POOL_TIMEOUT, XMAS_DB_POOL_RECYCLE and XMAS_DB_POOL_PRE_PING. See the settings reference for defaults and details.

Container image usage

Input data should be mounted in the /data directory in the container, e.g.

docker run \
-v input/dir:/data \
registry.opencode.de/xleitstelle/xplanung/xplan-tools:latest \
convert test.gml inspire.gml --to-version=plu
The output file will then be available in the mounted directory.