| 1 | from django.contrib.syndication.feeds import Feed as BaseFeed, FeedDoesNotExist |
| 2 | from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed |
| 3 | |
| 4 | class GeoFeedMixin(object): |
| 5 | w3c_geo = False |
| 6 | |
| 7 | def georss_coords(self, coords): |
| 8 | """ |
| 9 | In GeoRSS coordinate pairs are ordered by lat/lon and separated by |
| 10 | a single white space. Given a tuple of coordinates, this will return |
| 11 | a unicode GeoRSS representation. |
| 12 | """ |
| 13 | return u' '.join([u'%f %f' % (coord[1], coord[0]) for coord in coords]) |
| 14 | |
| 15 | def add_georss_point(self, handler, coords): |
| 16 | """ |
| 17 | Adds a GeoRSS point with the given coords using the given handler. |
| 18 | Handles the differences between simple GeoRSS and the more pouplar |
| 19 | W3C Geo specification. |
| 20 | """ |
| 21 | if self.w3c_geo: |
| 22 | lon, lat = coords[:2] |
| 23 | handler.addQuickElement(u'geo:lat', u'%f' % lat) |
| 24 | handler.addQuickElement(u'geo:lon', u'%f' % lon) |
| 25 | else: |
| 26 | handler.addQuickElement(u'georss:point', self.georss_coords((coords,))) |
| 27 | |
| 28 | def add_georss_element(self, handler, item): |
| 29 | """ |
| 30 | This routine adds a GeoRSS XML element using the given item and handler. |
| 31 | """ |
| 32 | # Getting the Geometry object. |
| 33 | geom = item.get('geometry', None) |
| 34 | if not geom is None: |
| 35 | if isinstance(geom, (list, tuple)): |
| 36 | # Special case if a tuple/list was passed in. The tuple may be |
| 37 | # a point or a box |
| 38 | box_coords = None |
| 39 | if isinstance(geom[0], (list, tuple)): |
| 40 | # Box: ( (X0, Y0), (X1, Y1) ) |
| 41 | if len(geom) == 2: |
| 42 | box_coords = geom |
| 43 | else: |
| 44 | raise ValueError('Only should be two sets of coordinates.') |
| 45 | else: |
| 46 | if len(geom) == 2: |
| 47 | # Point: (X, Y) |
| 48 | self.add_georss_point(handler, geom) |
| 49 | elif len(geom) == 4: |
| 50 | # Box: (X0, Y0, X1, Y1) |
| 51 | box_coords = (geom[:2], geom[2:]) |
| 52 | else: |
| 53 | raise ValueError('Only should be 2 or 4 numeric elements.') |
| 54 | # If a GeoRSS box was given via tuple. |
| 55 | if not box_coords is None: |
| 56 | if self.w3c_geo: raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.') |
| 57 | handler.addQuickElement(u'georss:box', self.georss_coords(box_coords)) |
| 58 | else: |
| 59 | # Getting the lower-case geometry type. |
| 60 | gtype = str(geom.geom_type).lower() |
| 61 | |
| 62 | if gtype == 'point': |
| 63 | self.add_georss_point(handler, geom.coords) |
| 64 | else: |
| 65 | if self.w3c_geo: raise TypeError('W3C Geo only supports Point geometries.') |
| 66 | # For formatting consistent w/the GeoRSS simple standard: |
| 67 | # http://georss.org/1.0#simple |
| 68 | if gtype in ('linestring', 'linearring'): |
| 69 | handler.addQuickElement(u'georss:line', self.georss_coords(geom.coords)) |
| 70 | elif gtype in ('polygon',): |
| 71 | # Only support the exterior ring. |
| 72 | handler.addQuickElement(u'georss:polygon', self.georss_coords(geom[0].coords)) |
| 73 | else: |
| 74 | raise TypeError('Geometry type "%s" not supported.' % geom.geom_type) |
| 75 | |
| 76 | ### SyndicationFeed subclasses ### |
| 77 | class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin): |
| 78 | w3c_geo = True |
| 79 | |
| 80 | def rss_attributes(self): |
| 81 | attrs = super(W3CGeoFeed, self).rss_attributes() |
| 82 | attrs[u'xmlns:geo'] = u'http://www.w3.org/2003/01/geo/wgs84_pos#' |
| 83 | return attrs |
| 84 | |
| 85 | def add_item_elements(self, handler, item): |
| 86 | super(W3CGeoFeed, self).add_item_elements(handler, item) |
| 87 | self.add_georss_element(handler, item) |
| 88 | |
| 89 | def add_root_elements(self, handler): |
| 90 | super(W3CGeoFeed, self).add_root_elements(handler) |
| 91 | self.add_georss_element(handler, self.feed) |
| 92 | |
| 93 | class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin): |
| 94 | def rss_attributes(self): |
| 95 | attrs = super(GeoRSSFeed, self).rss_attributes() |
| 96 | attrs[u'xmlns:georss'] = u'http://www.georss.org/georss' |
| 97 | return attrs |
| 98 | |
| 99 | def add_item_elements(self, handler, item): |
| 100 | super(GeoRSSFeed, self).add_item_elements(handler, item) |
| 101 | self.add_georss_element(handler, item) |
| 102 | |
| 103 | def add_root_elements(self, handler): |
| 104 | super(GeoRSSFeed, self).add_root_elements(handler) |
| 105 | self.add_georss_element(handler, self.feed) |
| 106 | |
| 107 | class GeoAtom1Feed(Atom1Feed, GeoFeedMixin): |
| 108 | def root_attributes(self): |
| 109 | attrs = super(GeoAtom1Feed, self).root_attributes() |
| 110 | attrs[u'xmlns:georss'] = u'http://www.georss.org/georss' |
| 111 | return attrs |
| 112 | |
| 113 | def add_item_elements(self, handler, item): |
| 114 | super(GeoAtom1Feed, self).add_item_elements(handler, item) |
| 115 | self.add_georss_element(handler, item) |
| 116 | |
| 117 | def add_root_elements(self, handler): |
| 118 | super(GeoAtom1Feed, self).add_root_elements(handler) |
| 119 | self.add_georss_element(handler, self.feed) |
| 120 | |
| 121 | class Feed(BaseFeed): |
| 122 | """ |
| 123 | This is a subclass of the `Feed` from `django.contrib.syndication`. |
| 124 | This allows users to define a `geometry(obj)` and/or `item_geometry(item)` |
| 125 | methods on their own subclasses so that geo-referenced information may |
| 126 | placed in the feed. |
| 127 | """ |
| 128 | feed_type = GeoRSSFeed |
| 129 | |
| 130 | def feed_extra_kwargs(self, obj): |
| 131 | return {'geometry' : self.__get_dynamic_attr('geometry', obj)} |
| 132 | |
| 133 | def item_extra_kwargs(self, item): |
| 134 | return {'geometry' : self.__get_dynamic_attr('item_geometry', item)} |