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