Add a geom_from_geo_interface method for GeoRSS Entry

A standard way for different geometry libraries in Python to be
interoperable is a `__geo_interface__` for the geometry (see the
specification: https://gist.github.com/sgillies/2217756). This
includes the shapely library, geometries from QGIS, and geometries in
Esri's arcpy libraries for ArcGIS desktop and ArcGIS pro.

To make it easier to generate a georss entry a simple method which
does the conversion (of the supported geometries only) and sets the
appropriate geometry type.

This includes a custom error for the geometry being incompatible and a
custom warning for a polygon with interior holes. This is done to
store the geometries on the exception / warning if required for
debugging.
This commit is contained in:
Henry Walshaw 2019-07-08 13:38:39 +10:00
parent 642862bb2b
commit 8d413f576f

View file

@ -10,11 +10,48 @@
:license: FreeBSD and LGPL, see license.* for more details. :license: FreeBSD and LGPL, see license.* for more details.
''' '''
import numbers import numbers
import warnings
from lxml import etree from lxml import etree
from feedgen.ext.base import BaseEntryExtension from feedgen.ext.base import BaseEntryExtension
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):
return "Geometry of type '{}' not in Point, Linestring or Polygon".format(
self.geom.__geo_interface__['type']
)
class GeoEntryExtension(BaseEntryExtension): class GeoEntryExtension(BaseEntryExtension):
'''FeedEntry extension for Simple GeoRSS. '''FeedEntry extension for Simple GeoRSS.
''' '''
@ -224,3 +261,62 @@ class GeoEntryExtension(BaseEntryExtension):
self.__radius = radius self.__radius = radius
return self.__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
generaated
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)