Util
util
General-purpose utilities.
Geometry/WKT/SRS/BBOX helpers, UUID and XPlanung art xpath
parsing/serialization, external-reference (URL and raster) validation, and
version-migration path computation.
ExternalReferenceUtil(ref_url, georef_url=None)
Utility class to validate external references.
Attributes:
| Name | Type | Description |
|---|---|---|
ref_url |
AnyUrl
|
The reference URL stored as an AnyUrl object. |
georef_url |
AnyUrl
|
The URL of a georeference sidecar file. |
Source code in xplan_tools/util/__init__.py
MigrationPath(from_version, to_version)
Computes migration path between two XPlanung versions.
Source code in xplan_tools/util/__init__.py
path
property
Returns migration path, given the initial and the target version of the plan.
RasterReferenceUtil(ref_url, georef_url=None)
Bases: ExternalReferenceUtil
Utility class to validate external raster data references.
Provides the raster_data_valid() method for an in-depth check regarding projection data etc.
Attributes:
| Name | Type | Description |
|---|---|---|
ref_url |
AnyUrl
|
The reference URL. |
georef_url |
AnyUrl
|
The URL of a georeference sidecar file. |
Source code in xplan_tools/util/__init__.py
raster_data_valid()
Validate raster data referenced via URL.
The raster file is checked for projection data using GDAL. \n If GDAL detects a georeference file that was not initially provided, it is added to the georef_url attribute.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if successful, False otherwise. |
Source code in xplan_tools/util/__init__.py
cast_geom_to_multi(geom)
Cast a single geometry to its multi variant.
Source code in xplan_tools/util/__init__.py
cast_geom_to_single(geom)
Cast a multi geometry to its single variant.
Source code in xplan_tools/util/__init__.py
enrich_attr_tuple(obj, art_tuple)
Return feature property information for construction of xpath expression.
Source code in xplan_tools/util/__init__.py
format_srs(srid, fmt='url')
Formats an EPSG SRID as an SRS string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
srid
|
int
|
The EPSG SRID. |
required |
fmt
|
Literal['short', 'url']
|
|
'url'
|
Returns:
| Type | Description |
|---|---|
str
|
The formatted SRS string. |
Source code in xplan_tools/util/__init__.py
get_envelope(geoms)
Return a BBOX for a list of geometries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geoms
|
list[str]
|
A list of WKT strings. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[float]
|
The BBOX coordinates in the format min_X, max_X, min_Y, max_Y. |
Source code in xplan_tools/util/__init__.py
get_geometry_type_from_wkt(geom)
Derives the geometry type from a WKT string.
Source code in xplan_tools/util/__init__.py
is_uuid(value, exact=False)
Check whether a given string is - or, unless exact, contains - a valid UUID.
Source code in xplan_tools/util/__init__.py
linearize_geom(geom)
Returns the linearized WKT string.
parse_art_xpath(xpath)
Parse an xpath expression into a tuple of feature property information and corresponding indices.
Source code in xplan_tools/util/__init__.py
parse_srs(srs)
Returns the EPSG SRID for an SRS reference.
Accepts any notation OGR's SetFromUserInput understands (EPSG:25832,
urn:ogc:def:crs:EPSG::25832, the OGC URL form, WKT) as well as the JSON-FG
coordRefSys object form ({"type": "Reference", "href": ...}). CRS84 and
its OGC URI/URN aliases resolve to 4326. The JSON-FG array (compound CRS) form
is not supported. Returns None if the SRS cannot be resolved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
srs
|
str | dict | None
|
The SRS reference, as a string, a JSON-FG |
required |
Returns:
| Type | Description |
|---|---|
int | None
|
The EPSG SRID, or None if it could not be determined. |
Source code in xplan_tools/util/__init__.py
parse_uuid(value, exact=False, raise_exception=False)
Return the UUID a given string contains, or None.
Source code in xplan_tools/util/__init__.py
serialize_art_xpath(t, prefix='xplan')
Construct xpath expression from tuple of feature property information.
Source code in xplan_tools/util/__init__.py
serialize_style_rules(format)
Serializes the style rules for XPlanung presentational objects in the selected format.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
format
|
Literal['json', 'yaml']
|
The format to serialize to. |
required |
Source code in xplan_tools/util/__init__.py
db
SQLAlchemy statement builders and helpers for coretable DB functions.
Each function returns a Select that invokes the corresponding PostgreSQL function; execute it
against a sync Session or an async AsyncSession. The function's schema is resolved at
execution time via the session's search_path, so the statements are not schema-qualified.
add_navigable_role(source_featuretype, navigable_role, target_featuretype, appschema, appschema_version, rel_direction, dependent_part=None)
Build a statement that idempotently registers a navigable role.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_featuretype
|
str
|
Owning/source feature type name. |
required |
navigable_role
|
str
|
Role (association) name linking source to target. |
required |
target_featuretype
|
str
|
Referenced/target feature type name. |
required |
appschema
|
str
|
Appschema prefix (e.g. |
required |
appschema_version
|
str
|
Appschema version (e.g. |
required |
rel_direction
|
Literal['forward', 'inverse']
|
Whether the refs edge runs |
required |
dependent_part
|
Literal['source', 'target'] | None
|
Ownership marker; |
None
|
Returns:
| Type | Description |
|---|---|
Select[tuple[bool]]
|
A |
Source code in xplan_tools/util/db.py
coretable_delete_object_recursive(start_id, dry_run=False)
Build a statement that recursively deletes start_id and its safe cascade closure.
Warning
Executing the returned statement performs the deletion unless dry_run is True; run
it inside a transaction. The affected rows are captured before removal, so the statement
still yields them after the delete.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_id
|
UUID
|
Root coretable id to delete. |
required |
dry_run
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
Select[tuple[Feature]]
|
A |
Select[tuple[Feature]]
|
rows; execute with |
Source code in xplan_tools/util/db.py
coretable_delete_orphans_recursive()
Build a statement that deletes dependent-part objects no longer owned by any whole.
Warning
Executing the returned statement performs the deletion; run it inside a transaction.
Returns:
| Type | Description |
|---|---|
Select[tuple[int]]
|
A |
Select[tuple[int]]
|
|
Source code in xplan_tools/util/db.py
coretable_feature_graph(start_id, depth_limit=3, include_forward=True, include_backward=True)
Return a chainable Select of Features reachable from start_id via the role graph.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_id
|
UUID
|
Root coretable id to traverse from. |
required |
depth_limit
|
int
|
Maximum BFS depth; defaults to 3 = the current appschemas' maximum depth. |
3
|
include_forward
|
bool
|
Traverse navigable roles in the source->target direction. |
True
|
include_backward
|
bool
|
Traverse navigable roles in the target->source direction. |
True
|
Returns:
| Type | Description |
|---|---|
Select[tuple[Feature]]
|
A chainable |
Source code in xplan_tools/util/db.py
coretable_role_graph(start_id, depth_limit=0, include_forward=True, include_backward=True, dependent_parts=False)
Build a statement returning the Feature rows reachable from start_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_id
|
UUID
|
Root coretable id to traverse from. |
required |
depth_limit
|
int
|
Maximum BFS depth; |
0
|
include_forward
|
bool
|
Traverse navigable roles in the source->target direction. |
True
|
include_backward
|
bool
|
Traverse navigable roles in the target->source direction. |
True
|
dependent_parts
|
bool
|
Collect the transitive existential dependent-part (ownership) closure instead of a general traversal; both directions are followed, ignoring the include_* flags. |
False
|
Returns:
| Type | Description |
|---|---|
Select[tuple[Feature]]
|
A |
Source code in xplan_tools/util/db.py
coretable_role_graph_cascade(start_id, depth_limit=0)
Build a statement returning the Feature rows safe to cascade-delete with start_id.
The result is the dependent-part closure of start_id minus any part whose owning whole lies
outside that closure (transitively), i.e. the objects that can be removed together with
start_id without orphaning a part still owned from elsewhere.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_id
|
UUID
|
Root coretable id whose cascade closure is collected. |
required |
depth_limit
|
int
|
Maximum BFS depth; |
0
|
Returns:
| Type | Description |
|---|---|
Select[tuple[Feature]]
|
A |
Source code in xplan_tools/util/db.py
coretable_role_graph_ids(start_id, depth_limit=0, include_forward=True, include_backward=True, dependent_parts=False)
Build a statement returning the ids reachable from start_id via navigable roles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start_id
|
UUID
|
Root coretable id to traverse from. |
required |
depth_limit
|
int
|
Maximum BFS depth; |
0
|
include_forward
|
bool
|
Traverse navigable roles in the source->target direction. |
True
|
include_backward
|
bool
|
Traverse navigable roles in the target->source direction. |
True
|
dependent_parts
|
bool
|
Collect the transitive existential dependent-part (ownership) closure instead of a general traversal; both directions are followed, ignoring the include_* flags. |
False
|
Returns:
| Type | Description |
|---|---|
Select[tuple[UUID]]
|
A |
Select[tuple[UUID]]
|
exist); execute with |
Source code in xplan_tools/util/db.py
list_navigable_roles()
Build a statement returning every configured navigable role.
Returns:
| Type | Description |
|---|---|
Select[tuple[str, str, str, str, str, str, str | None]]
|
A |
Select[tuple[str, str, str, str, str, str, str | None]]
|
|
Select[tuple[str, str, str, str, str, str, str | None]]
|
|
Source code in xplan_tools/util/db.py
list_top_level_featuretypes()
Build a statement returning the top-level (containment/deletion root) feature types.
Returns:
| Type | Description |
|---|---|
Select[tuple[str, str, str]]
|
A |
Select[tuple[str, str, str]]
|
|
Source code in xplan_tools/util/db.py
pg_array(values, type_)
Bind a Python sequence as a single PostgreSQL array parameter.
Keeps a statement's bind-parameter count independent of len(values): asyncpg refuses
more than 32767 parameters per statement, and a bind list whose length varies also defeats
the driver's prepared-statement cache, since it caches by SQL text.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Sequence[Any]
|
The values to bind; element bind processing runs through |
required |
type_
|
TypeEngine
|
The array's item type, e.g. |
required |
Returns:
| Type | Description |
|---|---|
Cast
|
A |
Cast
|
cast is explicit because PostgreSQL cannot infer a polymorphic function's argument |
Cast
|
type from an untyped parameter (see |
Cast
|
may render a second, redundant |
Source code in xplan_tools/util/db.py
unnest_rows(rows, columns, *, types=None)
Expose row dicts as a table-valued unnest over one array parameter per column.
The PostgreSQL counterpart to an inline VALUES list: instead of one bind parameter per
cell it binds one array per column, so the parameter count stays constant (see pg_array).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
Sequence[dict]
|
Row dicts keyed by column name; every column in |
required |
columns
|
Sequence[Column]
|
The columns to emit, in order. |
required |
types
|
Mapping[str, TypeEngine] | None
|
Array item types overriding a column's own type, keyed by column name. Needed
where the column type is not what the driver can encode an array of — geometry
columns travel as |
None
|
Returns:
| Type | Description |
|---|---|
TableValuedAlias
|
A derived table aliased with |
Source code in xplan_tools/util/db.py
streams
Reading a datasource as a binary stream.
A datasource reaches this package as a file path or as a file-like object, and every reader needs the same thing from it: bytes, from the start, without consuming a buffer its caller still holds. That normalisation lives here rather than in any one reader.
Not named io: importing xplan_tools.util.io would bind that name on the package
and shadow the standard library io that xplan_tools/util/__init__.py imports.
as_binary_stream(source)
Yields source as a binary stream positioned at the start.
A path is opened and closed here. A seekable binary stream is rewound first and put
back where it was found afterwards, so a caller's buffer comes back as it was handed
over. Anything else - a text stream, a non-seekable one - is copied into a BytesIO,
since a reader must not consume a stream the caller still needs. Copying a text
stream also lets a StringIO carrying an encoding declaration be read, where
etree.parse refuses it.
The copy made for a non-seekable stream serves that one read: the source is consumed
either way, so a caller that reads twice has to hold the bytes itself.
Datasource.buffer is what does that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | BytesIO | StringIO
|
A file path, or a binary or text file-like object. |
required |
Raises:
| Type | Description |
|---|---|
UnsupportedDatasourceError
|
|
OSError
|
The path could not be opened. |
Yields:
| Type | Description |
|---|---|
IO[bytes]
|
A binary stream. |
Source code in xplan_tools/util/streams.py
style
Derive XPlanung styling for presentational objects from the rule set.
Matches a presentational object's art property references against the rules
in :data:xplan_tools.resources.styles.RULES to populate stylesheetId and
schriftinhalt.
add_style_properties_to_feature(obj, ref_obj, to_text=False, always_populate_schriftinhalt=False)
Add styling properties to presentational objects.
This method parses object (dientZurDarstellungVon) and property (art) references from presentational objects and derives styling information (stylesheetId, schriftinhalt) based on a set of defined rules.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
BaseFeature
|
The presentational object. |
required |
ref_obj
|
BaseFeature
|
The object referenced by the presentational object. |
required |
to_text
|
bool
|
Whether to convert symbolic presentational objects to textual ones. Defaults to False. |
False
|
always_populate_schriftinhalt
|
bool
|
Populate |
False
|
Source code in xplan_tools/util/style.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | |
validate
This module contains a method to validate Feature Collections with the official XPlanValidator.
save_validation_reports(report_dict, output='report.json')
Save validation reports to JSON files.
Source code in xplan_tools/util/validate.py
xplan_validate(collection, input='xplan.gml', single_plans=False, validator_url='https://www.xplanungsplattform.de/xplan-api-validator/xvalidator/api/v1/')
async
Validate a Feature Collection with the official XPlanValidator.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
collection
|
BaseCollection
|
A BaseCollection instance. |
required |
input
|
str
|
An optional input file name to use in the validation report. |
'xplan.gml'
|
single_plans
|
bool
|
Whether to validate plans in the collection individually. |
False
|
validator_url
|
str
|
The base URL of the XPlanValidator instance. Must have a trailing slash. |
'https://www.xplanungsplattform.de/xplan-api-validator/xvalidator/api/v1/'
|
Source code in xplan_tools/util/validate.py
xml
Hardened XML reading.
Every XML document this package reads goes through here, so that the parser configuration lives in exactly one place.
lxml already refuses external entities and remote document URLs by default, and
libxml2 already caps entity amplification. HARDENED_PARSER_OPTIONS pins that behaviour rather than relying on
defaults that may change, and adds what the defaults do not cover:
preflight_rootstreams just the firststartevent, so a document with a DOCTYPE or an unexpected root element is rejected after a single ~32 KB read instead of after a full parse.resolve_entities=Falseleaves entity references unexpanded, which would silently drop element text - hence a DOCTYPE is refused outright rather than tolerated.
ALLOWED_ROOTS = {'XPlanAuszug': None, 'FeatureCollection': frozenset({'http://www.opengis.net/wfs/2.0', 'http://www.opengis.net/ogcapi-features-1/1.0/sf'})}
module-attribute
Root elements a GML datasource may start with.
HARDENED_PARSER_OPTIONS = {'resolve_entities': False, 'no_network': True, 'load_dtd': False, 'dtd_validation': False, 'attribute_defaults': False, 'huge_tree': False, 'collect_ids': False, 'recover': False}
module-attribute
Parser flags applied to every parse of an XML document.
RootInfo(namespace, localname, nsmap, schema_location)
dataclass
What the preflight learned about a document's root element.
Attributes:
| Name | Type | Description |
|---|---|---|
namespace |
str | None
|
The root element's namespace URI, if it has one. |
localname |
str
|
The root element's local name. |
nsmap |
dict[str | None, str]
|
The namespace bindings declared on the root element. |
schema_location |
str
|
The root's |
hardened_parser()
Returns a new parser configured with HARDENED_PARSER_OPTIONS.
A fresh instance per call: an lxml parser holds state and is not safe to share
across threads.
Source code in xplan_tools/util/xml.py
parse_hardened(source)
Parses an XML document with the hardened parser.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | BytesIO | StringIO
|
A file path, or a binary or text file-like object. |
required |
Raises:
| Type | Description |
|---|---|
XMLParseError
|
The document is not well-formed. |
UnsupportedDatasourceError
|
|
OSError
|
The path could not be opened. |
Returns:
| Type | Description |
|---|---|
_ElementTree
|
The parsed tree. |
Source code in xplan_tools/util/xml.py
preflight_root(source, allowed=ALLOWED_ROOTS)
Reads a document's root element without parsing the rest of it.
Streams up to the first start event - roughly one 32 KB read - and rejects the
document there if it declares a DOCTYPE or opens with an element this package does
not read. A document that ends mid-tree still passes; parse_hardened catches that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
str | BytesIO | StringIO
|
A file path, or a binary or text file-like object. |
required |
allowed
|
dict[str, frozenset[str] | None]
|
Accepted root elements, as local name to accepted namespaces ( |
ALLOWED_ROOTS
|
Raises:
| Type | Description |
|---|---|
ForbiddenDoctypeError
|
The document declares an internal or external DTD. |
UnsupportedRootElementError
|
The root element is not in |
XMLParseError
|
The document is not well-formed, or is empty. |
UnsupportedDatasourceError
|
|
OSError
|
The path could not be opened. |
Returns:
| Type | Description |
|---|---|
RootInfo
|
The root element's name, namespace bindings and |