xplan-tools 1.12.0__py3-none-any.whl → 1.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xplan_tools/interface/__init__.py +1 -4
- xplan_tools/interface/db.py +77 -25
- xplan_tools/interface/gml.py +46 -10
- xplan_tools/interface/jsonfg.py +12 -3
- xplan_tools/main.py +10 -3
- xplan_tools/model/__init__.py +8 -1
- xplan_tools/model/adapters/gml.py +11 -4
- xplan_tools/model/appschema/definitions.py +15 -44
- xplan_tools/model/appschema/xplan41.py +493 -722
- xplan_tools/model/appschema/xplan54.py +521 -734
- xplan_tools/model/appschema/xplan60.py +528 -752
- xplan_tools/model/appschema/xplan61.py +541 -771
- xplan_tools/model/appschema/xtrasse20.py +331 -446
- xplan_tools/model/appschema/xwp09.py +5591 -0
- xplan_tools/model/base.py +211 -3
- xplan_tools/model/migrations/env.py +3 -2
- xplan_tools/model/migrations/versions/3c3445a58565_base_schema.py +8 -6
- xplan_tools/model/migrations/versions/f8b74c08ec07_add_refs_indexes_ensure_polygon_ccw.py +5 -1
- xplan_tools/model/orm.py +12 -18
- xplan_tools/settings/__init__.py +3 -0
- xplan_tools/settings/settings.py +23 -0
- xplan_tools/util/__init__.py +0 -24
- {xplan_tools-1.12.0.dist-info → xplan_tools-1.13.0.dist-info}/METADATA +7 -4
- {xplan_tools-1.12.0.dist-info → xplan_tools-1.13.0.dist-info}/RECORD +27 -24
- {xplan_tools-1.12.0.dist-info → xplan_tools-1.13.0.dist-info}/WHEEL +1 -1
- {xplan_tools-1.12.0.dist-info → xplan_tools-1.13.0.dist-info}/entry_points.txt +0 -0
- {xplan_tools-1.12.0.dist-info → xplan_tools-1.13.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -24,9 +24,6 @@ logger = logging.getLogger(__name__)
|
|
|
24
24
|
def repo_factory(
|
|
25
25
|
datasource: str = "",
|
|
26
26
|
repo_type: Literal["gml", "jsonfg", "shape", "db"] | None = None,
|
|
27
|
-
schema: str | None = None,
|
|
28
|
-
srid: int = 25832,
|
|
29
|
-
views: bool = False,
|
|
30
27
|
) -> "BaseRepository":
|
|
31
28
|
"""Factory method for Repositories.
|
|
32
29
|
|
|
@@ -111,7 +108,7 @@ def repo_factory(
|
|
|
111
108
|
case "db":
|
|
112
109
|
logger.debug("initializing DB repository")
|
|
113
110
|
return locate("xplan_tools.interface.db.DBRepository")(
|
|
114
|
-
datasource,
|
|
111
|
+
datasource,
|
|
115
112
|
)
|
|
116
113
|
case _:
|
|
117
114
|
raise ValueError("Unknown datasource")
|
xplan_tools/interface/db.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# import json
|
|
4
4
|
import logging
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Iterable
|
|
6
|
+
from typing import Iterable, Literal
|
|
7
7
|
|
|
8
8
|
from alembic import command, config, script
|
|
9
9
|
from geoalchemy2 import load_spatialite_gpkg
|
|
@@ -32,7 +32,7 @@ from sqlalchemy.orm import sessionmaker
|
|
|
32
32
|
from xplan_tools.model import model_factory
|
|
33
33
|
from xplan_tools.model.base import BaseCollection, BaseFeature
|
|
34
34
|
from xplan_tools.model.orm import Base, Feature, Geometry, Refs
|
|
35
|
-
from xplan_tools.
|
|
35
|
+
from xplan_tools.settings import get_settings
|
|
36
36
|
|
|
37
37
|
# from xplan_tools.util import linearize_geom
|
|
38
38
|
from .base import BaseRepository
|
|
@@ -46,9 +46,6 @@ class DBRepository(BaseRepository):
|
|
|
46
46
|
def __init__(
|
|
47
47
|
self,
|
|
48
48
|
datasource: str = "",
|
|
49
|
-
schema: str | None = None,
|
|
50
|
-
srid: int = 25832,
|
|
51
|
-
with_views: bool = False,
|
|
52
49
|
) -> None:
|
|
53
50
|
"""Initializes the DB Repository.
|
|
54
51
|
|
|
@@ -59,40 +56,89 @@ class DBRepository(BaseRepository):
|
|
|
59
56
|
|
|
60
57
|
Args:
|
|
61
58
|
datasource: A connection string which will be transformed to a URL instance.
|
|
62
|
-
schema: Schema name for DB repository. If not specified, the default schema is used. Only for PostgreSQL.
|
|
63
|
-
srid: the EPSG code for spatial data
|
|
64
|
-
with_views: whether to create geometrytype-specific views (postgres only)
|
|
65
59
|
"""
|
|
60
|
+
settings = get_settings()
|
|
66
61
|
self.datasource: URL = make_url(datasource)
|
|
67
62
|
self.content = None
|
|
68
|
-
self.schema =
|
|
63
|
+
self.schema = settings.db_schema
|
|
64
|
+
self.srid = settings.db_srid
|
|
69
65
|
self.dialect = self.datasource.get_dialect().name
|
|
70
66
|
self.Session = sessionmaker(bind=self._engine)
|
|
71
|
-
# self.session = self.Session()
|
|
72
|
-
self.srid = srid
|
|
73
|
-
self.with_views = with_views
|
|
74
67
|
|
|
75
68
|
self.alembic_cfg = config.Config()
|
|
76
69
|
self.alembic_cfg.set_main_option(
|
|
77
70
|
"script_location", "xplan_tools:model:migrations"
|
|
78
71
|
)
|
|
79
|
-
self.alembic_cfg.set_main_option("srid", str(srid))
|
|
80
|
-
if with_views:
|
|
81
|
-
self.alembic_cfg.set_main_option("with_views", "1")
|
|
82
72
|
self.alembic_cfg.set_main_option(
|
|
83
73
|
"sqlalchemy.url",
|
|
84
74
|
datasource.replace("gpkg:", "sqlite:").replace(
|
|
85
75
|
"postgresql:", "postgresql+psycopg:"
|
|
86
76
|
),
|
|
87
77
|
)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
self._ensure_repo()
|
|
79
|
+
|
|
80
|
+
def _ensure_repo(self) -> None:
|
|
81
|
+
"""Runs initial connection/schema tests and ensures DB revision."""
|
|
82
|
+
|
|
83
|
+
def _check_schema_accessibility(privilege: Literal["USAGE", "CREATE"]) -> None:
|
|
84
|
+
"""Raises an exception if the schema does not exist or is not accessible to the current user."""
|
|
85
|
+
# Check if the schema exists and is accessible
|
|
86
|
+
user = conn.execute(text("SELECT current_user")).scalar()
|
|
87
|
+
result = conn.execute(
|
|
88
|
+
text("""
|
|
89
|
+
SELECT has_schema_privilege(:user, :schema, :privilege)
|
|
90
|
+
"""),
|
|
91
|
+
{
|
|
92
|
+
"user": user,
|
|
93
|
+
"schema": self.schema,
|
|
94
|
+
"privilege": privilege,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
if not result.scalar():
|
|
98
|
+
raise RuntimeError(
|
|
99
|
+
f"User {user} lacks {privilege} on schema '{self.schema}'"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _check_db_srid() -> None:
|
|
103
|
+
"""Raises an exception if the DB SRID differs from the one of the repo."""
|
|
104
|
+
if self.dialect == "geopackage":
|
|
105
|
+
geometry_columns = Table(
|
|
106
|
+
"gpkg_geometry_columns",
|
|
107
|
+
MetaData(),
|
|
108
|
+
Column("table_name"),
|
|
109
|
+
Column("srs_id"),
|
|
110
|
+
)
|
|
111
|
+
stmt = select(geometry_columns.c.srs_id).where(
|
|
112
|
+
geometry_columns.c.table_name == "coretable"
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
geometry_columns = Table(
|
|
116
|
+
"geometry_columns",
|
|
117
|
+
MetaData(),
|
|
118
|
+
Column("f_table_name"),
|
|
119
|
+
Column("srid"),
|
|
120
|
+
)
|
|
121
|
+
stmt = select(geometry_columns.c.srid).where(
|
|
122
|
+
geometry_columns.c.f_table_name == "coretable"
|
|
123
|
+
)
|
|
124
|
+
if self.dialect == "postgresql":
|
|
125
|
+
geometry_columns.append_column(Column("f_table_schema"))
|
|
126
|
+
stmt = stmt.where(
|
|
127
|
+
geometry_columns.c.f_table_schema == (self.schema or "public")
|
|
128
|
+
)
|
|
129
|
+
srid = conn.execute(stmt).scalar_one()
|
|
130
|
+
if srid != self.srid:
|
|
131
|
+
raise RuntimeError(
|
|
132
|
+
f"DB SRID '{srid}' and configured SRID '{self.srid}' must identical"
|
|
133
|
+
)
|
|
134
|
+
|
|
91
135
|
current_version = script.ScriptDirectory.from_config(
|
|
92
136
|
self.alembic_cfg
|
|
93
137
|
).get_heads()
|
|
94
138
|
# test for tables and revision
|
|
95
139
|
with self._engine.connect() as conn:
|
|
140
|
+
if self.schema and self.dialect == "postgresql":
|
|
141
|
+
_check_schema_accessibility("USAGE")
|
|
96
142
|
inspector = inspect(conn)
|
|
97
143
|
tables = inspector.get_table_names(schema=self.schema)
|
|
98
144
|
is_coretable = {"coretable", "refs"}.issubset(set(tables))
|
|
@@ -106,10 +152,14 @@ class DBRepository(BaseRepository):
|
|
|
106
152
|
db_version = conn.execute(stmt).scalars().all()
|
|
107
153
|
else:
|
|
108
154
|
db_version = []
|
|
155
|
+
if db_version:
|
|
156
|
+
_check_db_srid()
|
|
109
157
|
is_current_version = set(db_version) == set(current_version)
|
|
110
158
|
if is_current_version:
|
|
111
159
|
logger.info("Database is at current revision")
|
|
112
160
|
return
|
|
161
|
+
elif self.schema and self.dialect == "postgresql":
|
|
162
|
+
_check_schema_accessibility("CREATE")
|
|
113
163
|
# handle schema upgrade or table creation
|
|
114
164
|
if is_coretable and not db_version:
|
|
115
165
|
e = RuntimeError("Coretable with no revision found in database")
|
|
@@ -140,7 +190,7 @@ class DBRepository(BaseRepository):
|
|
|
140
190
|
else:
|
|
141
191
|
# create tables if it's a fresh file-based DB and set it to current revision
|
|
142
192
|
logger.info("Creating new database schema")
|
|
143
|
-
self.create_tables(
|
|
193
|
+
self.create_tables()
|
|
144
194
|
command.stamp(self.alembic_cfg, "head")
|
|
145
195
|
|
|
146
196
|
@property
|
|
@@ -151,8 +201,10 @@ class DBRepository(BaseRepository):
|
|
|
151
201
|
else self.datasource
|
|
152
202
|
)
|
|
153
203
|
connect_args: dict[str, str] = {}
|
|
154
|
-
if self.
|
|
155
|
-
connect_args["
|
|
204
|
+
if self.dialect == "postgresql":
|
|
205
|
+
connect_args["connect_timeout"] = 5
|
|
206
|
+
if self.schema:
|
|
207
|
+
connect_args["options"] = f"-csearch_path={self.schema},public"
|
|
156
208
|
engine = create_engine(url, connect_args=connect_args)
|
|
157
209
|
if self.dialect == "geopackage":
|
|
158
210
|
listen(engine, "connect", load_spatialite_gpkg)
|
|
@@ -315,7 +367,7 @@ class DBRepository(BaseRepository):
|
|
|
315
367
|
else:
|
|
316
368
|
raise ValueError(f"no feature found with id {id}")
|
|
317
369
|
|
|
318
|
-
def create_tables(self
|
|
370
|
+
def create_tables(self) -> None:
|
|
319
371
|
"""Creates coretable and related/spatial tables in the database.
|
|
320
372
|
|
|
321
373
|
Args:
|
|
@@ -326,7 +378,7 @@ class DBRepository(BaseRepository):
|
|
|
326
378
|
def pre_creation(_, conn, **kwargs):
|
|
327
379
|
if self.dialect == "sqlite":
|
|
328
380
|
conn.execute(text("SELECT InitSpatialMetaData('EMPTY')"))
|
|
329
|
-
conn.execute(text("SELECT InsertEpsgSrid(:srid)"), {"srid": srid})
|
|
381
|
+
conn.execute(text("SELECT InsertEpsgSrid(:srid)"), {"srid": self.srid})
|
|
330
382
|
|
|
331
383
|
@listens_for(Base.metadata, "after_create")
|
|
332
384
|
def post_creation(_, conn, **kwargs):
|
|
@@ -362,7 +414,7 @@ class DBRepository(BaseRepository):
|
|
|
362
414
|
)
|
|
363
415
|
)
|
|
364
416
|
|
|
365
|
-
logger.debug(f"creating tables with srid {srid}")
|
|
417
|
+
logger.debug(f"creating tables with srid {self.srid}")
|
|
366
418
|
tables = Base.metadata.sorted_tables
|
|
367
419
|
if not self.dialect == "geopackage":
|
|
368
420
|
tables.pop(1)
|
|
@@ -370,7 +422,7 @@ class DBRepository(BaseRepository):
|
|
|
370
422
|
Column(
|
|
371
423
|
"geometry",
|
|
372
424
|
Geometry(
|
|
373
|
-
srid=srid,
|
|
425
|
+
srid=self.srid,
|
|
374
426
|
spatial_index=True,
|
|
375
427
|
),
|
|
376
428
|
nullable=True,
|
xplan_tools/interface/gml.py
CHANGED
|
@@ -56,6 +56,13 @@ class GMLRepository(BaseRepository):
|
|
|
56
56
|
),
|
|
57
57
|
):
|
|
58
58
|
return "xtrasse"
|
|
59
|
+
elif re.search(
|
|
60
|
+
"http://www[.]xwaermeplan[.]de/[0-9]/[0-9]",
|
|
61
|
+
self.content.get(
|
|
62
|
+
f"{{{xsi}}}schemaLocation", self.content.nsmap.get(None, "")
|
|
63
|
+
),
|
|
64
|
+
):
|
|
65
|
+
return "xwp"
|
|
59
66
|
elif self.content.nsmap.get(
|
|
60
67
|
"xplan", self.content.nsmap.get(None, "")
|
|
61
68
|
) or re.search(
|
|
@@ -73,6 +80,11 @@ class GMLRepository(BaseRepository):
|
|
|
73
80
|
if self.appschema == "xtrasse":
|
|
74
81
|
uri = self.content.nsmap.get("xtrasse", self.content.nsmap.get(None, ""))
|
|
75
82
|
return uri.split("http://www.xtrasse.de/")[1].replace("/", ".")
|
|
83
|
+
elif self.appschema == "xwp":
|
|
84
|
+
uri = self.content.nsmap.get(
|
|
85
|
+
"xwaermeplan", self.content.nsmap.get(None, "")
|
|
86
|
+
)
|
|
87
|
+
return uri.split("http://www.xwaermeplan.de/")[1].replace("/", ".")
|
|
76
88
|
else:
|
|
77
89
|
uri = self.content.nsmap.get("xplan", self.content.nsmap.get(None, ""))
|
|
78
90
|
if "xplan" not in uri:
|
|
@@ -149,6 +161,24 @@ class GMLRepository(BaseRepository):
|
|
|
149
161
|
},
|
|
150
162
|
nsmap=nsmap,
|
|
151
163
|
)
|
|
164
|
+
elif self.appschema == "xwp":
|
|
165
|
+
nsmap = {
|
|
166
|
+
None: f"http://www.xwaermeplan.de/{self.version.replace('.', '/')}",
|
|
167
|
+
"gml": "http://www.opengis.net/gml/3.2",
|
|
168
|
+
"xml": "http://www.w3.org/XML/1998/namespace",
|
|
169
|
+
"xlink": "http://www.w3.org/1999/xlink",
|
|
170
|
+
"xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
171
|
+
"sf": "http://www.opengis.net/ogcapi-features-1/1.0/sf",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
root = etree.Element(
|
|
175
|
+
"{http://www.opengis.net/ogcapi-features-1/1.0/sf}FeatureCollection",
|
|
176
|
+
attrib={
|
|
177
|
+
"{http://www.w3.org/2001/XMLSchema-instance}schemaLocation": f"{nsmap[None]} https://gitlab.opencode.de/xleitstelle/xwaermeplan/spezifikation/-/raw/main/xsd/waermeplan.xsd {nsmap['sf']} http://schemas.opengis.net/ogcapi/features/part1/1.0/xml/core-sf.xsd {nsmap['gml']} https://schemas.opengis.net/gml/3.2.1/gml.xsd",
|
|
178
|
+
"{http://www.opengis.net/gml/3.2}id": f"GML_{uuid4()}",
|
|
179
|
+
},
|
|
180
|
+
nsmap=nsmap,
|
|
181
|
+
)
|
|
152
182
|
|
|
153
183
|
elif self.appschema == "plu":
|
|
154
184
|
nsmap = {
|
|
@@ -173,12 +203,14 @@ class GMLRepository(BaseRepository):
|
|
|
173
203
|
nsmap=nsmap,
|
|
174
204
|
)
|
|
175
205
|
|
|
176
|
-
if self.appschema
|
|
206
|
+
if self.appschema not in ["xtrasse", "xwp"]:
|
|
177
207
|
bounds = etree.SubElement(
|
|
178
208
|
root,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
209
|
+
(
|
|
210
|
+
"{http://www.opengis.net/gml/3.2}boundedBy"
|
|
211
|
+
if self.appschema == "xplan"
|
|
212
|
+
else "{http://www.opengis.net/wfs/2.0}boundedBy"
|
|
213
|
+
),
|
|
182
214
|
)
|
|
183
215
|
|
|
184
216
|
geoms = []
|
|
@@ -193,11 +225,15 @@ class GMLRepository(BaseRepository):
|
|
|
193
225
|
srs = feature.get_geom_srid()
|
|
194
226
|
etree.SubElement(
|
|
195
227
|
root,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
228
|
+
(
|
|
229
|
+
"{http://www.opengis.net/gml/3.2}featureMember"
|
|
230
|
+
if self.appschema == "xplan"
|
|
231
|
+
else (
|
|
232
|
+
"{http://www.opengis.net/ogcapi-features-1/1.0/sf}featureMember"
|
|
233
|
+
if self.appschema in ["xtrasse", "xwp"]
|
|
234
|
+
else "{http://www.opengis.net/wfs/2.0}member"
|
|
235
|
+
)
|
|
236
|
+
),
|
|
201
237
|
).append(
|
|
202
238
|
feature.model_dump_gml(feature_srs=kwargs.get("feature_srs", True))
|
|
203
239
|
)
|
|
@@ -210,7 +246,7 @@ class GMLRepository(BaseRepository):
|
|
|
210
246
|
else {"srsName": f"EPSG:{srs}"}
|
|
211
247
|
)
|
|
212
248
|
|
|
213
|
-
if self.appschema
|
|
249
|
+
if self.appschema not in ["xtrasse", "xwp"]:
|
|
214
250
|
envelope = etree.SubElement(
|
|
215
251
|
bounds,
|
|
216
252
|
"{http://www.opengis.net/gml/3.2}Envelope",
|
xplan_tools/interface/jsonfg.py
CHANGED
|
@@ -91,6 +91,8 @@ class JsonFGRepository(BaseRepository):
|
|
|
91
91
|
raise ValueError("JSON Schema not found in links")
|
|
92
92
|
if "https://gitlab.opencode.de/xleitstelle/xtrasse/spezifikation" in uri:
|
|
93
93
|
return "xtrasse"
|
|
94
|
+
elif "https://gitlab.opencode.de/xleitstelle/xwaermeplan/spezifikation/" in uri:
|
|
95
|
+
return "xwp"
|
|
94
96
|
elif "https://gitlab.opencode.de/xleitstelle/xplanung/schemas/json" in uri:
|
|
95
97
|
return "xplan"
|
|
96
98
|
else:
|
|
@@ -107,9 +109,18 @@ class JsonFGRepository(BaseRepository):
|
|
|
107
109
|
raise ValueError("JSON Schema not found in links")
|
|
108
110
|
if self.appschema == "xtrasse":
|
|
109
111
|
return "2.0"
|
|
112
|
+
elif self.appschema == "xwp":
|
|
113
|
+
return "0.9"
|
|
110
114
|
return re.search(r"(.*\/)(\d.\d)(\/.*)", uri).group(2)
|
|
111
115
|
|
|
112
116
|
def _collection_template(self, srid: int, featuretype: str | None = None):
|
|
117
|
+
match self.appschema:
|
|
118
|
+
case "xtrasse":
|
|
119
|
+
href = "https://gitlab.opencode.de/xleitstelle/xtrasse/spezifikation/-/raw/main/json/featurecollection.json"
|
|
120
|
+
case "xwp":
|
|
121
|
+
href = f"https://gitlab.opencode.de/xleitstelle/xwaermeplan/spezifikation/-/raw/main/json/{self.version}/featurecollection.json"
|
|
122
|
+
case "xplan":
|
|
123
|
+
href = f"https://gitlab.opencode.de/xleitstelle/xplanung/schemas/json/-/raw/main/{self.version}/featurecollection.json"
|
|
113
124
|
template = {
|
|
114
125
|
"type": "FeatureCollection",
|
|
115
126
|
"featureType": featuretype,
|
|
@@ -117,9 +128,7 @@ class JsonFGRepository(BaseRepository):
|
|
|
117
128
|
"features": [],
|
|
118
129
|
"links": [
|
|
119
130
|
{
|
|
120
|
-
"href":
|
|
121
|
-
if self.appschema == "xtrasse"
|
|
122
|
-
else f"https://gitlab.opencode.de/xleitstelle/xplanung/schemas/json/-/raw/main/{self.version}/featurecollection.json",
|
|
131
|
+
"href": href,
|
|
123
132
|
"rel": "describedby",
|
|
124
133
|
"type": "application/schema+json",
|
|
125
134
|
"title": "JSON Schema of this document",
|
xplan_tools/main.py
CHANGED
|
@@ -13,6 +13,7 @@ from rich.logging import RichHandler
|
|
|
13
13
|
from rich.traceback import Traceback
|
|
14
14
|
|
|
15
15
|
from xplan_tools.interface import repo_factory
|
|
16
|
+
from xplan_tools.settings.settings import get_settings
|
|
16
17
|
from xplan_tools.transform import transformer_factory
|
|
17
18
|
from xplan_tools.util import MigrationPath, _Versions, serialize_style_rules
|
|
18
19
|
from xplan_tools.util.validate import xplan_validate
|
|
@@ -25,6 +26,8 @@ logger = logging.getLogger(__name__)
|
|
|
25
26
|
# don't propagate alembic logs when using CLI
|
|
26
27
|
logging.getLogger("alembic").propagate = False
|
|
27
28
|
|
|
29
|
+
settings = get_settings()
|
|
30
|
+
|
|
28
31
|
app = typer.Typer(help=f"XPlan-Tools {__version__}")
|
|
29
32
|
db_app = typer.Typer()
|
|
30
33
|
app.add_typer(
|
|
@@ -335,13 +338,16 @@ def create_schema(
|
|
|
335
338
|
] = 25832,
|
|
336
339
|
views: Annotated[
|
|
337
340
|
bool, typer.Option(help="Whether to create views for geometry types.")
|
|
338
|
-
] =
|
|
341
|
+
] = True,
|
|
339
342
|
schema: Annotated[
|
|
340
343
|
str | None, typer.Option(help="Whether to specify a schema.")
|
|
341
344
|
] = None,
|
|
342
345
|
):
|
|
343
346
|
"""Create Coretable schema in the given DB."""
|
|
344
|
-
|
|
347
|
+
settings.db_schema = schema
|
|
348
|
+
settings.db_srid = srid
|
|
349
|
+
settings.db_views = views
|
|
350
|
+
repo_factory(connection_string)
|
|
345
351
|
console.log("Schema created.")
|
|
346
352
|
|
|
347
353
|
|
|
@@ -355,7 +361,8 @@ def drop_schema(
|
|
|
355
361
|
] = None,
|
|
356
362
|
):
|
|
357
363
|
"""Drop Coretable schema in the given DB."""
|
|
358
|
-
|
|
364
|
+
settings.db_schema = schema
|
|
365
|
+
repo_factory(connection_string).delete_tables()
|
|
359
366
|
console.log("Schema dropped.")
|
|
360
367
|
|
|
361
368
|
|
xplan_tools/model/__init__.py
CHANGED
|
@@ -27,14 +27,17 @@ Example:
|
|
|
27
27
|
from pydoc import locate
|
|
28
28
|
from typing import TYPE_CHECKING, Literal, Type
|
|
29
29
|
|
|
30
|
+
from deprecated import deprecated
|
|
31
|
+
|
|
30
32
|
if TYPE_CHECKING:
|
|
31
33
|
from .base import BaseFeature
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
@deprecated(reason="use Appschema.model_factory instead")
|
|
34
37
|
def model_factory(
|
|
35
38
|
model_name: str,
|
|
36
39
|
model_version: str | None,
|
|
37
|
-
appschema: Literal["xplan", "xtrasse", "plu", "def"] = "xplan",
|
|
40
|
+
appschema: Literal["xplan", "xtrasse", "xwp", "plu", "def"] = "xplan",
|
|
38
41
|
) -> Type["BaseFeature"]:
|
|
39
42
|
"""Factory method for retrieving the corresponding pydantic model representation of a feature class.
|
|
40
43
|
|
|
@@ -75,6 +78,10 @@ def model_factory(
|
|
|
75
78
|
model = locate(
|
|
76
79
|
f"xplan_tools.model.appschema.xtrasse{model_version.replace('.', '')}.{model_name.replace('_', '')}"
|
|
77
80
|
)
|
|
81
|
+
case "xwp":
|
|
82
|
+
model = locate(
|
|
83
|
+
f"xplan_tools.model.appschema.xwp{model_version.replace('.', '')}.{model_name.replace('_', '')}"
|
|
84
|
+
)
|
|
78
85
|
|
|
79
86
|
if not model:
|
|
80
87
|
raise ValueError(
|
|
@@ -24,9 +24,14 @@ class GMLAdapter:
|
|
|
24
24
|
|
|
25
25
|
def parse_property(name, value, index: int | None = None):
|
|
26
26
|
gml_name = f"{{{ns}}}{name}"
|
|
27
|
-
|
|
28
27
|
if name == "id":
|
|
29
28
|
feature.set("{http://www.opengis.net/gml/3.2}id", f"GML_{value}")
|
|
29
|
+
if self.get_appschema() != "plu":
|
|
30
|
+
etree.SubElement(
|
|
31
|
+
feature,
|
|
32
|
+
"{http://www.opengis.net/gml/3.2}identifier",
|
|
33
|
+
attrib={"codeSpace": "urn:uuid"},
|
|
34
|
+
).text = value
|
|
30
35
|
return
|
|
31
36
|
# Patch for vertikaleDifferenzierung being optional with a default value of False instead of None
|
|
32
37
|
if name == "vertikaleDifferenzierung" and value is False:
|
|
@@ -59,9 +64,11 @@ class GMLAdapter:
|
|
|
59
64
|
options=[
|
|
60
65
|
"FORMAT=GML32",
|
|
61
66
|
f"GMLID=GML_{uuid4()}",
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
(
|
|
68
|
+
"SRSNAME_FORMAT=OGC_URL"
|
|
69
|
+
if self.get_appschema() == "plu"
|
|
70
|
+
else "GML3_LONGSRS=NO"
|
|
71
|
+
),
|
|
65
72
|
"NAMESPACE_DECL=YES",
|
|
66
73
|
]
|
|
67
74
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# generated from JSON Schema
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
|
|
5
6
|
from datetime import date as date_aliased
|
|
@@ -42,86 +43,56 @@ class MultiPoint(GeometryBase):
|
|
|
42
43
|
wkt: Annotated[str, Field(pattern="^(MULTIPOINT).*$")]
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
class XPPunktgeometrie(RootModel[Point | MultiPoint]):
|
|
46
|
-
root: Point | MultiPoint
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class XPLiniengeometrie(RootModel[Line | MultiLine]):
|
|
50
|
-
root: Line | MultiLine
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class XPFlaechengeometrie(RootModel[Polygon | MultiPolygon]):
|
|
54
|
-
root: Polygon | MultiPolygon
|
|
55
|
-
|
|
56
|
-
|
|
57
46
|
class Measure(BaseFeature):
|
|
58
|
-
"""
|
|
59
|
-
Basisklasse für Maße
|
|
60
|
-
"""
|
|
47
|
+
"""Basisklasse für Maße"""
|
|
61
48
|
|
|
62
49
|
value: Annotated[float, Field(description="Wert des Maßes")]
|
|
63
50
|
|
|
64
51
|
|
|
65
52
|
class Length(Measure):
|
|
66
|
-
"""
|
|
67
|
-
Angabe einer Länge in Metern
|
|
68
|
-
"""
|
|
53
|
+
"""Angabe einer Länge in Metern"""
|
|
69
54
|
|
|
70
55
|
uom: Annotated[str | None, Field(description="Maßeinheit")] = "m"
|
|
71
56
|
|
|
72
57
|
|
|
73
58
|
class Area(Measure):
|
|
74
|
-
"""
|
|
75
|
-
Angabe einer Fläche in Quadratmetern
|
|
76
|
-
"""
|
|
59
|
+
"""Angabe einer Fläche in Quadratmetern"""
|
|
77
60
|
|
|
78
61
|
uom: Annotated[str | None, Field(description="Maßeinheit")] = "m2"
|
|
79
62
|
|
|
80
63
|
|
|
81
64
|
class Angle(Measure):
|
|
82
|
-
"""
|
|
83
|
-
Angabe eines Winkels in Grad
|
|
84
|
-
"""
|
|
65
|
+
"""Angabe eines Winkels in Grad"""
|
|
85
66
|
|
|
86
67
|
uom: Annotated[str | None, Field(description="Maßeinheit")] = "grad"
|
|
87
68
|
|
|
88
69
|
|
|
89
70
|
class Volume(Measure):
|
|
90
|
-
"""
|
|
91
|
-
Angabe eines Volumens in Kubikmetern
|
|
92
|
-
"""
|
|
71
|
+
"""Angabe eines Volumens in Kubikmetern"""
|
|
93
72
|
|
|
94
73
|
uom: Annotated[str | None, Field(description="Maßeinheit")] = "m3"
|
|
95
74
|
|
|
96
75
|
|
|
97
|
-
class
|
|
98
|
-
"""
|
|
99
|
-
Angabe einer Skala in Prozent
|
|
100
|
-
"""
|
|
76
|
+
class Velocity(Measure):
|
|
77
|
+
"""Angabe einer Geschwindigkeit in km/h"""
|
|
101
78
|
|
|
102
|
-
uom: Annotated[str | None, Field(description="Maßeinheit")] = "
|
|
79
|
+
uom: Annotated[str | None, Field(description="Maßeinheit")] = "km/h"
|
|
103
80
|
|
|
104
81
|
|
|
105
|
-
class
|
|
106
|
-
"""
|
|
107
|
-
Angabe einer Geschwindigkeit in Kilometern pro Stunde
|
|
108
|
-
"""
|
|
82
|
+
class Scale(Measure):
|
|
83
|
+
"""Angabe einer Skala in Prozent"""
|
|
109
84
|
|
|
110
|
-
uom: Annotated[str | None, Field(description="Maßeinheit")] = "
|
|
85
|
+
uom: Annotated[str | None, Field(description="Maßeinheit")] = "vH"
|
|
111
86
|
|
|
112
87
|
|
|
113
88
|
class GenericMeasure(Measure):
|
|
114
|
-
"""
|
|
115
|
-
Nicht näher konkretisiertes Maß
|
|
116
|
-
"""
|
|
89
|
+
"""Nicht näher konkretisiertes Maß"""
|
|
117
90
|
|
|
118
|
-
uom: Annotated[str | None, Field(description="Maßeinheit")] = "
|
|
91
|
+
uom: Annotated[str | None, Field(description="Maßeinheit")] = "unknown"
|
|
119
92
|
|
|
120
93
|
|
|
121
94
|
class VoidReasonValue(BaseFeature):
|
|
122
|
-
"""
|
|
123
|
-
Reasons for void values.
|
|
124
|
-
"""
|
|
95
|
+
"""Reasons for void values."""
|
|
125
96
|
|
|
126
97
|
nilReason: Annotated[AnyUrl, Field(description="Reason")]
|
|
127
98
|
|