Coverage for gws-app/gws/gis/gml/parser.py: 0%
104 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-17 01:37 +0200
1"""GML geometry parsers."""
3import gws
4import gws.base.shape
5import gws.gis.bounds
6import gws.gis.crs
7import gws.gis.extent
10class Error(gws.Error):
11 pass
14_GEOMETRY_TAGS = {
15 'curve',
16 'linearring',
17 'linestring',
18 'linestringsegment',
19 'multicurve',
20 'multilinestring',
21 'multipoint',
22 'multipolygon',
23 'multisurface',
24 'point',
25 'polygon',
26}
29def parse_envelope(el: gws.XmlElement, default_crs: gws.Crs = None, always_xy: bool = False) -> gws.Bounds:
30 """Parse a gml:Box/gml:Envelope element
32 Args:
33 el: A xml-Element.
34 default_crs: A Crs object.
35 always_xy: If ``True``, coordinates are assumed to be in the XY (lon/lat) order.
37 Returns:
38 A Bounds object.
39 """
41 # GML2: <gml:Box><gml:coordinates>1,2 3,4
42 # GML3: <gml:Envelope srsDimension="2"><gml:lowerCorner>1 2 <gml:upperCorner>3 4
44 crs = gws.gis.crs.get(el.get('srsName')) or default_crs
45 if not crs:
46 raise Error('no CRS declared for envelope')
48 coords = None
50 try:
51 if el.lcName == 'box':
52 coords = _coords(el)
54 elif el.lcName == 'envelope':
55 coords = [None, None]
56 for coord_el in el:
57 if coord_el.lcName == 'lowercorner':
58 coords[0] = _coords_pos(coord_el)[0]
59 if coord_el.lcName == 'uppercorner':
60 coords[1] = _coords_pos(coord_el)[0]
62 ext = gws.gis.extent.from_points(*coords)
64 except Exception as exc:
65 raise Error('envelope parse error') from exc
67 return gws.gis.bounds.from_extent(ext, crs, always_xy)
70def is_geometry_element(el: gws.XmlElement) -> bool:
71 """Checks if the current element is a valid geometry type.
73 Args:
74 el: A GML element.
76 Returns:
77 ``True`` if the element is a geometry type.
78 """
80 return el.lcName in _GEOMETRY_TAGS
83def parse_shape(el: gws.XmlElement, default_crs: gws.Crs = None, always_xy: bool = False) -> gws.Shape:
84 """Convert a GML geometry element to a Shape.
86 Args:
87 el: A GML element.
88 default_crs: A Crs object.
89 always_xy: If ``True``, coordinates are assumed to be in the XY (lon/lat) order.
91 Returns:
92 A GWS shape object.
93 """
95 crs = gws.gis.crs.get(el.get('srsName')) or default_crs
96 if not crs:
97 raise Error('no CRS declared')
99 dct = parse_geometry(el)
100 return gws.base.shape.from_geojson(dct, crs, always_xy)
103def parse_geometry(el: gws.XmlElement) -> dict:
104 """Convert a GML geometry element to a geometry dict.
106 Args:
107 el: A GML element.
109 Returns:
110 The GML geometry as a geometry dict.
111 """
113 try:
114 return _to_geom(el)
115 except Exception as exc:
116 raise Error('parse error') from exc
119##
121def _to_geom(el: gws.XmlElement):
122 if el.lcName == 'point':
123 # <gml:Point> pos/coordinates
124 return {'type': 'Point', 'coordinates': _coords(el)[0]}
126 if el.lcName in {'linestring', 'linearring', 'linestringsegment'}:
127 # <gml:LineString> posList/coordinates
128 return {'type': 'LineString', 'coordinates': _coords(el)}
130 if el.lcName == 'curve':
131 # GML3: <gml:Curve> <gml:segments> <gml:LineStringSegment>
132 # NB we only take the first segment
133 return _to_geom(el[0][0])
135 if el.lcName == 'polygon':
136 # GML2: <gml:Polygon> <gml:outerBoundaryIs> <gml:LinearRing> <gml:innerBoundaryIs> <gml:LinearRing>...
137 # GML3: <gml:Polygon> <gml:exterior> <gml:LinearRing> <gml:interior> <gml:LinearRing>...
138 return {'type': 'Polygon', 'coordinates': _rings(el)}
140 if el.lcName == 'multipoint':
141 # <gml:MultiPoint> <gml:pointMember> <gml:Point>
142 return {'type': 'MultiPoint', 'coordinates': [m['coordinates'] for m in _members(el)]}
144 if el.lcName in {'multilinestring', 'multicurve'}:
145 # GML2: <gml:MultiLineString> <gml:lineStringMember> <gml:LineString>
146 # GML3: <gml:MultiCurve> <gml:curveMember> <gml:Curve>
147 return {'type': 'MultiLineString', 'coordinates': [m['coordinates'] for m in _members(el)]}
149 if el.lcName in {'multipolygon', 'multisurface'}:
150 # GML2: <gml:MultiPolygon> <gml:polygonMember> <gml:Polygon>
151 # GML3: <gml:MultiSurface> <gml:surfaceMember> <gml:Polygon>
152 return {'type': 'MultiPolygon', 'coordinates': [m['coordinates'] for m in _members(el)]}
154 raise Error(f'unknown GML geometry tag {el.name!r}')
157def _members(multi_el: gws.XmlElement):
158 ms = []
160 for el in multi_el:
161 if el.lcName.endswith('member'):
162 ms.append(_to_geom(el[0]))
164 return ms
167def _rings(poly_el):
168 rings = [None]
170 for el in poly_el:
171 if el.lcName in {'exterior', 'outerboundaryis'}:
172 d = _to_geom(el[0])
173 rings[0] = d['coordinates']
174 continue
176 if el.lcName in {'interior', 'innerboundaryis'}:
177 d = _to_geom(el[0])
178 rings.append(d['coordinates'])
179 continue
181 return rings
184def _coords(any_el):
185 for el in any_el:
186 if el.lcName == 'coordinates':
187 return _coords_coordinates(el)
188 if el.lcName == 'pos':
189 return _coords_pos(el)
190 if el.lcName == 'poslist':
191 return _coords_poslist(el)
194def _coords_coordinates(el):
195 # <gml:coordinates>1,2 3,4...
197 ts = el.get('ts', default=' ')
198 cs = el.get('cs', default=',')
200 clist = []
202 for pair in el.text.split(ts):
203 x, y = pair.split(cs)
204 clist.append([float(x), float(y)])
206 return clist
209def _coords_pos(el):
210 # <gml:pos srsDimension="2">1 2</gml:pos>
212 s = el.text.split()
213 x = s[0]
214 y = s[1]
215 # NB pos returns a list of points too!
216 return [[float(x), float(y)]]
219def _coords_poslist(el):
220 # <gml:posList srsDimension="2">1 2 3...
222 clist = []
223 dim = int(el.get('srsDimension', default='2'))
224 s = el.text.split()
226 for n in range(0, len(s), dim):
227 x = s[n]
228 y = s[n + 1]
229 clist.append([float(x), float(y)])
231 return clist