Index: django/contrib/gis/feeds.py
===================================================================
--- django/contrib/gis/feeds.py	(revision 0)
+++ django/contrib/gis/feeds.py	(revision 0)
@@ -0,0 +1,138 @@
+from django.contrib.syndication.feeds import Feed as BaseFeed, FeedDoesNotExist
+from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
+
+class GeoFeedMixin(object):
+    w3c_geo = False
+
+    def georss_coords(self, coords):
+        """
+        In GeoRSS coordinate pairs are ordered by lat/lon and separated by
+        a single white space.  Given a tuple of coordinates, this will return
+        a unicode GeoRSS representation.
+        """
+        return u' '.join([u'%f %f' % (coord[1], coord[0]) for coord in coords])
+
+    def add_georss_point(self, handler, coords):
+        """
+        Adds a GeoRSS point with the given coords using the given handler.
+        Handles the differences between simple GeoRSS and the more pouplar
+        W3C Geo specification.
+        """
+        if self.w3c_geo:
+            lon, lat = coords[:2]
+            handler.addQuickElement(u'geo:lat', u'%f' % lat)
+            handler.addQuickElement(u'geo:lon', u'%f' % lon)
+        else:
+            handler.addQuickElement(u'georss:point', self.georss_coords((coords,)))
+
+    def add_georss_element(self, handler, item):
+        """
+        This routine adds a GeoRSS XML element using the given item and handler.
+        """
+        # Getting the Geometry object.
+        geom = item.get('geometry', None)
+        if not geom is None:
+            if isinstance(geom, (list, tuple)):
+                # Special case if a tuple/list was passed in.  The tuple may be
+                # a point or a box
+                box_coords = None
+                if isinstance(geom[0], (list, tuple)):
+                    # Box: ( (X0, Y0), (X1, Y1) )
+                    if len(geom) == 2:
+                        box_coords = geom
+                    else:
+                        raise ValueError('Only should be two sets of coordinates.')
+                else:
+                    if len(geom) == 2:
+                        # Point: (X, Y)
+                        self.add_georss_point(handler, geom)
+                    elif len(geom) == 4:
+                        # Box: (X0, Y0, X1, Y1)
+                        box_coords = (geom[:2], geom[2:])
+                    else:
+                        raise ValueError('Only should be 2 or 4 numeric elements.')
+                # If a GeoRSS box was given via tuple.
+                if not box_coords is None:
+                    if self.w3c_geo: raise ValueError('Cannot use simple GeoRSS box in W3C Geo feeds.')
+                    handler.addQuickElement(u'georss:box', self.georss_coords(box_coords))
+            else:
+                # Getting the lower-case geometry type.
+                gtype = str(geom.geom_type).lower()
+             
+                if gtype == 'point':
+                    self.add_georss_point(handler, geom.coords) 
+                else:
+                    if self.w3c_geo: raise TypeError('W3C Geo only supports Point geometries.')
+                    # For formatting consistent w/the GeoRSS simple standard:
+                    # http://georss.org/1.0#simple
+                    if gtype in ('linestring', 'linearring'):
+                        handler.addQuickElement(u'georss:line', self.georss_coords(geom.coords))
+                    elif gtype in ('polygon',):
+                        # Only support the exterior ring.
+                        handler.addQuickElement(u'georss:polygon', self.georss_coords(geom[0].coords))
+                    else:
+                        raise TypeError('Geometry type "%s" not supported.' % geom.geom_type)
+
+### SyndicationFeed subclasses ###
+class W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin):
+    w3c_geo = True
+
+    def rss_attributes(self):
+        attrs = super(W3CGeoFeed, self).rss_attributes()
+        attrs[u'xmlns:geo'] = u'http://www.w3.org/2003/01/geo/wgs84_pos#'
+        return attrs
+
+    def add_item_elements(self, handler, item):
+        super(W3CGeoFeed, self).add_item_elements(handler, item)
+        self.add_georss_element(handler, item)
+
+    def add_root_elements(self, handler):
+        super(W3CGeoFeed, self).add_root_elements(handler)
+        self.add_georss_element(handler, self.feed)
+
+class GeoRSSFeed(Rss201rev2Feed, GeoFeedMixin):
+    def rss_attributes(self):
+        attrs = super(GeoRSSFeed, self).rss_attributes()
+        attrs[u'xmlns:georss'] = u'http://www.georss.org/georss'
+        return attrs
+
+    def add_item_elements(self, handler, item):
+        super(GeoRSSFeed, self).add_item_elements(handler, item)
+        self.add_georss_element(handler, item)
+
+    def add_root_elements(self, handler):
+        super(GeoRSSFeed, self).add_root_elements(handler)
+        self.add_georss_element(handler, self.feed)
+
+class GeoAtom1Feed(Atom1Feed, GeoFeedMixin):
+    def root_attributes(self):
+        attrs = super(GeoAtom1Feed, self).root_attributes()
+        attrs[u'xmlns:georss'] = u'http://www.georss.org/georss'
+        return attrs
+
+    def add_item_elements(self, handler, item):
+        super(GeoAtom1Feed, self).add_item_elements(handler, item)
+        self.add_georss_element(handler, item)
+
+    def add_root_elements(self, handler):
+        super(GeoAtom1Feed, self).add_root_elements(handler)
+        self.add_georss_element(handler, self.feed)
+        
+class Feed(BaseFeed):
+    """
+    This is a subclass of the `Feed` from `django.contrib.syndication`.
+    This allows users to define a `geometry(obj)` and/or `item_geometry(item)`
+    methods on their own subclasses so that geo-referenced information may
+    placed in the feed.
+    """
+    feed_type = GeoRSSFeed
+
+    def get_feed_kwargs(self, obj):
+        kwargs = super(Feed, self).get_feed_kwargs(obj)
+        kwargs['geometry'] = self.__get_dynamic_attr('geometry', obj)
+        return kwargs
+
+    def get_item_kwargs(self, item):
+        kwargs = super(Feed, self).get_item_kwargs(item)
+        kwargs['geometry'] = self.__get_dynamic_attr('item_geometry', item)
+        return kwargs
Index: django/contrib/syndication/feeds.py
===================================================================
--- django/contrib/syndication/feeds.py	(revision 8352)
+++ django/contrib/syndication/feeds.py	(working copy)
@@ -62,6 +62,81 @@
     def get_object(self, bits):
         return None
 
