99
1010 :license: FreeBSD and LGPL, see license.* for more details.
1111'''
12+ import numbers
13+ import warnings
1214
1315from lxml import etree
1416from feedgen .ext .base import BaseEntryExtension
1517
1618
19+ class GeoRSSPolygonInteriorWarning (Warning ):
20+ """
21+ Simple placeholder for warning about ignored polygon interiors.
22+
23+ Stores the original geom on a ``geom`` attribute (if required warnings are
24+ raised as errors).
25+ """
26+
27+ def __init__ (self , geom , * args , ** kwargs ):
28+ self .geom = geom
29+ super (GeoRSSPolygonInteriorWarning , self ).__init__ (* args , ** kwargs )
30+
31+ def __str__ (self ):
32+ return '{:d} interiors of polygon ignored' .format (
33+ len (self .geom .__geo_interface__ ['coordinates' ]) - 1
34+ ) # ignore exterior in count
35+
36+
37+ class GeoRSSGeometryError (ValueError ):
38+ """
39+ Subclass of ValueError for a GeoRSS geometry error
40+
41+ Only some geometries are supported in Simple GeoRSS, so if not raise an
42+ error. Offending geometry is stored on the ``geom`` attribute.
43+ """
44+
45+ def __init__ (self , geom , * args , ** kwargs ):
46+ self .geom = geom
47+ super (GeoRSSGeometryError , self ).__init__ (* args , ** kwargs )
48+
49+ def __str__ (self ):
50+ msg = "Geometry of type '{}' not in Point, Linestring or Polygon"
51+ return msg .format (
52+ self .geom .__geo_interface__ ['type' ]
53+ )
54+
55+
1756class GeoEntryExtension (BaseEntryExtension ):
1857 '''FeedEntry extension for Simple GeoRSS.
1958 '''
2059
2160 def __init__ (self ):
22- # Simple GeoRSS tag
61+ '''Simple GeoRSS tag'''
62+ # geometries
2363 self .__point = None
64+ self .__line = None
65+ self .__polygon = None
66+ self .__box = None
67+
68+ # additional properties
69+ self .__featuretypetag = None
70+ self .__relationshiptag = None
71+ self .__featurename = None
72+
73+ # elevation
74+ self .__elev = None
75+ self .__floor = None
76+
77+ # radius
78+ self .__radius = None
2479
2580 def extend_file (self , entry ):
2681 '''Add additional fields to an RSS item.
@@ -34,6 +89,48 @@ def extend_file(self, entry):
3489 point = etree .SubElement (entry , '{%s}point' % GEO_NS )
3590 point .text = self .__point
3691
92+ if self .__line :
93+ line = etree .SubElement (entry , '{%s}line' % GEO_NS )
94+ line .text = self .__line
95+
96+ if self .__polygon :
97+ polygon = etree .SubElement (entry , '{%s}polygon' % GEO_NS )
98+ polygon .text = self .__polygon
99+
100+ if self .__box :
101+ box = etree .SubElement (entry , '{%s}box' % GEO_NS )
102+ box .text = self .__box
103+
104+ if self .__featuretypetag :
105+ featuretypetag = etree .SubElement (
106+ entry ,
107+ '{%s}featuretypetag' % GEO_NS
108+ )
109+ featuretypetag .text = self .__featuretypetag
110+
111+ if self .__relationshiptag :
112+ relationshiptag = etree .SubElement (
113+ entry ,
114+ '{%s}relationshiptag' % GEO_NS
115+ )
116+ relationshiptag .text = self .__relationshiptag
117+
118+ if self .__featurename :
119+ featurename = etree .SubElement (entry , '{%s}featurename' % GEO_NS )
120+ featurename .text = self .__featurename
121+
122+ if self .__elev :
123+ elevation = etree .SubElement (entry , '{%s}elev' % GEO_NS )
124+ elevation .text = str (self .__elev )
125+
126+ if self .__floor :
127+ floor = etree .SubElement (entry , '{%s}floor' % GEO_NS )
128+ floor .text = str (self .__floor )
129+
130+ if self .__radius :
131+ radius = etree .SubElement (entry , '{%s}radius' % GEO_NS )
132+ radius .text = str (self .__radius )
133+
37134 return entry
38135
39136 def extend_rss (self , entry ):
@@ -53,3 +150,186 @@ def point(self, point=None):
53150 self .__point = point
54151
55152 return self .__point
153+
154+ def line (self , line = None ):
155+ '''Get or set the georss:line of the entry
156+
157+ :param point: The GeoRSS formatted line (i.e. "45.256 -110.45 46.46
158+ -109.48 43.84 -109.86")
159+ :return: The current georss:line of the entry
160+ '''
161+ if line is not None :
162+ self .__line = line
163+
164+ return self .__line
165+
166+ def polygon (self , polygon = None ):
167+ '''Get or set the georss:polygon of the entry
168+
169+ :param polygon: The GeoRSS formatted polygon (i.e. "45.256 -110.45
170+ 46.46 -109.48 43.84 -109.86 45.256 -110.45")
171+ :return: The current georss:polygon of the entry
172+ '''
173+ if polygon is not None :
174+ self .__polygon = polygon
175+
176+ return self .__polygon
177+
178+ def box (self , box = None ):
179+ '''
180+ Get or set the georss:box of the entry
181+
182+ :param box: The GeoRSS formatted box (i.e. "42.943 -71.032 43.039
183+ -69.856")
184+ :return: The current georss:box of the entry
185+ '''
186+ if box is not None :
187+ self .__box = box
188+
189+ return self .__box
190+
191+ def featuretypetag (self , featuretypetag = None ):
192+ '''
193+ Get or set the georss:featuretypetag of the entry
194+
195+ :param featuretypetag: The GeoRSS feaaturertyptag (e.g. "city")
196+ :return: The current georss:featurertypetag
197+ '''
198+ if featuretypetag is not None :
199+ self .__featuretypetag = featuretypetag
200+
201+ return self .__featuretypetag
202+
203+ def relationshiptag (self , relationshiptag = None ):
204+ '''
205+ Get or set the georss:relationshiptag of the entry
206+
207+ :param relationshiptag: The GeoRSS relationshiptag (e.g.
208+ "is-centred-at")
209+ :return: the current georss:relationshiptag
210+ '''
211+ if relationshiptag is not None :
212+ self .__relationshiptag = relationshiptag
213+
214+ return self .__relationshiptag
215+
216+ def featurename (self , featurename = None ):
217+ '''
218+ Get or set the georss:featurename of the entry
219+
220+ :param featuretypetag: The GeoRSS featurename (e.g. "Footscray")
221+ :return: the current georss:featurename
222+ '''
223+ if featurename is not None :
224+ self .__featurename = featurename
225+
226+ return self .__featurename
227+
228+ def elev (self , elev = None ):
229+ '''
230+ Get or set the georss:elev of the entry
231+
232+ :param elev: The GeoRSS elevation (e.g. 100.3)
233+ :type elev: numbers.Number
234+ :return: the current georss:elev
235+ '''
236+ if elev is not None :
237+ if not isinstance (elev , numbers .Number ):
238+ raise ValueError ("elev tag must be numeric: {}" .format (elev ))
239+
240+ self .__elev = elev
241+
242+ return self .__elev
243+
244+ def floor (self , floor = None ):
245+ '''
246+ Get or set the georss:floor of the entry
247+
248+ :param floor: The GeoRSS floor (e.g. 4)
249+ :type floor: int
250+ :return: the current georss:floor
251+ '''
252+ if floor is not None :
253+ if not isinstance (floor , int ):
254+ raise ValueError ("floor tag must be int: {}" .format (floor ))
255+
256+ self .__floor = floor
257+
258+ return self .__floor
259+
260+ def radius (self , radius = None ):
261+ '''
262+ Get or set the georss:radius of the entry
263+
264+ :param radius: The GeoRSS radius (e.g. 100.3)
265+ :type radius: numbers.Number
266+ :return: the current georss:radius
267+ '''
268+ if radius is not None :
269+ if not isinstance (radius , numbers .Number ):
270+ raise ValueError (
271+ "radius tag must be numeric: {}" .format (radius )
272+ )
273+
274+ self .__radius = radius
275+
276+ return self .__radius
277+
278+ def geom_from_geo_interface (self , geom ):
279+ '''
280+ Generate a georss geometry from some Python object with a
281+ ``__geo_interface__`` property (see the `geo_interface specification by
282+ Sean Gillies`_geointerface )
283+
284+ Note only a subset of GeoJSON (see `geojson.org`_geojson ) can be
285+ easily converted to GeoRSS:
286+
287+ - Point
288+ - LineString
289+ - Polygon (if there are holes / donuts in the polygons a warning will
290+ be generaated
291+
292+ Other GeoJson types will raise a ``ValueError``.
293+
294+ .. note:: The geometry is assumed to be x, y as longitude, latitude in
295+ the WGS84 projection.
296+
297+ .. _geointerface: https://gist.github.com/sgillies/2217756
298+ .. _geojson: https://geojson.org/
299+
300+ :param geom: Geometry object with a __geo_interface__ property
301+ :return: the formatted GeoRSS geometry
302+ '''
303+ geojson = geom .__geo_interface__
304+
305+ if geojson ['type' ] not in ('Point' , 'LineString' , 'Polygon' ):
306+ raise GeoRSSGeometryError (geom )
307+
308+ if geojson ['type' ] == 'Point' :
309+
310+ coords = '{:f} {:f}' .format (
311+ geojson ['coordinates' ][1 ], # latitude is y
312+ geojson ['coordinates' ][0 ]
313+ )
314+ return self .point (coords )
315+
316+ elif geojson ['type' ] == 'LineString' :
317+
318+ coords = ' ' .join (
319+ '{:f} {:f}' .format (vertex [1 ], vertex [0 ])
320+ for vertex in
321+ geojson ['coordinates' ]
322+ )
323+ return self .line (coords )
324+
325+ elif geojson ['type' ] == 'Polygon' :
326+
327+ if len (geojson ['coordinates' ]) > 1 :
328+ warnings .warn (GeoRSSPolygonInteriorWarning (geom ))
329+
330+ coords = ' ' .join (
331+ '{:f} {:f}' .format (vertex [1 ], vertex [0 ])
332+ for vertex in
333+ geojson ['coordinates' ][0 ]
334+ )
335+ return self .polygon (coords )
0 commit comments