Skip to content

Commit 1fca356

Browse files
Merge pull request #2 from geopython/georss
GeoRSS
2 parents 84549ba + 8c75637 commit 1fca356

File tree

7 files changed

+261
-9
lines changed

7 files changed

+261
-9
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $ pip install pygml
1414

1515
## Features
1616

17-
Parse GML 3.1, 3.2 and compact encoded GML 3.3 geometries to a [`__geo_interface__`](https://gist.github.com/sgillies/2217756) compliant class.
17+
Parse GML 3.1, 3.2, compact encoded GML 3.3 and GeoRSS geometries to a [`__geo_interface__`](https://gist.github.com/sgillies/2217756) compliant class.
1818

1919

2020
```python
@@ -25,7 +25,7 @@ Parse GML 3.1, 3.2 and compact encoded GML 3.3 geometries to a [`__geo_interface
2525
... </gml:Point>
2626
... """)
2727
>>> print(geom)
28-
Geometry(geometry={'type': 'Point', 'coordinates': [1.0, 1.0]})
28+
Geometry(geometry={'type': 'Point', 'coordinates': (1.0, 1.0)})
2929
>>> print(geom.__geo_interface__)
30-
{'type': 'Point', 'coordinates': [1.0, 1.0]}
30+
{'type': 'Point', 'coordinates': (1.0, 1.0)}
3131
```

pygml/basics.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ def parse_pos(value: str) -> Coordinate:
8484
return tuple(float(v) for v in value.split())
8585

8686

87+
def swap_coordinate_xy(coordinate: Coordinate) -> Coordinate:
88+
""" Swaps the X and Y coordinates of a given coordinate
89+
"""
90+
return (coordinate[1], coordinate[0], *coordinate[2:])
91+
92+
8793
def swap_coordinates_xy(coordinates: Coordinates) -> Coordinates:
8894
""" Swaps the X and Y coordinates of a given coordinates list
8995
"""

pygml/georss.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# ------------------------------------------------------------------------------
2+
#
3+
# Project: pygml <https://github.com/geopython/pygml>
4+
# Authors: Fabian Schindler <[email protected]>
5+
#
6+
# ------------------------------------------------------------------------------
7+
# Copyright (C) 2021 EOX IT Services GmbH
8+
#
9+
# Permission is hereby granted, free of charge, to any person obtaining a copy
10+
# of this software and associated documentation files (the "Software"), to deal
11+
# in the Software without restriction, including without limitation the rights
12+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
# copies of the Software, and to permit persons to whom the Software is
14+
# furnished to do so, subject to the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be included in
17+
# all copies of this Software or works derived from this Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
# THE SOFTWARE.
26+
# ------------------------------------------------------------------------------
27+
28+
from typing import List
29+
30+
from lxml import etree
31+
32+
from .basics import (
33+
parse_pos, parse_poslist, swap_coordinate_xy, swap_coordinates_xy
34+
)
35+
from .types import GeomDict
36+
from .pre_v32 import NAMESPACE as NAMESPACE_PRE32, parse_pre_v32
37+
from .v32 import NAMESPACE as NAMESPACE_32, parse_v32
38+
from .v33 import NAMESPACE as NAMESPACE_33_CE, parse_v33_ce
39+
40+
41+
NAMESPACE = 'http://www.georss.org/georss'
42+
NSMAP = {'georss': NAMESPACE}
43+
44+
45+
Element = etree._Element
46+
Elements = List[Element]
47+
48+
49+
def parse_georss(element: Element) -> GeomDict:
50+
""" Parses the GeoRSS basic elements to their respective GeoJSON
51+
representation. As all coordinates in GeoRSS are expressed in
52+
WGS84 and in Latitude/Longitude order, the coordinates are
53+
swapped to XY order.
54+
55+
In case of georss:where, it is expected that it contains a
56+
single GML element which is parsed as either GML 3.1.1, GML 3.2
57+
or GML 3.3 CE.
58+
"""
59+
qname = etree.QName(element.tag)
60+
if qname.namespace != NAMESPACE:
61+
raise ValueError(f'Unsupported namespace {qname.namespace}')
62+
63+
bbox = None
64+
localname = qname.localname
65+
66+
if localname == 'point':
67+
type_ = 'Point'
68+
coordinates = swap_coordinate_xy(parse_pos(element.text))
69+
elif localname == 'line':
70+
type_ = 'LineString'
71+
coordinates = swap_coordinates_xy(parse_poslist(element.text))
72+
elif localname == 'box':
73+
# boxes are expanded to Polygons, but store the 'bbox' value
74+
type_ = 'Polygon'
75+
low, high = swap_coordinates_xy(parse_poslist(element.text))
76+
lx, ly = low
77+
hx, hy = high
78+
coordinates = [
79+
[
80+
(lx, ly),
81+
(lx, hy),
82+
(hx, hy),
83+
(hx, ly),
84+
(lx, ly),
85+
]
86+
]
87+
bbox = (lx, ly, hx, hy)
88+
elif localname == 'polygon':
89+
type_ = 'Polygon'
90+
coordinates = [swap_coordinates_xy(parse_poslist(element.text))]
91+
92+
elif localname == 'where':
93+
# special handling here: defer to the gml definition. Although,
94+
# only GML 3.1.1 is officially supported, we also allow GML 3.2 and 3.3
95+
if not len(element) == 1:
96+
raise ValueError(
97+
'Invalid number of child elements in georss:where'
98+
)
99+
child = element[0]
100+
child_namespace = etree.QName(child.tag).namespace
101+
102+
if child_namespace == NAMESPACE_PRE32:
103+
return parse_pre_v32(child)
104+
elif child_namespace == NAMESPACE_32:
105+
return parse_v32(child)
106+
elif child_namespace == NAMESPACE_33_CE:
107+
return parse_v33_ce(child)
108+
else:
109+
raise ValueError(
110+
f'Unsupported child element in georss:where: {child.tag}'
111+
)
112+
else:
113+
raise ValueError(f'Unsupported georss element: {localname}')
114+
115+
result = {
116+
'type': type_,
117+
'coordinates': coordinates,
118+
}
119+
if bbox:
120+
result['bbox'] = bbox
121+
122+
return result

pygml/parse.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
from typing import Union
3030
from lxml import etree
3131

32-
from .pre_v32 import parse_pre_v32
33-
from .v32 import parse_v32
34-
from .v33 import parse_v33_ce
32+
from .georss import NAMESPACE as NAMESPACE_GEORSS, parse_georss
33+
from .pre_v32 import NAMESPACE as NAMESPACE_PRE_v32, parse_pre_v32
34+
from .v32 import NAMESPACE as NAMESPACE_32, parse_v32
35+
from .v33 import NAMESPACE as NAMESPACE_33_CE, parse_v33_ce
3536
from .types import Geometry
3637

3738

@@ -45,11 +46,13 @@ def parse(source: Union[etree._Element, str]) -> Geometry:
4546
element = etree.fromstring(source)
4647

4748
namespace = etree.QName(element.tag).namespace
48-
if namespace == 'http://www.opengis.net/gml':
49+
if namespace == NAMESPACE_PRE_v32:
4950
result = parse_pre_v32(element)
50-
elif namespace == 'http://www.opengis.net/gml/3.2':
51+
elif namespace == NAMESPACE_32:
5152
result = parse_v32(element)
52-
elif namespace == 'http://www.opengis.net/gml/3.3/ce':
53+
elif namespace == NAMESPACE_33_CE:
5354
result = parse_v33_ce(element)
55+
elif namespace == NAMESPACE_GEORSS:
56+
result = parse_georss(element)
5457

5558
return Geometry(result)

pygml/pre_v32.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,9 @@
2626
# ------------------------------------------------------------------------------
2727

2828

29+
NAMESPACE = 'http://www.opengis.net/gml'
30+
NSMAP = {'gml': NAMESPACE}
31+
32+
2933
def parse_pre_v32():
3034
pass

pygml/v33.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,9 @@
2626
# ------------------------------------------------------------------------------
2727

2828

29+
NAMESPACE = 'http://www.opengis.net/gml/3.3/ce'
30+
NSMAP = {'gmlce': NAMESPACE}
31+
32+
2933
def parse_v33_ce():
3034
pass

tests/test_georss.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# ------------------------------------------------------------------------------
2+
#
3+
# Project: pygml <https://github.com/geopython/pygml>
4+
# Authors: Fabian Schindler <[email protected]>
5+
#
6+
# ------------------------------------------------------------------------------
7+
# Copyright (C) 2021 EOX IT Services GmbH
8+
#
9+
# Permission is hereby granted, free of charge, to any person obtaining a copy
10+
# of this software and associated documentation files (the "Software"), to deal
11+
# in the Software without restriction, including without limitation the rights
12+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
# copies of the Software, and to permit persons to whom the Software is
14+
# furnished to do so, subject to the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be included in
17+
# all copies of this Software or works derived from this Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
# THE SOFTWARE.
26+
# ------------------------------------------------------------------------------
27+
28+
29+
from lxml import etree
30+
31+
from pygml.georss import parse_georss
32+
33+
34+
def test_parse_point():
35+
# basic test
36+
result = parse_georss(
37+
etree.fromstring("""
38+
<georss:point xmlns:georss="http://www.georss.org/georss">
39+
1.0 1.0
40+
</georss:point>
41+
""")
42+
)
43+
assert result == {'type': 'Point', 'coordinates': (1.0, 1.0)}
44+
45+
46+
def test_parse_line():
47+
# basic test
48+
result = parse_georss(
49+
etree.fromstring("""
50+
<georss:line xmlns:georss="http://www.georss.org/georss">
51+
1.0 2.0 2.0 1.0
52+
</georss:line>
53+
""")
54+
)
55+
assert result == {
56+
'type': 'LineString',
57+
'coordinates': [
58+
(2.0, 1.0),
59+
(1.0, 2.0)
60+
]
61+
}
62+
63+
64+
def test_parse_box():
65+
# basic test
66+
result = parse_georss(
67+
etree.fromstring("""
68+
<georss:box xmlns:georss="http://www.georss.org/georss">
69+
1.0 0.5 2.0 1.5
70+
</georss:box>
71+
""")
72+
)
73+
assert result == {
74+
'type': 'Polygon',
75+
'bbox': (0.5, 1.0, 1.5, 2.0),
76+
'coordinates': [
77+
[
78+
(0.5, 1.0),
79+
(0.5, 2.0),
80+
(1.5, 2.0),
81+
(1.5, 1.0),
82+
(0.5, 1.0)
83+
]
84+
]
85+
}
86+
87+
88+
def test_parse_polygon():
89+
# basic test
90+
result = parse_georss(
91+
etree.fromstring("""
92+
<georss:polygon xmlns:georss="http://www.georss.org/georss">
93+
1.0 0.5 2.0 0.5 2.0 1.5 1.0 1.5 1.0 0.5
94+
</georss:polygon>
95+
""")
96+
)
97+
assert result == {
98+
'type': 'Polygon',
99+
'coordinates': [
100+
[
101+
(0.5, 1.0),
102+
(0.5, 2.0),
103+
(1.5, 2.0),
104+
(1.5, 1.0),
105+
(0.5, 1.0)
106+
]
107+
]
108+
}
109+
110+
111+
def test_parse_where():
112+
# TODO: add tests as soon as gml 3.1 is done
113+
pass

0 commit comments

Comments
 (0)