Permalink
Browse files

Move KML, GeoJSON and WKT generation into a method of Area

It is useful to be able to export an area in these different
formats in situations other than a view, e.g. from a django-admin
command.  This refactoring moves the code that generates KML,
GeoJSON and WKT into a method of Area, which doesn't depend on
having a request object.

This refactoring also makes it easier to detect when your
simplify_tolerance is high enough that all the polygons disappear,
in which case a SimplifiedAway exception is raised.
  • Loading branch information...
1 parent 32d445c commit af74dc228bba0b015328a4294268a30c612f514c @mhl mhl committed Dec 14, 2012
Showing with 93 additions and 40 deletions.
  1. +83 −0 mapit/models.py
  2. +10 −40 mapit/views/areas.py
View
83 mapit/models.py
@@ -4,6 +4,7 @@
from django.contrib.gis.db import models
from django.conf import settings
from django.db import connection
+from django.utils.html import escape
from mapit.managers import Manager, GeoManager
from mapit import countries
@@ -197,6 +198,9 @@ def get_or_create_with_code(self, country=None, type=None, code_type='', code=''
area.save()
return area
+class SimplifiedAway(Exception):
+ pass
+
class Area(models.Model):
name = models.CharField(max_length=100, editable=False, blank=True) # Automatically set from name children
parent_area = models.ForeignKey('self', related_name='children', null=True, blank=True)
@@ -253,6 +257,85 @@ def css_indent_class(self):
else:
return ""
+ def export(self,
+ srid,
+ export_format,
+ simplify_tolerance=0,
+ line_colour="70ff0000",
+ fill_colour="3dff5500",
+ kml_type="full"):
+ """Generate a representation of the area in KML, GeoJSON or WKT
+
+ This returns a tuple of (data, content_type), which are
+ strings representing the data itself and its MIME type. If
+ there are no polygons associated with this area (None, None)
+ is returned. 'export_format' may be one of 'kml', 'wkt,
+ 'json' and 'geojson', the last two being synonymous. The
+ 'srid' parameter specifies the coordinate system that the
+ polygons should be transformed into before being exported, if
+ it is different from this MapIt. simplify_tolerance, if
+ non-zero, is passed to
+ django.contrib.gis.geos.GEOSGeometry.simplify for simplifying
+ the polygon boundary before export. The line_colour and
+ fill_colour parameters are only used if the export type is KML
+ and kml_type is 'full'. The 'kml_type' parameter may be
+ either 'full' (in which case a complete, valid KML file is
+ returned) or 'polygon' (in which case just the <Polygon>
+ element is returned).
+
+ If the simplify_tolerance provided is large enough that all
+ the polygons completely disappear under simplification,
+ SimplifiedAway exception is raised.
+ """
+ all_areas = self.polygons.all()
+ if len(all_areas) > 1:
+ all_areas = all_areas.collect()
+ elif len(all_areas) == 1:
+ all_areas = all_areas[0].polygon
+ else:
+ return (None, None)
+
+ if srid != settings.MAPIT_AREA_SRID:
+ all_areas.transform(srid)
+
+ num_points_before_simplification = all_areas.num_points
+ if simplify_tolerance:
+ all_areas = all_areas.simplify(simplify_tolerance)
+ if all_areas.num_points == 0 and num_points_before_simplification > 0:
+ raise SimplifiedAway, "Simplifying %s with tolerance %f left no boundary at all" % (self, simplify_tolerance)
+
+ if export_format=='kml':
+ if kml_type == "polygon":
+ out = all_areas.kml
+ elif kml_type == "full":
+ out = '''<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://www.opengis.net/kml/2.2">
+ <Style id="ourPolygonStyle">
+ <LineStyle>
+ <color>%s</color>
+ <width>2</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>%s</color>
+ </PolyStyle>
+ </Style>
+ <Placemark>
+ <styleUrl>#ourPolygonStyle</styleUrl>
+ <name>%s</name>
+ %s
+ </Placemark>
+</kml>''' % (line_colour, fill_colour, escape(self.name), all_areas.kml)
+ else:
+ raise Exception, "Unknown kml_type: '%s'" % (kml_type,)
+ content_type = 'application/vnd.google-earth.kml+xml'
+ elif export_format in ('json', 'geojson'):
+ out = all_areas.json
+ content_type = 'application/json'
+ elif export_format=='wkt':
+ out = all_areas.wkt
+ content_type = 'text/plain'
+ return (out, content_type)
+
class Geometry(models.Model):
area = models.ForeignKey(Area, related_name='polygons')
polygon = models.PolygonField(srid=settings.MAPIT_AREA_SRID)
View
50 mapit/views/areas.py
@@ -12,11 +12,10 @@
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import resolve, reverse
from django.db.models import Q
-from django.utils.html import escape
from django.conf import settings
from django.shortcuts import redirect
-from mapit.models import Area, Generation, Geometry, Code, Name
+from mapit.models import Area, Generation, Geometry, Code, Name, SimplifiedAway
from mapit.shortcuts import output_json, output_html, render, get_object_or_404, set_timeout
from mapit.middleware import ViewException
from mapit.ratelimitcache import ratelimit
@@ -82,49 +81,20 @@ def area_polygon(request, srid='', area_id='', format='kml'):
srid = int(srid)
area = get_object_or_404(Area, id=area_id)
- all_areas = area.polygons.all()
- if len(all_areas) > 1:
- all_areas = all_areas.collect()
- elif len(all_areas) == 1:
- all_areas = all_areas[0].polygon
- else:
- return output_json({ 'error': 'No polygons found' }, code=404)
- if srid != settings.MAPIT_AREA_SRID:
- all_areas.transform(srid)
try:
simplify_tolerance = float(request.GET.get('simplify_tolerance', 0))
except ValueError:
raise ViewException(format, 'Badly specified tolerance', 400)
- if simplify_tolerance:
- all_areas = all_areas.simplify(simplify_tolerance)
-
- if format=='kml':
- out = '''<?xml version="1.0" encoding="UTF-8"?>
-<kml xmlns="http://www.opengis.net/kml/2.2">
- <Style id="transBluePoly">
- <LineStyle>
- <color>70ff0000</color>
- <width>2</width>
- </LineStyle>
- <PolyStyle>
- <color>3dff5500</color>
- </PolyStyle>
- </Style>
- <Placemark>
- <styleUrl>#transBluePoly</styleUrl>
- <name>%s</name>
- %s
- </Placemark>
-</kml>''' % (escape(area.name), all_areas.kml)
- content_type = 'application/vnd.google-earth.kml+xml'
- elif format in ('json', 'geojson'):
- out = all_areas.json
- content_type = 'application/json'
- elif format=='wkt':
- out = all_areas.wkt
- content_type = 'text/plain'
- return HttpResponse(out, content_type='%s; charset=utf-8' % content_type)
+
+ try:
+ output, content_type = area.export(srid, format, simplify_tolerance=simplify_tolerance)
+ if output is None:
+ return output_json({'error': 'No polygons found'}, code=404)
+ except SimplifiedAway:
+ return output_json({'error': 'Simplifying removed all the polygons'}, code=404)
+
+ return HttpResponse(output, content_type='%s; charset=utf-8' % content_type)
@ratelimit(minutes=3, requests=100)
def area_children(request, area_id, format='json'):

0 comments on commit af74dc2

Please sign in to comment.