NCZ2Geo 0.1.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.
ncz2geo/__init__.py ADDED
@@ -0,0 +1,30 @@
1
+ """NCZ2Geo: Netcad NCZ/NCA reader with PlanGML and e-Plan style metadata."""
2
+
3
+ from .geojson import entities_to_feature_collection, write_geojson
4
+ from .plangml import (
5
+ LayerClassification,
6
+ classify_entity,
7
+ classify_layer,
8
+ classify_layers,
9
+ detect_plan_type,
10
+ normalize_layer_name,
11
+ )
12
+ from .reader import LayerSummary, NetcadReader, inspect_source, parse_netcad
13
+
14
+ __version__ = "0.1.0"
15
+
16
+ __all__ = [
17
+ "LayerClassification",
18
+ "LayerSummary",
19
+ "NetcadReader",
20
+ "__version__",
21
+ "classify_entity",
22
+ "classify_layer",
23
+ "classify_layers",
24
+ "detect_plan_type",
25
+ "entities_to_feature_collection",
26
+ "inspect_source",
27
+ "normalize_layer_name",
28
+ "parse_netcad",
29
+ "write_geojson",
30
+ ]
ncz2geo/cli.py ADDED
@@ -0,0 +1,119 @@
1
+ """Command-line interface for NCZ2Geo."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from . import __version__
11
+ from .geojson import write_geojson
12
+ from .plangml import classify_layer
13
+ from .reader import NetcadReader, parse_netcad
14
+
15
+
16
+ def build_parser() -> argparse.ArgumentParser:
17
+ parser = argparse.ArgumentParser(
18
+ prog="ncz2geo",
19
+ description="Inspect Netcad NCZ/NCA files and export PlanGML-aware GeoJSON.",
20
+ )
21
+ parser.add_argument("--version", action="version", version=f"NCZ2Geo {__version__}")
22
+ subparsers = parser.add_subparsers(dest="command", required=True)
23
+
24
+ inspect_parser = subparsers.add_parser("inspect", help="List NCZ/NCA layers and PlanGML matches.")
25
+ inspect_parser.add_argument("source", type=Path)
26
+ inspect_parser.add_argument("--plan-type", default="UIP", choices=("UIP", "NIP", "CDP"))
27
+ inspect_parser.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
28
+
29
+ convert_parser = subparsers.add_parser("convert", help="Convert an NCZ/NCA file to styled GeoJSON.")
30
+ convert_parser.add_argument("source", type=Path)
31
+ convert_parser.add_argument("output", type=Path)
32
+ convert_parser.add_argument("--plan-type", default="UIP", choices=("UIP", "NIP", "CDP"))
33
+ convert_parser.add_argument(
34
+ "--layers",
35
+ help="Comma-separated layer codes to decode; defaults to all layers.",
36
+ )
37
+ return parser
38
+
39
+
40
+ def main(argv: list[str] | None = None) -> int:
41
+ parser = build_parser()
42
+ args = parser.parse_args(argv)
43
+ if args.command == "inspect":
44
+ return _inspect(args)
45
+ if args.command == "convert":
46
+ return _convert(args)
47
+ parser.error(f"Unknown command: {args.command}")
48
+ return 2
49
+
50
+
51
+ def _inspect(args: argparse.Namespace) -> int:
52
+ reader = NetcadReader(args.source).index()
53
+ summaries = reader.layer_summaries()
54
+ classified = [
55
+ {
56
+ **summary.to_dict(),
57
+ "plangml": classify_layer(summary.layer_name, args.plan_type).to_dict(),
58
+ }
59
+ for summary in summaries
60
+ ]
61
+ if args.json:
62
+ print(
63
+ json.dumps(
64
+ {
65
+ "source": str(args.source),
66
+ "backend": reader.backend,
67
+ "from_cache": reader.from_cache,
68
+ "version_name": reader.version_name,
69
+ "epsg": reader.epsg,
70
+ "projection_text": reader.projection_text,
71
+ "plan_type": args.plan_type,
72
+ "layers": classified,
73
+ },
74
+ ensure_ascii=False,
75
+ indent=2,
76
+ )
77
+ )
78
+ return 0
79
+
80
+ print(f"Source: {args.source}")
81
+ print(f"Backend: {reader.backend}")
82
+ print(f"Plan type: {args.plan_type}")
83
+ if reader.version_name:
84
+ print(f"Version: {reader.version_name}")
85
+ if reader.epsg:
86
+ print(f"CRS: {reader.epsg}")
87
+ if not classified:
88
+ print("No supported Netcad geometry layers found.")
89
+ return 0
90
+ print("Layers:")
91
+ for item in classified:
92
+ families = ", ".join(item["families"]) or "-"
93
+ plangml = item["plangml"]
94
+ identity = plangml["identity"] or {}
95
+ label = ""
96
+ if plangml["style"]:
97
+ label = plangml["style"].get("label", "")
98
+ tabaka = identity.get("tabaka") or plangml["style_key"] or "unmatched"
99
+ print(
100
+ f" {item['layer_code']:>3} {item['layer_name'] or '(unnamed)'} "
101
+ f"{item['record_count']} record(s) [{families}] {tabaka} {label}"
102
+ )
103
+ return 0
104
+
105
+
106
+ def _convert(args: argparse.Namespace) -> int:
107
+ if args.layers:
108
+ layer_codes = [int(part.strip()) for part in args.layers.split(",") if part.strip()]
109
+ reader = NetcadReader(args.source).index()
110
+ entities = reader.decode_layers(layer_codes)
111
+ else:
112
+ entities = parse_netcad(args.source).entities
113
+ collection = write_geojson(entities, args.output, plan_type=args.plan_type)
114
+ print(f"Wrote {len(collection['features'])} feature(s) to {args.output}")
115
+ return 0
116
+
117
+
118
+ if __name__ == "__main__":
119
+ raise SystemExit(main(sys.argv[1:]))