├── geotagging ├── admin │ ├── __init__.py │ └── options.py ├── fixes │ ├── __init__.py │ └── gis │ │ ├── __init__.py │ │ └── admin │ │ ├── __init__.py │ │ ├── widgets.py │ │ └── options.py ├── templatetags │ ├── __init__.py │ └── geotagging_tags.py ├── tests │ ├── __init__.py │ ├── model_tests.py │ └── tag_tests.py ├── templates │ └── geotagging │ │ ├── admin │ │ ├── osm_multiwidget.html │ │ ├── openlayer_multiwidget.html │ │ ├── osm_multiwidget.js │ │ ├── edit_inline │ │ │ └── geotagging_inline.html │ │ └── openlayer_multiwidget.js │ │ ├── geotags.kml │ │ ├── view_kml_feed.html │ │ ├── view_georss_feed.html │ │ ├── view_neighborhood_monitoring.html │ │ └── view_kml_feeds.html ├── managers.py ├── __init__.py ├── urls.py ├── models.py └── views.py ├── .gitignore ├── AUTHORS ├── setup.py ├── README.rst └── LICENSE /geotagging/admin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /geotagging/fixes/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /geotagging/fixes/gis/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /geotagging/fixes/gis/admin/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /geotagging/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | local_settings.py 4 | *.egg-info 5 | -------------------------------------------------------------------------------- /geotagging/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from tag_tests import * 2 | from model_tests import * -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Yann Malet 2 | Peter Baumgartner 4 | {{ place }} 5 | {{ place.content_type.name }} 6 | {{ place.object.get_absolute_url }} 7 | {{ place.kml|safe }} 8 | {% endfor %} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='django-geotagging', 5 | version='0.0.2', 6 | description='This is a geotagging application. It can be used to localised your content', 7 | author='Yann Malet', 8 | author_email='yann.malet@gmail.com', 9 | url='https://code.launchpad.net/django-geotagging', 10 | packages=find_packages(), 11 | classifiers=[ 12 | 'Development Status :: 3 - Alpha', 13 | 'Environment :: Web Environment', 14 | 'Intended Audience :: Developers', 15 | 'License :: OSI Approved :: BSD License', 16 | 'Operating System :: OS Independent', 17 | 'Programming Language :: Python', 18 | 'Framework :: Django', 19 | ], 20 | include_package_data=True, 21 | zip_safe=False, 22 | ) 23 | -------------------------------------------------------------------------------- /geotagging/managers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom managers for Django models registered with the geotagging 3 | application. 4 | """ 5 | from django.db import models 6 | 7 | from geotagging.models import Geotag 8 | 9 | class ModelGeotagManager(models.Manager): 10 | """ 11 | A manager for retrieving tags for a particular model. 12 | """ 13 | # TODO: when does this actually get called and what should be here? 14 | pass 15 | 16 | 17 | class GeotagDescriptor(object): 18 | """ 19 | A descriptor which provides access to a ``ModelGeotagManager`` for 20 | model classes and simple retrieval, updating and deletion of tags 21 | for model instances. 22 | """ 23 | def __get__(self, instance, owner): 24 | if not instance: 25 | tag_manager = ModelGeotagManager() 26 | tag_manager.model = owner 27 | return tag_manager 28 | else: 29 | return Geotag.objects.get_for_object(instance) 30 | 31 | def __set__(self, instance, value): 32 | Geotag.objects.update_geotag(instance, value) 33 | 34 | def __delete__(self, instance): 35 | Geotag.objects.update_geotag(instance, None) 36 | -------------------------------------------------------------------------------- /geotagging/__init__.py: -------------------------------------------------------------------------------- 1 | """Handles registration of models in the geotagging registry""" 2 | 3 | 4 | class AlreadyRegistered(Exception): 5 | """ 6 | An attempt was made to register a model more than once. 7 | """ 8 | pass 9 | 10 | 11 | registry = [] 12 | 13 | 14 | def register(model, geotag_descriptor_attr='geotag'): 15 | """ 16 | Sets the given model class up for working with geotagging. 17 | """ 18 | 19 | from geotagging.managers import GeotagDescriptor 20 | 21 | if model in registry: 22 | raise AlreadyRegistered("The model '%s' has already been " 23 | "registered." % model._meta.object_name) 24 | if hasattr(model, geotag_descriptor_attr): 25 | raise AttributeError("'%s' already has an attribute '%s'. You must " 26 | "provide a custom geotag_descriptor_attr to register." % ( 27 | model._meta.object_name, 28 | geotag_descriptor_attr, 29 | ) 30 | ) 31 | 32 | # Add tag descriptor 33 | setattr(model, geotag_descriptor_attr, GeotagDescriptor()) 34 | 35 | # Finally register in registry 36 | registry.append(model) 37 | 38 | from django.contrib.auth.models import User 39 | register(User) -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Django Geotagging 2 | ================= 3 | 4 | This application is designed to store and query geographic information on any 5 | model in your Django project. It is still under development, use at your own 6 | risk. 7 | 8 | Installation 9 | ------------ 10 | 11 | Via ``pip``:: 12 | 13 | pip install -e git+git://github.com:lincolnloop/django-geotagging.git#egg=django-geotagging 14 | 15 | The old fashioned way: 16 | 17 | 1. Download repo http://github.com/lincolnloop/django-geotagging/tarball/master 18 | 2. Unpack and ``cd django_geotagging`` 19 | 3. ``python setup.py install`` 20 | 21 | 22 | Configuration 23 | ------------- 24 | 25 | Add ``'geotagging'`` to your ``INSTALLED_APPS``. 26 | 27 | Optional 28 | ^^^^^^^^ 29 | 30 | Register your models with geotagging to add a ``geotag`` attribute to the model 31 | instances:: 32 | 33 | import geotagging 34 | 35 | class MyModel(models.Model): 36 | ... 37 | geotagging.register(MyModel) 38 | 39 | Add the geotag widget to your admin:: 40 | 41 | from geotagging.admin.options import GeotagsInline 42 | 43 | class MyModelAdmin(admin.ModelAdmin): 44 | ... 45 | inlines = [GeotagsInline] 46 | 47 | .. image:: http://img.skitch.com/20100129-6p6952tsg2y9s8qwanu62sgms.png 48 | 49 | Usage 50 | ----- 51 | 52 | See how it is used in the ``tests`` directory for now. 53 | 54 | 55 | To Do 56 | ----- 57 | 58 | * Lots of clean-up and further testing 59 | * Plug in some views 60 | -------------------------------------------------------------------------------- /geotagging/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | from geotagging.views import kml_feed, kml_feed_map, kml_feeds_map 4 | from geotagging.views import neighborhood_monitoring, kml_neighborhood_feed 5 | 6 | 7 | urlpatterns = patterns('', 8 | 9 | # KML feeds 10 | url(r'^kml_feed/(?Ppoint|line|polygon)/$',kml_feed, 11 | name="geotagging-kml_feed"), 12 | url(r'^kml_feed/(?Ppoint|line|polygon)/(?P[a-z ]+)/$', 13 | kml_feed, 14 | name="geotagging-kml_feed_per_contenttype"), 15 | 16 | # KML Feeds visualiser 17 | url(r'^kml_feeds_map/all/$', kml_feeds_map, 18 | name="geotagging-kml_feeds_map"), 19 | url(r'^kml_feeds_map/all/(?P[a-z]+)/$', kml_feeds_map, 20 | name="geotagging-kml_feeds_map_per_contenttype"), 21 | 22 | url(r'^kml_feed_map/(?P[a-z]+)/$', kml_feed_map, 23 | name="geotagging-kml_feed_map"), 24 | url(r'^kml_feed_map/(?P[a-z]+)/(?P[a-z ]+)/$', kml_feed_map, 25 | name="geotagging-kml_feed_map_per_contenttype"), 26 | 27 | # neighborhood monitoring 28 | url(r'^neighborhood_monitoring/(?P\d*)/$', 29 | neighborhood_monitoring, 30 | name="geotagging-neighborhood_monitoring"), 31 | url(r'^kml_neighborhood_feed/(?P\d*)/$', 32 | kml_neighborhood_feed, 33 | name="geotagging-kml_neighborhood_feed"), 34 | ) 35 | -------------------------------------------------------------------------------- /geotagging/templates/geotagging/admin/edit_inline/geotagging_inline.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
3 |

{% trans "Geotag" %}

4 | {{ inline_admin_formset.formset.management_form }} 5 | {{ inline_admin_formset.formset.non_form_errors }} 6 | 7 | {% for inline_admin_form in inline_admin_formset %} 8 |
9 | {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %} 10 |

11 | {{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %} 12 |

13 | {% if inline_admin_form.show_url %} 14 |

{% trans "View on site" %}

15 | {% endif %} 16 |

{% trans "Mark object on the map as either a point, path, or polygon." %}

17 | {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} 18 | 19 | {% for fieldset in inline_admin_form %} 20 | {% include "admin/includes/fieldset.html" %} 21 | {% endfor %} 22 | {{ inline_admin_form.pk_field.field }} 23 | {{ inline_admin_form.fk_field.field }} 24 |
25 | {% endfor %} 26 | 27 | {# #} 30 |
31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Lincoln Loop, LLC and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Lincoln Loop nor the names of its contributors may 15 | be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /geotagging/tests/model_tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.contrib.auth.models import User 3 | from django.contrib.gis.geos import Point, Polygon 4 | 5 | from geotagging.models import Geotag 6 | from geotagging import register, AlreadyRegistered 7 | 8 | class ModelTest(TestCase): 9 | 10 | def setUp(self): 11 | try: 12 | register(User) 13 | except AlreadyRegistered: 14 | pass 15 | self.obj = User.objects.create(username='user') 16 | self.point = Point(5, 5) 17 | self.poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), 18 | ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4))) 19 | 20 | def testEmptyGeotag(self): 21 | "Empty geotag returns none" 22 | self.assertEqual(self.obj.geotag, None) 23 | 24 | def testSetGeotag(self): 25 | "Geotag can be set on the object" 26 | self.obj.geotag = self.point 27 | self.assertEqual(self.obj.geotag.point, self.point) 28 | geotag = Geotag.objects.get_for_object(self.obj) 29 | self.assertEqual(geotag.point, self.point) 30 | 31 | def testChangeGeotag(self): 32 | "Geotag can be changed on the object" 33 | self.obj.geotag = self.point 34 | self.obj.geotag = self.poly 35 | self.assertEqual(self.obj.geotag.polygon, self.poly) 36 | geotag = Geotag.objects.get_for_object(self.obj) 37 | self.assertEqual(geotag.point, None) 38 | self.assertEqual(geotag.polygon, self.poly) 39 | 40 | def testGetGeomGeotag(self): 41 | "Geotag can find the right geom" 42 | self.obj.geotag = self.poly 43 | self.assertEqual(self.obj.geotag.get_geom(), self.poly) 44 | 45 | def testDeleteGeotag(self): 46 | "Geotag can be removed from the object" 47 | self.obj.geotag = None 48 | self.assertEqual(self.obj.geotag, None) 49 | geotag = Geotag.objects.get_for_object(self.obj) 50 | self.assertEqual(geotag, None) 51 | -------------------------------------------------------------------------------- /geotagging/fixes/gis/admin/widgets.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.gis.admin.widgets import OpenLayersWidget 3 | from django.contrib.gis.geos import GEOSGeometry, GEOSException 4 | from django.contrib.gis.gdal import OGRException 5 | from django.template import loader, Context 6 | from django.utils import translation 7 | 8 | # Creating a template context that contains Django settings 9 | # values needed by admin map templates. 10 | geo_context = Context({'ADMIN_MEDIA_PREFIX' : settings.ADMIN_MEDIA_PREFIX, 11 | 'LANGUAGE_BIDI' : translation.get_language_bidi(), 12 | }) 13 | 14 | class OpenLayersWidgetFixed(OpenLayersWidget): 15 | """ 16 | Renders an OpenLayers map using the WKT of the geometry. 17 | """ 18 | def render(self, name, value, attrs=None): 19 | attrs['field_name'] = name 20 | # Update the template parameters with any attributes passed in. 21 | if attrs: self.params.update(attrs) 22 | # Defaulting the WKT value to a blank string -- this 23 | # will be tested in the JavaScript and the appropriate 24 | # interfaace will be constructed. 25 | self.params['wkt'] = '' 26 | 27 | # If a string reaches here (via a validation error on another 28 | # field) then just reconstruct the Geometry. 29 | if isinstance(value, basestring): 30 | try: 31 | value = GEOSGeometry(value) 32 | except (GEOSException, ValueError): 33 | value = None 34 | 35 | if value and value.geom_type.upper() != self.geom_type: 36 | value = None 37 | 38 | # Constructing the dictionary of the map options. 39 | self.params['map_options'] = self.map_options() 40 | 41 | # Constructing the JavaScript module name using the ID of 42 | # the GeometryField (passed in via the `attrs` keyword). 43 | js_safe_field_name = self.params['field_name'].replace('-', '__') 44 | self.params['module'] = 'geodjango_%s' % js_safe_field_name 45 | 46 | if value: 47 | # Transforming the geometry to the projection used on the 48 | # OpenLayers map. 49 | srid = self.params['srid'] 50 | if value.srid != srid: 51 | try: 52 | ogr = value.ogr 53 | ogr.transform(srid) 54 | wkt = ogr.wkt 55 | except OGRException: 56 | wkt = '' 57 | else: 58 | wkt = value.wkt 59 | 60 | # Setting the parameter WKT with that of the transformed 61 | # geometry. 62 | self.params['wkt'] = wkt 63 | return loader.render_to_string(self.template, self.params, 64 | context_instance=geo_context) -------------------------------------------------------------------------------- /geotagging/templatetags/geotagging_tags.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | from django.contrib.gis.measure import D 4 | from django.db import models 5 | from django.db.models import Q 6 | 7 | from geotagging.models import Geotag, HAS_GEOGRAPHY 8 | 9 | register = template.Library() 10 | 11 | class GetGeotagsNode(template.Node): 12 | def __init__(self, geom, asvar=None, miles=5): 13 | self.geom = geom 14 | self.asvar = asvar 15 | self.distance = D(mi=miles) 16 | 17 | def render(self, context): 18 | try: 19 | geom = template.resolve_variable(self.geom, context) 20 | except template.VariableDoesNotExist: 21 | return "" 22 | 23 | # spheroid will result in more accurate results, but at the cost of 24 | # performance: http://code.djangoproject.com/ticket/6715 25 | if HAS_GEOGRAPHY: 26 | objects = Geotag.objects.filter( 27 | Q(point__distance_lte=(geom, self.distance)) | 28 | Q(line__distance_lte=(geom, self.distance)) | 29 | Q(multilinestring__distance_lte=(geom, self.distance)) | 30 | Q(polygon__distance_lte=(geom, self.distance))) 31 | else: 32 | if geom.geom_type != 'Point': 33 | raise template.TemplateSyntaxError("Geotagging Error: This database does not support non-Point geometry distance lookups.") 34 | else: 35 | objects = Geotag.objects.filter(point__distance_lte=(geom, 36 | self.distance)) 37 | context[self.asvar] = objects 38 | return "" 39 | 40 | @register.tag 41 | def get_objects_nearby(parser, token): 42 | """ 43 | Populates a context variable with a list of :model:`geotagging.Geotag` objects 44 | that are within a given distance of a map geometry (point, line, polygon). 45 | Example:: 46 | 47 | {% get_objects_nearby obj.point as nearby_objects %} 48 | 49 | This will find all objects tagged within 5 miles of ``obj.point``. To 50 | search within a different radius, use the following format:: 51 | 52 | {% get_objects_nearby obj.point within 10 as nearby_objects %} 53 | 54 | This will find all objects tagged within 10 miles of ``obj.point``. 55 | 56 | *Note: Distances queries are approximate and may vary slightly from 57 | true measurements.* 58 | 59 | """ 60 | 61 | bits = token.split_contents() 62 | item = bits[1] 63 | args = {} 64 | 65 | if len(bits) < 4: 66 | raise template.TemplateSyntaxError("%r tag takes at least 4 arguments" % bits[0]) 67 | 68 | biter = iter(bits[2:]) 69 | for bit in biter: 70 | if bit == "as": 71 | args["asvar"] = biter.next() 72 | elif bit == "within": 73 | args["miles"] = biter.next() 74 | else: 75 | raise template.TemplateSyntaxError("%r tag got an unknown argument: %r" % (bits[0], bit)) 76 | 77 | return GetGeotagsNode(item, **args) 78 | -------------------------------------------------------------------------------- /geotagging/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.gis.db import models 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.contrib.contenttypes import generic 4 | from django.core.exceptions import ObjectDoesNotExist 5 | from django.utils.translation import ugettext_lazy as _ 6 | from django.db import connection 7 | 8 | HAS_GEOGRAPHY = False 9 | try: 10 | # You need Django 1.2 and PostGIS > 1.5 11 | # http://code.djangoproject.com/wiki/GeoDjango1.2#PostGISGeographySupport 12 | if connection.ops.geography: 13 | HAS_GEOGRAPHY = True 14 | except AttributeError: 15 | pass 16 | 17 | def field_kwargs(verbose_name): 18 | """ 19 | Build kwargs for field based on the availability of geography fields 20 | """ 21 | kwargs = { 22 | 'blank': True, 23 | 'null': True, 24 | 'verbose_name': _(verbose_name), 25 | } 26 | if HAS_GEOGRAPHY: 27 | kwargs['geography'] = True 28 | return kwargs 29 | 30 | class GeotagManager(models.GeoManager): 31 | def get_for_object(self, obj): 32 | """ 33 | Returns the Geotag associated with the given object or None. 34 | """ 35 | ctype = ContentType.objects.get_for_model(obj) 36 | try: 37 | return self.get(content_type=ctype, object_id=obj.pk) 38 | except ObjectDoesNotExist: 39 | pass 40 | return None 41 | 42 | def update_geotag(self, obj, geotag): 43 | """ 44 | Updates the geotag associated with an object 45 | """ 46 | geotag_obj = self.get_for_object(obj) 47 | if not geotag_obj and not geotag: 48 | # you are trying to delete a geotag that does not exist. do nothing 49 | return 50 | if not geotag_obj: 51 | ctype = ContentType.objects.get_for_model(obj) 52 | geotag_obj = self.create(content_type=ctype, object_id=obj.pk) 53 | if not geotag: 54 | geotag_obj.delete() 55 | else: 56 | old_geotag_geom = geotag_obj.get_geom() 57 | if old_geotag_geom: 58 | old_field_name = old_geotag_geom.geom_type.lower() 59 | setattr(geotag_obj, old_field_name, None) 60 | field_name = geotag.geom_type.lower() 61 | setattr(geotag_obj, field_name, geotag) 62 | geotag_obj.save() 63 | 64 | 65 | 66 | class Geotag(models.Model): 67 | """ 68 | A simple wrapper around the GeoDjango field types 69 | """ 70 | 71 | # Content-object field 72 | content_type = models.ForeignKey(ContentType, 73 | related_name="content_type_set_for_%(class)s") 74 | object_id = models.PositiveIntegerField(_('object ID'), max_length=50) 75 | tagged_obj = generic.GenericForeignKey(ct_field="content_type", 76 | fk_field="object_id") 77 | 78 | point = models.PointField(**field_kwargs('point')) 79 | multilinestring = models.MultiLineStringField(**field_kwargs('multi-line')) 80 | line = models.LineStringField(**field_kwargs('line')) 81 | polygon = models.PolygonField(**field_kwargs('polygon')) 82 | geometry_collection = models.GeometryCollectionField( 83 | **field_kwargs('geometry collection')) 84 | 85 | objects = GeotagManager() 86 | 87 | def get_geom(self): 88 | """Returns the geometry in use or None""" 89 | for geom_type in ('point', 'line', 'multilinestring', 90 | 'polygon', 'geometry_collection'): 91 | geom = getattr(self, geom_type) 92 | if geom: 93 | return geom 94 | return None 95 | 96 | -------------------------------------------------------------------------------- /geotagging/admin/options.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | from django import forms 4 | from django.contrib.gis.forms import GeometryField 5 | from django.contrib.gis.gdal import OGRException 6 | 7 | from geotagging.fixes.gis.admin.options import GeoGenericStackedInline 8 | from geotagging.models import Geotag 9 | 10 | 11 | class GeotagsAdminForm(forms.ModelForm): 12 | """Custom form to use for inline admin widget""" 13 | catchall = forms.CharField(widget=forms.Textarea, required=False) 14 | line = GeometryField(widget=forms.HiddenInput, null=True, required=False, 15 | geom_type='LINESTRING', srid=4326) 16 | polygon = GeometryField(widget=forms.HiddenInput, null=True, required=False, 17 | geom_type='POLYGON', srid=4326) 18 | 19 | 20 | def full_clean(self): 21 | """ 22 | Sets geom based on catchall value and erases any other geoms 23 | """ 24 | if '%s-catchall' % self.prefix in self.data: 25 | value = self.data['%s-catchall' % self.prefix] 26 | self.data['%s-point' % self.prefix] = '' 27 | self.data['%s-line' % self.prefix] = '' 28 | self.data['%s-polygon' % self.prefix] = '' 29 | if re.search('POINT\((.*)\)', value): 30 | self.data['%s-point' % self.prefix] = value 31 | elif re.search('LINESTRING\((.*)\)', value): 32 | self.data['%s-line' % self.prefix] = value 33 | elif re.search('POLYGON\((.*)\)', value): 34 | self.data['%s-polygon' % self.prefix] = value 35 | super(GeotagsAdminForm, self).full_clean() 36 | 37 | def __init__(self, *args, **kwargs): 38 | super(GeotagsAdminForm, self).__init__(*args, **kwargs) 39 | 40 | # Prefill catchall field if geotag already exists 41 | if self.instance: 42 | found = False 43 | # get SRID of map widget 44 | srid = self.fields['point'].widget.params['srid'] 45 | # get srid of geom in model 46 | for geom_type in ('point', 'line', 'polygon'): 47 | geom = getattr(self.instance, geom_type) 48 | if geom: 49 | db_field = geom 50 | found = True 51 | break 52 | if not found: 53 | return 54 | 55 | # Transforming the geometry to the projection used on the 56 | # OpenLayers map. 57 | if db_field.srid != srid: 58 | try: 59 | ogr = db_field.ogr 60 | ogr.transform(srid) 61 | wkt = ogr.wkt 62 | except OGRException: 63 | wkt = '' 64 | else: 65 | wkt = db_field.wkt 66 | self.fields['catchall'].initial = wkt 67 | 68 | 69 | 70 | class GeotagsInline(GeoGenericStackedInline): 71 | """ 72 | A single inline form for use in the admin 73 | """ 74 | map_template = 'geotagging/admin/openlayer_multiwidget.html' 75 | # inject Open Street map if GDAL works 76 | from django.contrib.gis import gdal 77 | if gdal.HAS_GDAL: 78 | map_template = 'geotagging/admin/osm_multiwidget.html' 79 | template = 'geotagging/admin/edit_inline/geotagging_inline.html' 80 | model = Geotag 81 | max_num = 1 82 | form = GeotagsAdminForm 83 | 84 | 85 | def get_formset(self, request, obj=None, **kwargs): 86 | """ 87 | Hacks up the form so we can remove some geometries that OpenLayers 88 | can't handle. 89 | """ 90 | fset = super(GeotagsInline, self).get_formset(request, 91 | obj=None, **kwargs) 92 | # put catchall on top so the javascript can access it 93 | fset.form.base_fields.keyOrder.reverse() 94 | fset.form.base_fields['point'].label = "Geotag" 95 | # these fields aren't easily editable via openlayers 96 | for field in ('geometry_collection', 'multilinestring'): 97 | fset.form.Meta.exclude.append(field) 98 | del(fset.form.base_fields[field]) 99 | return fset 100 | -------------------------------------------------------------------------------- /geotagging/tests/tag_tests.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.test import TestCase 3 | from django.contrib.auth.models import User 4 | from django.contrib.gis.geos import Point, LineString 5 | 6 | from geotagging.models import Geotag, HAS_GEOGRAPHY 7 | 8 | class TagTestCase(TestCase): 9 | """Helper class with some tag helper functions""" 10 | 11 | def installTagLibrary(self, library): 12 | template.libraries[library] = __import__(library) 13 | 14 | def renderTemplate(self, tstr, **context): 15 | tmpl = template.Template(tstr) 16 | cntxt = template.Context(context) 17 | return tmpl.render(cntxt) 18 | 19 | class OutputTagTest(TagTestCase): 20 | 21 | def setUp(self): 22 | self.installTagLibrary('geotagging.templatetags.geotagging_tags') 23 | denver_user = User.objects.create(username='denver') 24 | dia_user = User.objects.create(username='dia') 25 | aa_user = User.objects.create(username='annarbor') 26 | self.line = LineString((-104.552299, 38.128626), (-103.211191, 40.715081)) 27 | self.denver = Geotag.objects.create(tagged_obj=denver_user, 28 | point=Point(-104.9847034, 39.739153600000002)) 29 | dia = Geotag.objects.create(tagged_obj=dia_user, 30 | point=Point(-104.673856, 39.849511999999997)) 31 | aa = Geotag.objects.create(tagged_obj=aa_user, 32 | point=Point(-83.726329399999997, 42.2708716)) 33 | 34 | def testOutput(self): 35 | "get_objects_nearby tag has no output" 36 | tmpl = "{% load geotagging_tags %}"\ 37 | "{% get_objects_nearby obj.point as nearby_objs %}" 38 | o = self.renderTemplate(tmpl, obj=self.denver) 39 | self.assertEqual(o.strip(), "") 40 | 41 | def testAsVar(self): 42 | tmpl = "{% load geotagging_tags %}"\ 43 | "{% get_objects_nearby obj.point as nearby_objs %}"\ 44 | "{{ nearby_objs|length }}" 45 | o = self.renderTemplate(tmpl, obj=self.denver) 46 | self.assertEqual(o.strip(), "1") 47 | 48 | def testShortDistance(self): 49 | # DIA is about 18 miles from downtown Denver 50 | short_tmpl = "{% load geotagging_tags %}"\ 51 | "{% get_objects_nearby obj.point as nearby_objs within 17 %}"\ 52 | "{{ nearby_objs|length }}" 53 | o = self.renderTemplate(short_tmpl, obj=self.denver) 54 | self.assertEqual(o.strip(), "1") 55 | long_tmpl = short_tmpl.replace("17", "19") 56 | o = self.renderTemplate(long_tmpl, obj=self.denver) 57 | self.assertEqual(o.strip(), "2") 58 | 59 | def testLongDistance(self): 60 | # Ann Arbor is about 1122 miles from Denver 61 | short_tmpl = "{% load geotagging_tags %}"\ 62 | "{% get_objects_nearby obj.point within 1115 as nearby_objs %}"\ 63 | "{{ nearby_objs|length }}" 64 | o = self.renderTemplate(short_tmpl, obj=self.denver) 65 | self.assertEqual(o.strip(), "2") 66 | long_tmpl = short_tmpl.replace("1115", "1125") 67 | o = self.renderTemplate(long_tmpl, obj=self.denver) 68 | self.assertEqual(o.strip(), "3") 69 | 70 | def testNonPoint(self): 71 | hit_tmpl = "{% load geotagging_tags %}"\ 72 | "{% get_objects_nearby line within 50 as nearby_objs %}"\ 73 | "{{ nearby_objs|length }}" 74 | miss_tmpl = hit_tmpl.replace("50", "10") 75 | 76 | if HAS_GEOGRAPHY: 77 | hit = self.renderTemplate(hit_tmpl, line=self.line) 78 | self.assertEqual(hit.strip(), "1") 79 | miss = self.renderTemplate(miss_tmpl, line=self.line) 80 | self.assertEqual(miss.strip(), "0") 81 | else: 82 | try: 83 | hit = self.renderTemplate(hit_tmpl, line=self.line) 84 | # the previous line should always render an exception 85 | self.assertEqual(True, False) 86 | except template.TemplateSyntaxError, e: 87 | self.assertEqual(e.args[0], 'Geotagging Error: This database does not support non-Point geometry distance lookups.') 88 | 89 | class SyntaxTagTest(TestCase): 90 | 91 | def getNode(self, strng): 92 | from geotagging.templatetags.geotagging_tags import get_objects_nearby 93 | return get_objects_nearby(None, template.Token(template.TOKEN_BLOCK, 94 | strng)) 95 | 96 | def assertNodeException(self, strng): 97 | self.assertRaises(template.TemplateSyntaxError, 98 | self.getNode, strng) 99 | 100 | def testInvalidSyntax(self): 101 | self.assertNodeException("get_objects_nearby as") 102 | self.assertNodeException("get_objects_nearby notas objects_nearby") 103 | -------------------------------------------------------------------------------- /geotagging/templates/geotagging/view_kml_feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Map 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 104 | 105 | 106 |