+    def get_feed_kwargs(self, obj):
+        """
+        Returns the keyword arguments to instatiate the `SyndicationFeed`
+        class specified by the `feed_type` attribute.
+        """
+        return {
+            'title' : self.__get_dynamic_attr('title', obj),
+            'subtitle' : self.__get_dynamic_attr('subtitle', obj),
+            'link' : add_domain(self.current_site.domain,
+                                self.__get_dynamic_attr('link', obj)),
+            'description' : self.__get_dynamic_attr('description', obj),
+            'language' : settings.LANGUAGE_CODE.decode(),
+            'feed_url' : add_domain(self.current_site.domain,
+                                  self.__get_dynamic_attr('feed_url', obj)),
+            'author_name' : self.__get_dynamic_attr('author_name', obj),
+            'author_link' : self.__get_dynamic_attr('author_link', obj),
+            'author_email' : self.__get_dynamic_attr('author_email', obj),
+            'categories' : self.__get_dynamic_attr('categories', obj),
+            'feed_copyright' : self.__get_dynamic_attr('feed_copyright', obj),
+            'feed_guid' : self.__get_dynamic_attr('feed_guid', obj),
+            'ttl' : self.__get_dynamic_attr('ttl', obj),
+            }
+
+    def get_item_kwargs(self, item):
+        """
+        Returns the keyword arguments to pass to the `add_item` method of the
+        `SyndicationFeed` instance (specified by the `feed_type` attribute).
+        """
+        link = add_domain(self.current_site.domain, self.__get_dynamic_attr('item_link', item))
+        enc = None
+        enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
+        if enc_url:
+            enc = feedgenerator.Enclosure(
+                url = smart_unicode(enc_url),
+                length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
+                mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
+                )
+        author_name = self.__get_dynamic_attr('item_author_name', item)
+        if author_name is not None:
+            author_email = self.__get_dynamic_attr('item_author_email', item)
+            author_link = self.__get_dynamic_attr('item_author_link', item)
+        else:
+            author_email = author_link = None
+
+        pubdate = self.__get_dynamic_attr('item_pubdate', item)
+        if pubdate:
+            now = datetime.now()
+            utcnow = datetime.utcnow()
+
+            # Must always subtract smaller time from larger time here.
+            if utcnow > now:
+                sign = -1
+                tzDifference = (utcnow - now)
+            else:
+                sign = 1
+                tzDifference = (now - utcnow)
+
+            # Round the timezone offset to the nearest half hour.
+            tzOffsetMinutes = sign * ((tzDifference.seconds / 60 + 15) / 30) * 30
+            tzOffset = timedelta(minutes=tzOffsetMinutes)
+            pubdate = pubdate.replace(tzinfo=FixedOffset(tzOffset))
+
+        return {'title' : self.title_tmp.render(RequestContext(self.request, {'obj': item, 'site': self.current_site})),
+                'link' : link,
+                'description' : self.description_tmp.render(RequestContext(self.request, {'obj': item, 'site': self.current_site})),
+                'unique_id' : self.__get_dynamic_attr('item_guid', item, link),
+                'enclosure' : enc,
+                'pubdate' : pubdate,
+                'author_name' : author_name,
+                'author_email' : author_email,
+                'author_link' : author_link,
+                'categories' : self.__get_dynamic_attr('item_categories', item),
+                'item_copyright' : self.__get_dynamic_attr('item_copyright', item),
+                }
+
     def get_feed(self, url=None):
         """
         Returns a feedgenerator.DefaultFeed object, fully populated, for
@@ -77,86 +152,26 @@
         except ObjectDoesNotExist:
             raise FeedDoesNotExist
 
+        # Getting the current site.
         if Site._meta.installed:
-            current_site = Site.objects.get_current()
+            self.current_site = Site.objects.get_current()
         else:
-            current_site = RequestSite(self.request)
-        
-        link = self.__get_dynamic_attr('link', obj)
-        link = add_domain(current_site.domain, link)
+            self.current_site = RequestSite(self.request)
 
-        feed = self.feed_type(
-            title = self.__get_dynamic_attr('title', obj),
-            subtitle = self.__get_dynamic_attr('subtitle', obj),
-            link = link,
-            description = self.__get_dynamic_attr('description', obj),
-            language = settings.LANGUAGE_CODE.decode(),
-            feed_url = add_domain(current_site.domain,
-                                  self.__get_dynamic_attr('feed_url', obj)),
-            author_name = self.__get_dynamic_attr('author_name', obj),
-            author_link = self.__get_dynamic_attr('author_link', obj),
-            author_email = self.__get_dynamic_attr('author_email', obj),
-            categories = self.__get_dynamic_attr('categories', obj),
-            feed_copyright = self.__get_dynamic_attr('feed_copyright', obj),
-            feed_guid = self.__get_dynamic_attr('feed_guid', obj),
-            ttl = self.__get_dynamic_attr('ttl', obj),
-        )
+        # Initializing the feed.
+        feed = self.feed_type(**self.get_feed_kwargs(obj))
 
+        # Getting instances of the title and description templates.
         try:
-            title_tmp = loader.get_template(self.title_template_name)
+            self.title_tmp = loader.get_template(self.title_template_name)
         except TemplateDoesNotExist:
-            title_tmp = Template('{{ obj }}')
+            self.title_tmp = Template('{{ obj }}')
         try:
-            description_tmp = loader.get_template(self.description_template_name)
+            self.description_tmp = loader.get_template(self.description_template_name)
         except TemplateDoesNotExist:
-            description_tmp = Template('{{ obj }}')
+            self.description_tmp = Template('{{ obj }}')
 
+        # Creating the entry for each item.
         for item in self.__get_dynamic_attr('items', obj):
-            link = add_domain(current_site.domain, self.__get_dynamic_attr('item_link', item))
-            enc = None
-            enc_url = self.__get_dynamic_attr('item_enclosure_url', item)
-            if enc_url:
-                enc = feedgenerator.Enclosure(
-                    url = smart_unicode(enc_url),
-                    length = smart_unicode(self.__get_dynamic_attr('item_enclosure_length', item)),
-                    mime_type = smart_unicode(self.__get_dynamic_attr('item_enclosure_mime_type', item))
-                )
-            author_name = self.__get_dynamic_attr('item_author_name', item)
-            if author_name is not None:
-                author_email = self.__get_dynamic_attr('item_author_email', item)
-                author_link = self.__get_dynamic_attr('item_author_link', item)
-            else:
-                author_email = author_link = None
-
-            pubdate = self.__get_dynamic_attr('item_pubdate', item)
-            if pubdate:
-                now = datetime.now()
-                utcnow = datetime.utcnow()
-
-                # Must always subtract smaller time from larger time here.
-                if utcnow > now:
-                    sign = -1
-                    tzDifference = (utcnow - now)
-                else:
-                    sign = 1
-                    tzDifference = (now - utcnow)
-
-                # Round the timezone offset to the nearest half hour.
-                tzOffsetMinutes = sign * ((tzDifference.seconds / 60 + 15) / 30) * 30
-                tzOffset = timedelta(minutes=tzOffsetMinutes)
-                pubdate = pubdate.replace(tzinfo=FixedOffset(tzOffset))
-
-            feed.add_item(
-                title = title_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
-                link = link,
-                description = description_tmp.render(RequestContext(self.request, {'obj': item, 'site': current_site})),
-                unique_id = self.__get_dynamic_attr('item_guid', item, link),
-                enclosure = enc,
-                pubdate = pubdate,
-                author_name = author_name,
-                author_email = author_email,
-                author_link = author_link,
-                categories = self.__get_dynamic_attr('item_categories', item),
-                item_copyright = self.__get_dynamic_attr('item_copyright', item),
-            )
+            feed.add_item(**self.get_item_kwargs(item))
         return feed
