Coverage for brodata / gml.py: 76%
58 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-20 14:37 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-20 14:37 +0000
1from shapely.geometry import (
2 Point,
3 LineString,
4 Polygon,
5 MultiPoint,
6 MultiLineString,
7 MultiPolygon,
8 GeometryCollection,
9)
12# GML 3.2 namespace
13ns = {"gml": "http://www.opengis.net/gml/3.2"}
16def parse_poslist(poslist_text, dim=2):
17 """Convert GML posList string to list of coordinate tuples (2D or 3D)."""
18 numbers = list(map(float, poslist_text.strip().split()))
19 if len(numbers) % dim != 0:
20 raise ValueError(f"Number of coordinates not divisible by dimension {dim}")
21 return [tuple(numbers[i : i + dim]) for i in range(0, len(numbers), dim)]
24def polygon_from_gml(polygon_node, dim=2):
25 """Convert GML 3.2 Polygon or PolygonPatch node to Shapely Polygon."""
26 exterior_text = polygon_node.findtext(
27 ".//gml:exterior/gml:LinearRing/gml:posList", namespaces=ns
28 )
29 if not exterior_text:
30 raise ValueError("Polygon has no exterior")
31 exterior = parse_poslist(exterior_text, dim=dim)
33 interiors = []
34 for inner in polygon_node.findall(
35 ".//gml:interior/gml:LinearRing/gml:posList", namespaces=ns
36 ):
37 interiors.append(parse_poslist(inner.text, dim=dim))
39 return Polygon(exterior, interiors)
42def multisurface_from_gml(ms_node, dim=2):
43 """Convert GML 3.2 MultiSurface node to Shapely MultiPolygon (supports Surface/PolygonPatch)."""
44 polygons = []
45 for member in ms_node.findall(".//gml:surfaceMember", namespaces=ns):
46 surface_node = member.find(".//gml:Surface", namespaces=ns)
47 if surface_node is not None:
48 for patch in surface_node.findall(".//gml:PolygonPatch", namespaces=ns):
49 polygons.append(polygon_from_gml(patch, dim=dim))
50 else:
51 poly_node = member.find(".//gml:Polygon", namespaces=ns)
52 if poly_node is not None:
53 polygons.append(polygon_from_gml(poly_node, dim=dim))
54 return MultiPolygon(polygons)
57def multigeometry_from_gml(mg_node, dim=2):
58 """Convert GML 3.2 MultiGeometry to Shapely GeometryCollection."""
59 geometries = []
60 for member in mg_node.findall(".//gml:geometryMember", namespaces=ns):
61 for child in list(member):
62 geometries.append(parse_geometry(child, dim=dim))
63 return GeometryCollection(geometries)
66def parse_geometry(node, dim=2):
67 """Parse any GML 3.2 geometry node to Shapely."""
68 tag = node.tag.split("}")[-1]
70 if tag == "Point":
71 pos_text = node.findtext(".//gml:pos", namespaces=ns)
72 coords = tuple(map(float, pos_text.strip().split()))
73 return Point(coords)
75 elif tag == "LineString":
76 pos_list = node.findtext(".//gml:posList", namespaces=ns)
77 return LineString(parse_poslist(pos_list, dim=dim))
79 elif tag in ("Polygon", "PolygonPatch"):
80 return polygon_from_gml(node, dim=dim)
82 elif tag == "MultiSurface":
83 return multisurface_from_gml(node, dim=dim)
85 elif tag == "MultiPolygon":
86 polygons = [
87 polygon_from_gml(p, dim=dim)
88 for p in node.findall(".//gml:polygonMember/gml:Polygon", namespaces=ns)
89 ]
90 return MultiPolygon(polygons)
92 elif tag == "MultiLineString":
93 lines = [
94 LineString(
95 parse_poslist(l.findtext(".//gml:posList", namespaces=ns), dim=dim)
96 )
97 for l in node.findall(
98 ".//gml:lineStringMember/gml:LineString", namespaces=ns
99 )
100 ]
101 return MultiLineString(lines)
103 elif tag == "MultiPoint":
104 points = [
105 Point(tuple(map(float, p.findtext(".//gml:pos", namespaces=ns).split())))
106 for p in node.findall(".//gml:pointMember/gml:Point", namespaces=ns)
107 ]
108 return MultiPoint(points)
110 elif tag == "MultiGeometry":
111 return multigeometry_from_gml(node, dim=dim)
113 else:
114 raise NotImplementedError(f"GML type {tag} not supported")