OSM + Google Maps + KML feed

107 |
108 | 109 | 110 | -------------------------------------------------------------------------------- /geotagging/templates/geotagging/view_georss_feed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | OpenLayers: Sundials on a Spherical Mercator Map 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 104 | 105 | 106 |

OSM + Google Maps + GeoRSS feed

107 |
108 | 109 | 110 | -------------------------------------------------------------------------------- /geotagging/templates/geotagging/view_neighborhood_monitoring.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | In your neighborhoood 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 104 | 105 | 106 |

Neighborhood monitoring

107 |

User details

108 |
    109 |
  • user IP : {{ user_ip }}
  • 110 |
  • user location : {{ user_location_pnt }}
  • 111 |
  • user city : {{ user_city|pprint }}
  • 112 |
113 |

Points

114 |
    115 | {% for point in geotag_points %} 116 |
  • {{ point.content_type.name }} | {{ point }} -- {{ point.distance }}
  • 117 | {% endfor %} 118 |
119 |
120 | 121 | 122 | -------------------------------------------------------------------------------- /geotagging/templates/geotagging/view_kml_feeds.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Map 4 | 5 | 6 | 7 | 8 | 9 | 18 | 19 | 20 | 124 | 125 | 126 |

OSM + Google Maps + KML feed

127 |
128 | 129 | 130 | -------------------------------------------------------------------------------- /geotagging/fixes/gis/admin/options.py: -------------------------------------------------------------------------------- 1 | from django.contrib.admin.options import BaseModelAdmin, InlineModelAdmin, \ 2 | StackedInline, TabularInline 3 | from geotagging.fixes.gis.admin.widgets import OpenLayersWidgetFixed as OpenLayersWidget 4 | 5 | from django.contrib.gis.gdal import OGRGeomType 6 | from django.contrib.gis.db import models 7 | 8 | from django.contrib.contenttypes.generic import GenericInlineModelAdmin, \ 9 | GenericStackedInline, GenericTabularInline 10 | 11 | class GeoBaseModelAdmin(BaseModelAdmin): 12 | """ 13 | The administration options class for Geographic models. Map settings 14 | may be overloaded from their defaults to create custom maps. 15 | """ 16 | # The default map settings that may be overloaded -- still subject 17 | # to API changes. 18 | default_lon = 0 19 | default_lat = 0 20 | default_zoom = 4 21 | display_wkt = False 22 | display_srid = False 23 | extra_js = [] 24 | num_zoom = 18 25 | max_zoom = False 26 | min_zoom = False 27 | units = False 28 | max_resolution = False 29 | max_extent = False 30 | modifiable = True 31 | mouse_position = True 32 | scale_text = True 33 | layerswitcher = True 34 | scrollable = True 35 | map_width = 600 36 | map_height = 400 37 | map_srid = 4326 38 | map_template = 'gis/admin/openlayers.html' 39 | openlayers_url = 'http://openlayers.org/api/2.8/OpenLayers.js' 40 | wms_url = 'http://labs.metacarta.com/wms/vmap0' 41 | wms_layer = 'basic' 42 | wms_name = 'OpenLayers WMS' 43 | debug = False 44 | widget = OpenLayersWidget 45 | # inject Open Street map if GDAL works 46 | from django.contrib.gis import gdal 47 | if gdal.HAS_GDAL: 48 | map_template = 'gis/admin/osm.html' 49 | extra_js = ['http://openstreetmap.org/openlayers/OpenStreetMap.js'] 50 | num_zoom = 20 51 | map_srid = 900913 52 | max_extent = '-20037508,-20037508,20037508,20037508' 53 | max_resolution = 156543.0339 54 | units = 'm' 55 | 56 | 57 | def formfield_for_dbfield(self, db_field, **kwargs): 58 | """ 59 | Overloaded from ModelAdmin so that an OpenLayersWidget is used 60 | for viewing/editing GeometryFields. 61 | """ 62 | if isinstance(db_field, models.GeometryField): 63 | # Setting the widget with the newly defined widget. 64 | kwargs['widget'] = self.get_map_widget(db_field) 65 | return db_field.formfield(**kwargs) 66 | else: 67 | return super(GeoBaseModelAdmin, self).formfield_for_dbfield(db_field, **kwargs) 68 | 69 | def get_map_widget(self, db_field): 70 | """ 71 | Returns a subclass of the OpenLayersWidget (or whatever was specified 72 | in the `widget` attribute) using the settings from the attributes set 73 | in this class. 74 | """ 75 | is_collection = db_field._geom in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION') 76 | if is_collection: 77 | if db_field._geom == 'GEOMETRYCOLLECTION': collection_type = 'Any' 78 | else: collection_type = OGRGeomType(db_field._geom.replace('MULTI', '')) 79 | else: 80 | collection_type = 'None' 81 | 82 | class OLMap(self.widget): 83 | template = self.map_template 84 | geom_type = db_field._geom 85 | params = {'default_lon' : self.default_lon, 86 | 'default_lat' : self.default_lat, 87 | 'default_zoom' : self.default_zoom, 88 | 'display_wkt' : self.debug or self.display_wkt, 89 | 'geom_type' : OGRGeomType(db_field._geom), 90 | 'field_name' : db_field.name, 91 | 'is_collection' : is_collection, 92 | 'scrollable' : self.scrollable, 93 | 'layerswitcher' : self.layerswitcher, 94 | 'collection_type' : collection_type, 95 | 'is_linestring' : db_field._geom in ('LINESTRING', 'MULTILINESTRING'), 96 | 'is_polygon' : db_field._geom in ('POLYGON', 'MULTIPOLYGON'), 97 | 'is_point' : db_field._geom in ('POINT', 'MULTIPOINT'), 98 | 'num_zoom' : self.num_zoom, 99 | 'max_zoom' : self.max_zoom, 100 | 'min_zoom' : self.min_zoom, 101 | 'units' : self.units, #likely shoud get from object 102 | 'max_resolution' : self.max_resolution, 103 | 'max_extent' : self.max_extent, 104 | 'modifiable' : self.modifiable, 105 | 'mouse_position' : self.mouse_position, 106 | 'scale_text' : self.scale_text, 107 | 'map_width' : self.map_width, 108 | 'map_height' : self.map_height, 109 | 'srid' : self.map_srid, 110 | 'display_srid' : self.display_srid, 111 | 'wms_url' : self.wms_url, 112 | 'wms_layer' : self.wms_layer, 113 | 'wms_name' : self.wms_name, 114 | 'debug' : self.debug, 115 | } 116 | return OLMap 117 | 118 | # Using the Beta OSM in the admin requires the following: 119 | # (1) The Google Maps Mercator projection needs to be added 120 | # to your `spatial_ref_sys` table. You'll need at least GDAL 1.5: 121 | # >>> from django.contrib.gis.gdal import SpatialReference 122 | # >>> from django.contrib.gis.utils import add_postgis_srs 123 | # >>> add_postgis_srs(SpatialReference(900913)) # Adding the Google Projection 124 | 125 | 126 | #inlines 127 | class GeoInlineModelAdmin(InlineModelAdmin, GeoBaseModelAdmin): 128 | def _media(self): 129 | "Injects OpenLayers JavaScript into the admin." 130 | media = super(GeoInlineModelAdmin, self)._media() 131 | media.add_js([self.openlayers_url]) 132 | media.add_js(self.extra_js) 133 | return media 134 | media = property(_media) 135 | 136 | class GeoStackedInline(StackedInline, GeoInlineModelAdmin): 137 | pass 138 | class GeoTabularInline(TabularInline, GeoInlineModelAdmin): 139 | map_width = 300 140 | map_height = 200 141 | 142 | #generic inlines 143 | class GeoGenericInlineModelAdmin(GenericInlineModelAdmin, GeoInlineModelAdmin): 144 | pass 145 | 146 | class GeoGenericStackedInline(GenericStackedInline, GeoGenericInlineModelAdmin): 147 | pass 148 | class GeoGenericTablularInline(GenericTabularInline, GeoGenericInlineModelAdmin): 149 | map_width = 300 150 | map_height = 200 151 | -------------------------------------------------------------------------------- /geotagging/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.contrib.gis.measure import D 4 | from django.contrib.gis.shortcuts import render_to_kml 5 | from django.core.exceptions import ObjectDoesNotExist 6 | from django.core.urlresolvers import reverse 7 | from django.http import HttpResponseRedirect 8 | from django.shortcuts import render_to_response 9 | from django.template import RequestContext 10 | from django.views.generic.simple import direct_to_template 11 | 12 | 13 | from geotagging.models import Geotag 14 | 15 | def kml_feed(request, template="geotagging/geotagging.kml", 16 | geotag_field_name=None, content_type_name=None, 17 | object_id=None): 18 | """ 19 | Return a KML feed of a particular geotag type : point, line, polygon 20 | This feed can be restricted by content_type and object_id. 21 | """ 22 | if geotag_field_name: 23 | kw = str('%s__isnull' % geotag_field_name) 24 | geotagging = Geotag.objects.filter(**{kw:False}) 25 | if content_type_name: 26 | geotagging = geotagging.objects.filter(content_type__name=content_type_name) 27 | if object_id: 28 | geotagging = geotagging.filter(object_id=object_id) 29 | context = RequestContext(request, { 30 | 'places' : geotagging.kml(), 31 | }) 32 | return render_to_kml(template,context_instance=context) 33 | 34 | def kml_feed_map(request,template="geotagging/view_kml_feed.html", 35 | geotag_field_name=None, content_type_name=None): 36 | """ 37 | Direct the user to a template with all the required parameters to render 38 | the KML feed on a google map. 39 | """ 40 | if content_type_name: 41 | kml_feed = reverse("geotagging-kml_feed_per_contenttype", 42 | kwargs={ 43 | "geotag_field_name" : geotag_class_name, 44 | "content_type_name" : content_type_name, 45 | }) 46 | else: 47 | kml_feed = reverse("geotagging-kml_feed",kwargs={"geotag_class_name":geotag_class_name}) 48 | 49 | 50 | extra_context = { 51 | "kml_feed" : kml_feed 52 | } 53 | return direct_to_template(request,template=template,extra_context=extra_context) 54 | 55 | def kml_feeds_map(request,template="geotagging/view_kml_feeds.html", 56 | content_type_name=None): 57 | """ 58 | Direct the user to a template with all the required parameters to render 59 | the KML feeds (point, line, polygon) on a google map. 60 | """ 61 | if content_type_name: 62 | kml_feed_point = reverse("geotagging-kml_feed_per_contenttype", 63 | kwargs={ 64 | "geotag_class_name" : "point", 65 | "content_type_name" : content_type_name, 66 | }) 67 | kml_feed_line = reverse("geotagging-kml_feed_per_contenttype", 68 | kwargs={ 69 | "geotag_class_name" : "line", 70 | "content_type_name" : content_type_name, 71 | }) 72 | kml_feed_polygon = reverse("geotagging-kml_feed_per_contenttype", 73 | kwargs={ 74 | "geotag_class_name" : "polygon", 75 | "content_type_name" : content_type_name, 76 | }) 77 | else: 78 | kml_feed_point = reverse("geotagging-kml_feed",kwargs={"geotag_class_name": "point"}) 79 | kml_feed_line = reverse("geotagging-kml_feed",kwargs={"geotag_class_name": "line"}) 80 | kml_feed_polygon = reverse("geotagging-kml_feed",kwargs={"geotag_class_name": "polygon"}) 81 | 82 | 83 | extra_context = { 84 | "kml_feed_point" : kml_feed_point, 85 | "kml_feed_line" : kml_feed_line, 86 | "kml_feed_polygon" : kml_feed_polygon 87 | } 88 | return direct_to_template(request,template=template,extra_context=extra_context) 89 | 90 | 91 | 92 | def kml_neighborhood_feed(request, template="geotagging/geotagging.kml", 93 | distance_lt_km=None ,content_type_name=None, 94 | object_id=None): 95 | """ 96 | Return a KML feed of all the geotagging in a around the user. This view takes 97 | an argument called `distance_lt_km` which is the radius of the permeter your 98 | are searching in. This feed can be restricted based on the content type of 99 | the element you want to get. 100 | """ 101 | from django.contrib.gis.utils import GeoIP 102 | gip=GeoIP() 103 | if request.META["REMOTE_ADDR"] != "127.0.0.1": 104 | user_ip = request.META["REMOTE_ADDR"] 105 | else: 106 | user_ip = "populous.com" 107 | user_location_pnt = gip.geos(user_ip) 108 | 109 | criteria_pnt = { 110 | "point__distance_lt" : (user_location_pnt, 111 | D(km=float(distance_lt_km)) 112 | ) 113 | } 114 | if content_type_name: 115 | criteria_pnt["content_type__name"]==content_type_name 116 | 117 | geotagging = Point.objects.filter(**criteria_pnt) 118 | 119 | context = RequestContext(request, { 120 | 'places' : geotagging.kml(), 121 | 122 | }) 123 | return render_to_kml(template,context_instance=context) 124 | 125 | def neighborhood_monitoring(request, 126 | template="geotagging/view_neighborhood_monitoring.html", 127 | content_type_name=None, distance_lt_km=None): 128 | """ 129 | Direct the user to a template that is able to render the `kml_neighborhood_feed` 130 | on a google map. This feed can be restricted based on the content type of 131 | the element you want to get. 132 | """ 133 | if distance_lt_km == None: 134 | distance_lt_km = 10 135 | gip=GeoIP() 136 | if request.META["REMOTE_ADDR"] != "127.0.0.1": 137 | user_ip = request.META["REMOTE_ADDR"] 138 | else: 139 | user_ip = "populous.com" 140 | user_location_pnt = gip.geos(user_ip) 141 | 142 | kml_feed = reverse("geotagging-kml_neighborhood_feed", 143 | kwargs={"distance_lt_km":distance_lt_km}) 144 | criteria_pnt = { 145 | "point__distance_lt" : (user_location_pnt, 146 | D(km=float(distance_lt_km)) 147 | ) 148 | } 149 | geotag_points = Point.objects.filter(**criteria_pnt).distance(user_location_pnt).order_by("-distance") 150 | context = RequestContext(request, { 151 | "user_ip" : user_ip, 152 | "user_location_pnt" : user_location_pnt, 153 | "geotag_points" : geotag_points, 154 | "user_city" : gip.city(user_ip), 155 | "kml_feed" : kml_feed, 156 | }) 157 | return render_to_response(template,context_instance=context) 158 | -------------------------------------------------------------------------------- /geotagging/templates/geotagging/admin/openlayer_multiwidget.js: -------------------------------------------------------------------------------- 1 | {# Author: Justin Bronn, Travis Pinney & Dane Springmeyer #} 2 | {% block vars %} 3 | var catchallId = '{{ id }}'.replace(/point$/, 'catchall'); 4 | document.getElementById(catchallId).parentNode.parentNode.style['display'] = "none"; 5 | var lineId = '{{ id }}'.replace(/point$/, 'line'); 6 | document.getElementById(lineId).parentNode.parentNode.style['display'] = "none"; 7 | var polygonId = '{{ id }}'.replace(/point$/, 'polygon'); 8 | document.getElementById(polygonId).parentNode.parentNode.style['display'] = "none"; 9 | 10 | var {{ module }} = {}; 11 | {{ module }}.map = null; {{ module }}.controls = null; {{ module }}.panel = null; {{ module }}.re = new RegExp("^SRID=\d+;(.+)", "i"); {{ module }}.layers = {}; 12 | {{ module }}.wkt_f = new OpenLayers.Format.WKT(); 13 | {{ module }}.is_collection = {{ is_collection|yesno:"true,false" }}; 14 | {{ module }}.collection_type = '{{ collection_type }}'; 15 | {{ module }}.is_linestring = {{ is_linestring|yesno:"true,false" }}; 16 | {{ module }}.is_polygon = {{ is_polygon|yesno:"true,false" }}; 17 | {{ module }}.is_point = {{ is_point|yesno:"true,false" }}; 18 | {% endblock %} 19 | {{ module }}.get_ewkt = function(feat){return 'SRID={{ srid }};' + {{ module }}.wkt_f.write(feat);} 20 | {{ module }}.read_wkt = function(wkt){ 21 | // OpenLayers cannot handle EWKT -- we make sure to strip it out. 22 | // EWKT is only exposed to OL if there's a validation error in the admin. 23 | var match = {{ module }}.re.exec(wkt); 24 | if (match){wkt = match[1];} 25 | return {{ module }}.wkt_f.read(wkt); 26 | } 27 | {{ module }}.write_wkt = function(feat){ 28 | if ({{ module }}.is_collection){ {{ module }}.num_geom = feat.geometry.components.length;} 29 | else { {{ module }}.num_geom = 1;} 30 | document.getElementById(catchallId).value = {{ module }}.get_ewkt(feat); 31 | } 32 | {{ module }}.add_wkt = function(event){ 33 | // This function will sync the contents of the `vector` layer with the 34 | // WKT in the text field. 35 | if ({{ module }}.is_collection){ 36 | var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}()); 37 | for (var i = 0; i < {{ module }}.layers.vector.features.length; i++){ 38 | feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]); 39 | } 40 | {{ module }}.write_wkt(feat); 41 | } else { 42 | // Make sure to remove any previously added features. 43 | if ({{ module }}.layers.vector.features.length > 1){ 44 | old_feats = [{{ module }}.layers.vector.features[0]]; 45 | {{ module }}.layers.vector.removeFeatures(old_feats); 46 | {{ module }}.layers.vector.destroyFeatures(old_feats); 47 | } 48 | {{ module }}.write_wkt(event.feature); 49 | } 50 | } 51 | {{ module }}.modify_wkt = function(event){ 52 | if ({{ module }}.is_collection){ 53 | if ({{ module }}.is_point){ 54 | {{ module }}.add_wkt(event); 55 | return; 56 | } else { 57 | // When modifying the selected components are added to the 58 | // vector layer so we only increment to the `num_geom` value. 59 | var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}()); 60 | for (var i = 0; i < {{ module }}.num_geom; i++){ 61 | feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]); 62 | } 63 | {{ module }}.write_wkt(feat); 64 | } 65 | } else { 66 | {{ module }}.write_wkt(event.feature); 67 | } 68 | } 69 | // Function to clear vector features and purge wkt from div 70 | {{ module }}.deleteFeatures = function(){ 71 | {{ module }}.layers.vector.removeFeatures({{ module }}.layers.vector.features); 72 | {{ module }}.layers.vector.destroyFeatures(); 73 | } 74 | {{ module }}.clearFeatures = function (){ 75 | {{ module }}.deleteFeatures(); 76 | document.getElementById(catchallId).value = ''; 77 | {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }}); 78 | } 79 | // Add Select control 80 | {{ module }}.addSelectControl = function(){ 81 | var select = new OpenLayers.Control.SelectFeature({{ module }}.layers.vector, {'toggle' : true, 'clickout' : true}); 82 | {{ module }}.map.addControl(select); 83 | select.activate(); 84 | } 85 | {{ module }}.enableDrawing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();} 86 | {{ module }}.enableEditing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();} 87 | // Create an array of controls based on geometry type 88 | {{ module }}.getControls = function(lyr){ 89 | {{ module }}.panel = new OpenLayers.Control.Panel({'displayClass': 'olControlEditingToolbar'}); 90 | var nav = new OpenLayers.Control.Navigation(); 91 | var draw_ctl; 92 | if ({{ module }}.is_linestring){ 93 | draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}); 94 | } else if ({{ module }}.is_polygon){ 95 | draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'}); 96 | } else if ({{ module }}.is_point){ 97 | draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}); 98 | } 99 | {% if modifiable %} 100 | var mod = new OpenLayers.Control.ModifyFeature(lyr, {'displayClass': 'olControlModifyFeature'}); 101 | {{ module }}.controls = [nav, draw_ctl, mod]; 102 | {% else %} 103 | {{ module }}.controls = [nav, darw_ctl]; 104 | {% endif %} 105 | } 106 | {{ module }}.init = function(){ 107 | //set catchall field to current element field 108 | //document.getElementById(catchallId).value = document.getElementById('{{ id }}').value; 109 | {% block map_options %}// The options hash, w/ zoom, resolution, and projection settings. 110 | var options = { 111 | {% autoescape off %}{% for item in map_options.items %} '{{ item.0 }}' : {{ item.1 }}{% if not forloop.last %},{% endif %} 112 | {% endfor %}{% endautoescape %} };{% endblock %} 113 | // The admin map for this geometry field. 114 | {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options); 115 | // Base Layer 116 | {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'} );{% endblock %} 117 | {{ module }}.map.addLayer({{ module }}.layers.base); 118 | {% block extra_layers %}{% endblock %} 119 | {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %} 120 | {{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}"); 121 | {{ module }}.map.addLayer({{ module }}.layers.vector); 122 | // Read WKT from the text field. 123 | var wkt = document.getElementById(catchallId).value; 124 | if (wkt){ 125 | // After reading into geometry, immediately write back to 126 | // WKT