summaryrefslogtreecommitdiff
path: root/parts/django/django/contrib/gis
diff options
context:
space:
mode:
Diffstat (limited to 'parts/django/django/contrib/gis')
-rw-r--r--parts/django/django/contrib/gis/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/admin/__init__.py12
-rw-r--r--parts/django/django/contrib/gis/admin/options.py124
-rw-r--r--parts/django/django/contrib/gis/admin/widgets.py107
-rw-r--r--parts/django/django/contrib/gis/db/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/db/backend/__init__.py11
-rw-r--r--parts/django/django/contrib/gis/db/backends/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/db/backends/adapter.py17
-rw-r--r--parts/django/django/contrib/gis/db/backends/base.py336
-rw-r--r--parts/django/django/contrib/gis/db/backends/mysql/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/db/backends/mysql/base.py13
-rw-r--r--parts/django/django/contrib/gis/db/backends/mysql/creation.py18
-rw-r--r--parts/django/django/contrib/gis/db/backends/mysql/introspection.py32
-rw-r--r--parts/django/django/contrib/gis/db/backends/mysql/operations.py65
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/adapter.py5
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/base.py12
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/compiler.py44
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/creation.py42
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/introspection.py39
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/models.py65
-rw-r--r--parts/django/django/contrib/gis/db/backends/oracle/operations.py293
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/adapter.py35
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/base.py12
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/creation.py60
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/introspection.py95
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/models.py66
-rw-r--r--parts/django/django/contrib/gis/db/backends/postgis/operations.py589
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/adapter.py8
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/base.py77
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/client.py5
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/creation.py96
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/introspection.py51
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/models.py60
-rw-r--r--parts/django/django/contrib/gis/db/backends/spatialite/operations.py343
-rw-r--r--parts/django/django/contrib/gis/db/backends/util.py56
-rw-r--r--parts/django/django/contrib/gis/db/models/__init__.py14
-rw-r--r--parts/django/django/contrib/gis/db/models/aggregates.py17
-rw-r--r--parts/django/django/contrib/gis/db/models/fields.py294
-rw-r--r--parts/django/django/contrib/gis/db/models/manager.py103
-rw-r--r--parts/django/django/contrib/gis/db/models/proxy.py64
-rw-r--r--parts/django/django/contrib/gis/db/models/query.py777
-rw-r--r--parts/django/django/contrib/gis/db/models/sql/__init__.py3
-rw-r--r--parts/django/django/contrib/gis/db/models/sql/aggregates.py61
-rw-r--r--parts/django/django/contrib/gis/db/models/sql/compiler.py278
-rw-r--r--parts/django/django/contrib/gis/db/models/sql/conversion.py27
-rw-r--r--parts/django/django/contrib/gis/db/models/sql/query.py119
-rw-r--r--parts/django/django/contrib/gis/db/models/sql/where.py89
-rw-r--r--parts/django/django/contrib/gis/feeds.py135
-rw-r--r--parts/django/django/contrib/gis/forms/__init__.py2
-rw-r--r--parts/django/django/contrib/gis/forms/fields.py67
-rw-r--r--parts/django/django/contrib/gis/gdal/LICENSE28
-rw-r--r--parts/django/django/contrib/gis/gdal/__init__.py54
-rw-r--r--parts/django/django/contrib/gis/gdal/base.py35
-rw-r--r--parts/django/django/contrib/gis/gdal/datasource.py128
-rw-r--r--parts/django/django/contrib/gis/gdal/driver.py65
-rw-r--r--parts/django/django/contrib/gis/gdal/envelope.py175
-rw-r--r--parts/django/django/contrib/gis/gdal/error.py42
-rw-r--r--parts/django/django/contrib/gis/gdal/feature.py110
-rw-r--r--parts/django/django/contrib/gis/gdal/field.py178
-rw-r--r--parts/django/django/contrib/gis/gdal/geometries.py737
-rw-r--r--parts/django/django/contrib/gis/gdal/geomtype.py85
-rw-r--r--parts/django/django/contrib/gis/gdal/layer.py212
-rw-r--r--parts/django/django/contrib/gis/gdal/libgdal.py104
-rw-r--r--parts/django/django/contrib/gis/gdal/prototypes/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/gdal/prototypes/ds.py71
-rw-r--r--parts/django/django/contrib/gis/gdal/prototypes/errcheck.py127
-rw-r--r--parts/django/django/contrib/gis/gdal/prototypes/generation.py119
-rw-r--r--parts/django/django/contrib/gis/gdal/prototypes/geom.py106
-rw-r--r--parts/django/django/contrib/gis/gdal/prototypes/srs.py72
-rw-r--r--parts/django/django/contrib/gis/gdal/srs.py337
-rw-r--r--parts/django/django/contrib/gis/gdal/tests/__init__.py25
-rw-r--r--parts/django/django/contrib/gis/gdal/tests/test_driver.py40
-rw-r--r--parts/django/django/contrib/gis/gdal/tests/test_ds.py226
-rw-r--r--parts/django/django/contrib/gis/gdal/tests/test_envelope.py94
-rw-r--r--parts/django/django/contrib/gis/gdal/tests/test_geom.py490
-rw-r--r--parts/django/django/contrib/gis/gdal/tests/test_srs.py169
-rw-r--r--parts/django/django/contrib/gis/geometry/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/geometry/backend/__init__.py21
-rw-r--r--parts/django/django/contrib/gis/geometry/backend/geos.py3
-rw-r--r--parts/django/django/contrib/gis/geometry/regex.py12
-rw-r--r--parts/django/django/contrib/gis/geometry/test_data.py105
-rw-r--r--parts/django/django/contrib/gis/geos/LICENSE27
-rw-r--r--parts/django/django/contrib/gis/geos/__init__.py14
-rw-r--r--parts/django/django/contrib/gis/geos/base.py52
-rw-r--r--parts/django/django/contrib/gis/geos/collections.py123
-rw-r--r--parts/django/django/contrib/gis/geos/coordseq.py156
-rw-r--r--parts/django/django/contrib/gis/geos/error.py20
-rw-r--r--parts/django/django/contrib/gis/geos/factory.py23
-rw-r--r--parts/django/django/contrib/gis/geos/geometry.py661
-rw-r--r--parts/django/django/contrib/gis/geos/io.py20
-rw-r--r--parts/django/django/contrib/gis/geos/libgeos.py141
-rw-r--r--parts/django/django/contrib/gis/geos/linestring.py152
-rw-r--r--parts/django/django/contrib/gis/geos/mutable_list.py309
-rw-r--r--parts/django/django/contrib/gis/geos/point.py135
-rw-r--r--parts/django/django/contrib/gis/geos/polygon.py166
-rw-r--r--parts/django/django/contrib/gis/geos/prepared.py30
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/__init__.py30
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/coordseq.py83
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/errcheck.py95
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/geom.py119
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/io.py242
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/misc.py28
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/predicates.py44
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/prepared.py25
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/threadsafe.py90
-rw-r--r--parts/django/django/contrib/gis/geos/prototypes/topology.py51
-rw-r--r--parts/django/django/contrib/gis/geos/tests/__init__.py25
-rw-r--r--parts/django/django/contrib/gis/geos/tests/test_geos.py926
-rw-r--r--parts/django/django/contrib/gis/geos/tests/test_geos_mutation.py137
-rw-r--r--parts/django/django/contrib/gis/geos/tests/test_io.py112
-rw-r--r--parts/django/django/contrib/gis/geos/tests/test_mutable_list.py398
-rw-r--r--parts/django/django/contrib/gis/management/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/management/base.py15
-rw-r--r--parts/django/django/contrib/gis/management/commands/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/management/commands/inspectdb.py32
-rw-r--r--parts/django/django/contrib/gis/management/commands/ogrinspect.py122
-rw-r--r--parts/django/django/contrib/gis/maps/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/maps/google/__init__.py61
-rw-r--r--parts/django/django/contrib/gis/maps/google/gmap.py226
-rw-r--r--parts/django/django/contrib/gis/maps/google/overlays.py301
-rw-r--r--parts/django/django/contrib/gis/maps/google/zoom.py161
-rw-r--r--parts/django/django/contrib/gis/maps/openlayers/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/measure.py336
-rw-r--r--parts/django/django/contrib/gis/models.py9
-rw-r--r--parts/django/django/contrib/gis/shortcuts.py32
-rw-r--r--parts/django/django/contrib/gis/sitemaps/__init__.py4
-rw-r--r--parts/django/django/contrib/gis/sitemaps/georss.py53
-rw-r--r--parts/django/django/contrib/gis/sitemaps/kml.py63
-rw-r--r--parts/django/django/contrib/gis/sitemaps/views.py111
-rw-r--r--parts/django/django/contrib/gis/templates/gis/admin/openlayers.html37
-rw-r--r--parts/django/django/contrib/gis/templates/gis/admin/openlayers.js167
-rw-r--r--parts/django/django/contrib/gis/templates/gis/admin/osm.html2
-rw-r--r--parts/django/django/contrib/gis/templates/gis/admin/osm.js2
-rw-r--r--parts/django/django/contrib/gis/templates/gis/google/google-map.html12
-rw-r--r--parts/django/django/contrib/gis/templates/gis/google/google-map.js35
-rw-r--r--parts/django/django/contrib/gis/templates/gis/google/google-multi.js8
-rw-r--r--parts/django/django/contrib/gis/templates/gis/google/google-single.js2
-rw-r--r--parts/django/django/contrib/gis/templates/gis/kml/base.kml6
-rw-r--r--parts/django/django/contrib/gis/templates/gis/kml/placemarks.kml8
-rw-r--r--parts/django/django/contrib/gis/templates/gis/sitemaps/geo_sitemap.xml17
-rw-r--r--parts/django/django/contrib/gis/tests/__init__.py141
-rw-r--r--parts/django/django/contrib/gis/tests/data/cities/cities.dbfbin0 -> 533 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/cities/cities.prj1
-rw-r--r--parts/django/django/contrib/gis/tests/data/cities/cities.shpbin0 -> 184 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/cities/cities.shxbin0 -> 124 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/counties/counties.dbfbin0 -> 3961 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/counties/counties.shpbin0 -> 37364 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/counties/counties.shxbin0 -> 292 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/geometries.json.gzbin0 -> 9100 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/interstates/interstates.dbfbin0 -> 412 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/interstates/interstates.prj1
-rw-r--r--parts/django/django/contrib/gis/tests/data/interstates/interstates.shpbin0 -> 892 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/interstates/interstates.shxbin0 -> 124 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_point/test_point.dbfbin0 -> 749 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_point/test_point.prj1
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_point/test_point.shpbin0 -> 240 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_point/test_point.shxbin0 -> 140 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_poly/test_poly.dbfbin0 -> 501 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_poly/test_poly.prj1
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shpbin0 -> 620 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shxbin0 -> 124 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.csv4
-rw-r--r--parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt7
-rw-r--r--parts/django/django/contrib/gis/tests/distapp/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/tests/distapp/models.py50
-rw-r--r--parts/django/django/contrib/gis/tests/distapp/tests.py358
-rw-r--r--parts/django/django/contrib/gis/tests/geo3d/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/tests/geo3d/models.py69
-rw-r--r--parts/django/django/contrib/gis/tests/geo3d/tests.py231
-rw-r--r--parts/django/django/contrib/gis/tests/geo3d/views.py1
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/feeds.py63
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/fixtures/initial_data.json.gzbin0 -> 131252 bytes
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/models.py45
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/sitemaps.py8
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/test_feeds.py78
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/test_regress.py37
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/test_sitemaps.py86
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/tests.py735
-rw-r--r--parts/django/django/contrib/gis/tests/geoapp/urls.py14
-rw-r--r--parts/django/django/contrib/gis/tests/geogapp/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/tests/geogapp/fixtures/initial_data.json98
-rw-r--r--parts/django/django/contrib/gis/tests/geogapp/models.py20
-rw-r--r--parts/django/django/contrib/gis/tests/geogapp/tests.py87
-rw-r--r--parts/django/django/contrib/gis/tests/layermap/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/tests/layermap/models.py66
-rw-r--r--parts/django/django/contrib/gis/tests/layermap/tests.py268
-rw-r--r--parts/django/django/contrib/gis/tests/relatedapp/__init__.py0
-rw-r--r--parts/django/django/contrib/gis/tests/relatedapp/models.py49
-rw-r--r--parts/django/django/contrib/gis/tests/relatedapp/tests.py284
-rw-r--r--parts/django/django/contrib/gis/tests/test_geoforms.py65
-rw-r--r--parts/django/django/contrib/gis/tests/test_geoip.py103
-rw-r--r--parts/django/django/contrib/gis/tests/test_measure.py336
-rw-r--r--parts/django/django/contrib/gis/tests/test_spatialrefsys.py113
-rw-r--r--parts/django/django/contrib/gis/tests/utils.py26
-rw-r--r--parts/django/django/contrib/gis/utils/__init__.py25
-rw-r--r--parts/django/django/contrib/gis/utils/geoip.py361
-rw-r--r--parts/django/django/contrib/gis/utils/layermapping.py602
-rw-r--r--parts/django/django/contrib/gis/utils/ogrinfo.py53
-rw-r--r--parts/django/django/contrib/gis/utils/ogrinspect.py225
-rw-r--r--parts/django/django/contrib/gis/utils/srs.py77
-rw-r--r--parts/django/django/contrib/gis/utils/wkt.py55
205 files changed, 19908 insertions, 0 deletions
diff --git a/parts/django/django/contrib/gis/__init__.py b/parts/django/django/contrib/gis/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/__init__.py
diff --git a/parts/django/django/contrib/gis/admin/__init__.py b/parts/django/django/contrib/gis/admin/__init__.py
new file mode 100644
index 0000000..2b56276
--- /dev/null
+++ b/parts/django/django/contrib/gis/admin/__init__.py
@@ -0,0 +1,12 @@
+# Getting the normal admin routines, classes, and `site` instance.
+from django.contrib.admin import autodiscover, site, AdminSite, ModelAdmin, StackedInline, TabularInline, HORIZONTAL, VERTICAL
+
+# Geographic admin options classes and widgets.
+from django.contrib.gis.admin.options import GeoModelAdmin
+from django.contrib.gis.admin.widgets import OpenLayersWidget
+
+try:
+ from django.contrib.gis.admin.options import OSMGeoAdmin
+ HAS_OSM = True
+except ImportError:
+ HAS_OSM = False
diff --git a/parts/django/django/contrib/gis/admin/options.py b/parts/django/django/contrib/gis/admin/options.py
new file mode 100644
index 0000000..1814933
--- /dev/null
+++ b/parts/django/django/contrib/gis/admin/options.py
@@ -0,0 +1,124 @@
+from django.conf import settings
+from django.contrib.admin import ModelAdmin
+from django.contrib.gis.admin.widgets import OpenLayersWidget
+from django.contrib.gis.gdal import OGRGeomType
+from django.contrib.gis.db import models
+
+class GeoModelAdmin(ModelAdmin):
+ """
+ The administration options class for Geographic models. Map settings
+ may be overloaded from their defaults to create custom maps.
+ """
+ # The default map settings that may be overloaded -- still subject
+ # to API changes.
+ default_lon = 0
+ default_lat = 0
+ default_zoom = 4
+ display_wkt = False
+ display_srid = False
+ extra_js = []
+ num_zoom = 18
+ max_zoom = False
+ min_zoom = False
+ units = False
+ max_resolution = False
+ max_extent = False
+ modifiable = True
+ mouse_position = True
+ scale_text = True
+ layerswitcher = True
+ scrollable = True
+ map_width = 600
+ map_height = 400
+ map_srid = 4326
+ map_template = 'gis/admin/openlayers.html'
+ openlayers_url = 'http://openlayers.org/api/2.8/OpenLayers.js'
+ point_zoom = num_zoom - 6
+ wms_url = 'http://labs.metacarta.com/wms/vmap0'
+ wms_layer = 'basic'
+ wms_name = 'OpenLayers WMS'
+ debug = False
+ widget = OpenLayersWidget
+
+ def _media(self):
+ "Injects OpenLayers JavaScript into the admin."
+ media = super(GeoModelAdmin, self)._media()
+ media.add_js([self.openlayers_url])
+ media.add_js(self.extra_js)
+ return media
+ media = property(_media)
+
+ def formfield_for_dbfield(self, db_field, **kwargs):
+ """
+ Overloaded from ModelAdmin so that an OpenLayersWidget is used
+ for viewing/editing GeometryFields.
+ """
+ if isinstance(db_field, models.GeometryField):
+ request = kwargs.pop('request', None)
+ # Setting the widget with the newly defined widget.
+ kwargs['widget'] = self.get_map_widget(db_field)
+ return db_field.formfield(**kwargs)
+ else:
+ return super(GeoModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
+
+ def get_map_widget(self, db_field):
+ """
+ Returns a subclass of the OpenLayersWidget (or whatever was specified
+ in the `widget` attribute) using the settings from the attributes set
+ in this class.
+ """
+ is_collection = db_field.geom_type in ('MULTIPOINT', 'MULTILINESTRING', 'MULTIPOLYGON', 'GEOMETRYCOLLECTION')
+ if is_collection:
+ if db_field.geom_type == 'GEOMETRYCOLLECTION': collection_type = 'Any'
+ else: collection_type = OGRGeomType(db_field.geom_type.replace('MULTI', ''))
+ else:
+ collection_type = 'None'
+
+ class OLMap(self.widget):
+ template = self.map_template
+ geom_type = db_field.geom_type
+ params = {'default_lon' : self.default_lon,
+ 'default_lat' : self.default_lat,
+ 'default_zoom' : self.default_zoom,
+ 'display_wkt' : self.debug or self.display_wkt,
+ 'geom_type' : OGRGeomType(db_field.geom_type),
+ 'field_name' : db_field.name,
+ 'is_collection' : is_collection,
+ 'scrollable' : self.scrollable,
+ 'layerswitcher' : self.layerswitcher,
+ 'collection_type' : collection_type,
+ 'is_linestring' : db_field.geom_type in ('LINESTRING', 'MULTILINESTRING'),
+ 'is_polygon' : db_field.geom_type in ('POLYGON', 'MULTIPOLYGON'),
+ 'is_point' : db_field.geom_type in ('POINT', 'MULTIPOINT'),
+ 'num_zoom' : self.num_zoom,
+ 'max_zoom' : self.max_zoom,
+ 'min_zoom' : self.min_zoom,
+ 'units' : self.units, #likely shoud get from object
+ 'max_resolution' : self.max_resolution,
+ 'max_extent' : self.max_extent,
+ 'modifiable' : self.modifiable,
+ 'mouse_position' : self.mouse_position,
+ 'scale_text' : self.scale_text,
+ 'map_width' : self.map_width,
+ 'map_height' : self.map_height,
+ 'point_zoom' : self.point_zoom,
+ 'srid' : self.map_srid,
+ 'display_srid' : self.display_srid,
+ 'wms_url' : self.wms_url,
+ 'wms_layer' : self.wms_layer,
+ 'wms_name' : self.wms_name,
+ 'debug' : self.debug,
+ }
+ return OLMap
+
+from django.contrib.gis import gdal
+if gdal.HAS_GDAL:
+ class OSMGeoAdmin(GeoModelAdmin):
+ map_template = 'gis/admin/osm.html'
+ extra_js = ['http://openstreetmap.org/openlayers/OpenStreetMap.js']
+ num_zoom = 20
+ map_srid = 900913
+ max_extent = '-20037508,-20037508,20037508,20037508'
+ max_resolution = '156543.0339'
+ point_zoom = num_zoom - 6
+ units = 'm'
diff --git a/parts/django/django/contrib/gis/admin/widgets.py b/parts/django/django/contrib/gis/admin/widgets.py
new file mode 100644
index 0000000..be26261
--- /dev/null
+++ b/parts/django/django/contrib/gis/admin/widgets.py
@@ -0,0 +1,107 @@
+from django.conf import settings
+from django.contrib.gis.gdal import OGRException
+from django.contrib.gis.geos import GEOSGeometry, GEOSException
+from django.forms.widgets import Textarea
+from django.template import loader, Context
+from django.utils import translation
+
+# Creating a template context that contains Django settings
+# values needed by admin map templates.
+geo_context = Context({'ADMIN_MEDIA_PREFIX' : settings.ADMIN_MEDIA_PREFIX,
+ 'LANGUAGE_BIDI' : translation.get_language_bidi(),
+ })
+
+class OpenLayersWidget(Textarea):
+ """
+ Renders an OpenLayers map using the WKT of the geometry.
+ """
+ def render(self, name, value, attrs=None):
+ # Update the template parameters with any attributes passed in.
+ if attrs: self.params.update(attrs)
+
+ # Defaulting the WKT value to a blank string -- this
+ # will be tested in the JavaScript and the appropriate
+ # interface will be constructed.
+ self.params['wkt'] = ''
+
+ # If a string reaches here (via a validation error on another
+ # field) then just reconstruct the Geometry.
+ if isinstance(value, basestring):
+ try:
+ value = GEOSGeometry(value)
+ except (GEOSException, ValueError):
+ value = None
+
+ if value and value.geom_type.upper() != self.geom_type:
+ value = None
+
+ # Constructing the dictionary of the map options.
+ self.params['map_options'] = self.map_options()
+
+ # Constructing the JavaScript module name using the name of
+ # the GeometryField (passed in via the `attrs` keyword).
+ # Use the 'name' attr for the field name (rather than 'field')
+ self.params['name'] = name
+ # note: we must switch out dashes for underscores since js
+ # functions are created using the module variable
+ js_safe_name = self.params['name'].replace('-','_')
+ self.params['module'] = 'geodjango_%s' % js_safe_name
+
+ if value:
+ # Transforming the geometry to the projection used on the
+ # OpenLayers map.
+ srid = self.params['srid']
+ if value.srid != srid:
+ try:
+ ogr = value.ogr
+ ogr.transform(srid)
+ wkt = ogr.wkt
+ except OGRException:
+ wkt = ''
+ else:
+ wkt = value.wkt
+
+ # Setting the parameter WKT with that of the transformed
+ # geometry.
+ self.params['wkt'] = wkt
+
+ return loader.render_to_string(self.template, self.params,
+ context_instance=geo_context)
+
+ def map_options(self):
+ "Builds the map options hash for the OpenLayers template."
+
+ # JavaScript construction utilities for the Bounds and Projection.
+ def ol_bounds(extent):
+ return 'new OpenLayers.Bounds(%s)' % str(extent)
+ def ol_projection(srid):
+ return 'new OpenLayers.Projection("EPSG:%s")' % srid
+
+ # An array of the parameter name, the name of their OpenLayers
+ # counterpart, and the type of variable they are.
+ map_types = [('srid', 'projection', 'srid'),
+ ('display_srid', 'displayProjection', 'srid'),
+ ('units', 'units', str),
+ ('max_resolution', 'maxResolution', float),
+ ('max_extent', 'maxExtent', 'bounds'),
+ ('num_zoom', 'numZoomLevels', int),
+ ('max_zoom', 'maxZoomLevels', int),
+ ('min_zoom', 'minZoomLevel', int),
+ ]
+
+ # Building the map options hash.
+ map_options = {}
+ for param_name, js_name, option_type in map_types:
+ if self.params.get(param_name, False):
+ if option_type == 'srid':
+ value = ol_projection(self.params[param_name])
+ elif option_type == 'bounds':
+ value = ol_bounds(self.params[param_name])
+ elif option_type in (float, int):
+ value = self.params[param_name]
+ elif option_type in (str,):
+ value = '"%s"' % self.params[param_name]
+ else:
+ raise TypeError
+ map_options[js_name] = value
+ return map_options
diff --git a/parts/django/django/contrib/gis/db/__init__.py b/parts/django/django/contrib/gis/db/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/__init__.py
diff --git a/parts/django/django/contrib/gis/db/backend/__init__.py b/parts/django/django/contrib/gis/db/backend/__init__.py
new file mode 100644
index 0000000..72ebdfe
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backend/__init__.py
@@ -0,0 +1,11 @@
+from django.db import connection
+
+if hasattr(connection.ops, 'spatial_version'):
+ from warnings import warn
+ warn('The `django.contrib.gis.db.backend` module was refactored and '
+ 'renamed to `django.contrib.gis.db.backends` in 1.2. '
+ 'All functionality of `SpatialBackend` '
+ 'has been moved to the `ops` attribute of the spatial database '
+ 'backend. A `SpatialBackend` alias is provided here for '
+ 'backwards-compatibility, but will be removed in 1.3.')
+ SpatialBackend = connection.ops
diff --git a/parts/django/django/contrib/gis/db/backends/__init__.py b/parts/django/django/contrib/gis/db/backends/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/__init__.py
diff --git a/parts/django/django/contrib/gis/db/backends/adapter.py b/parts/django/django/contrib/gis/db/backends/adapter.py
new file mode 100644
index 0000000..9766ef0
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/adapter.py
@@ -0,0 +1,17 @@
+class WKTAdapter(object):
+ """
+ This provides an adaptor for Geometries sent to the
+ MySQL and Oracle database backends.
+ """
+ def __init__(self, geom):
+ self.wkt = geom.wkt
+ self.srid = geom.srid
+
+ def __eq__(self, other):
+ return self.wkt == other.wkt and self.srid == other.srid
+
+ def __str__(self):
+ return self.wkt
+
+ def prepare_database_save(self, unused):
+ return self
diff --git a/parts/django/django/contrib/gis/db/backends/base.py b/parts/django/django/contrib/gis/db/backends/base.py
new file mode 100644
index 0000000..0eaacae
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/base.py
@@ -0,0 +1,336 @@
+"""
+Base/mixin classes for the spatial backend database operations and the
+`SpatialRefSys` model the backend.
+"""
+import re
+from django.conf import settings
+from django.contrib.gis import gdal
+
+class BaseSpatialOperations(object):
+ """
+ This module holds the base `BaseSpatialBackend` object, which is
+ instantiated by each spatial database backend with the features
+ it has.
+ """
+ distance_functions = {}
+ geometry_functions = {}
+ geometry_operators = {}
+ geography_operators = {}
+ geography_functions = {}
+ gis_terms = {}
+ truncate_params = {}
+
+ # Quick booleans for the type of this spatial backend, and
+ # an attribute for the spatial database version tuple (if applicable)
+ postgis = False
+ spatialite = False
+ mysql = False
+ oracle = False
+ spatial_version = None
+
+ # How the geometry column should be selected.
+ select = None
+
+ # Does the spatial database have a geography type?
+ geography = False
+
+ area = False
+ centroid = False
+ difference = False
+ distance = False
+ distance_sphere = False
+ distance_spheroid = False
+ envelope = False
+ force_rhr = False
+ mem_size = False
+ bounding_circle = False
+ num_geom = False
+ num_points = False
+ perimeter = False
+ perimeter3d = False
+ point_on_surface = False
+ polygonize = False
+ reverse = False
+ scale = False
+ snap_to_grid = False
+ sym_difference = False
+ transform = False
+ translate = False
+ union = False
+
+ # Aggregates
+ collect = False
+ extent = False
+ extent3d = False
+ make_line = False
+ unionagg = False
+
+ # Serialization
+ geohash = False
+ geojson = False
+ gml = False
+ kml = False
+ svg = False
+
+ # Constructors
+ from_text = False
+ from_wkb = False
+
+ # Default conversion functions for aggregates; will be overridden if implemented
+ # for the spatial backend.
+ def convert_extent(self, box):
+ raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
+
+ def convert_extent3d(self, box):
+ raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
+
+ def convert_geom(self, geom_val, geom_field):
+ raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
+
+ # For quoting column values, rather than columns.
+ def geo_quote_name(self, name):
+ if isinstance(name, unicode):
+ name = name.encode('ascii')
+ return "'%s'" % name
+
+ # GeometryField operations
+ def geo_db_type(self, f):
+ """
+ Returns the database column type for the geometry field on
+ the spatial backend.
+ """
+ raise NotImplementedError
+
+ def get_distance(self, f, value, lookup_type):
+ """
+ Returns the distance parameters for the given geometry field,
+ lookup value, and lookup type.
+ """
+ raise NotImplementedError('Distance operations not available on this spatial backend.')
+
+ def get_geom_placeholder(self, f, value):
+ """
+ Returns the placeholder for the given geometry field with the given
+ value. Depending on the spatial backend, the placeholder may contain a
+ stored procedure call to the transformation function of the spatial
+ backend.
+ """
+ raise NotImplementedError
+
+ # Spatial SQL Construction
+ def spatial_aggregate_sql(self, agg):
+ raise NotImplementedError('Aggregate support not implemented for this spatial backend.')
+
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field):
+ raise NotImplmentedError
+
+ # Routines for getting the OGC-compliant models.
+ def geometry_columns(self):
+ raise NotImplementedError
+
+ def spatial_ref_sys(self):
+ raise NotImplementedError
+
+class SpatialRefSysMixin(object):
+ """
+ The SpatialRefSysMixin is a class used by the database-dependent
+ SpatialRefSys objects to reduce redundnant code.
+ """
+ # For pulling out the spheroid from the spatial reference string. This
+ # regular expression is used only if the user does not have GDAL installed.
+ # TODO: Flattening not used in all ellipsoids, could also be a minor axis,
+ # or 'b' parameter.
+ spheroid_regex = re.compile(r'.+SPHEROID\[\"(?P<name>.+)\",(?P<major>\d+(\.\d+)?),(?P<flattening>\d{3}\.\d+),')
+
+ # For pulling out the units on platforms w/o GDAL installed.
+ # TODO: Figure out how to pull out angular units of projected coordinate system and
+ # fix for LOCAL_CS types. GDAL should be highly recommended for performing
+ # distance queries.
+ units_regex = re.compile(r'.+UNIT ?\["(?P<unit_name>[\w \'\(\)]+)", ?(?P<unit>[\d\.]+)(,AUTHORITY\["(?P<unit_auth_name>[\w \'\(\)]+)","(?P<unit_auth_val>\d+)"\])?\]([\w ]+)?(,AUTHORITY\["(?P<auth_name>[\w \'\(\)]+)","(?P<auth_val>\d+)"\])?\]$')
+
+ @property
+ def srs(self):
+ """
+ Returns a GDAL SpatialReference object, if GDAL is installed.
+ """
+ if gdal.HAS_GDAL:
+ # TODO: Is caching really necessary here? Is complexity worth it?
+ if hasattr(self, '_srs'):
+ # Returning a clone of the cached SpatialReference object.
+ return self._srs.clone()
+ else:
+ # Attempting to cache a SpatialReference object.
+
+ # Trying to get from WKT first.
+ try:
+ self._srs = gdal.SpatialReference(self.wkt)
+ return self.srs
+ except Exception, msg:
+ pass
+
+ try:
+ self._srs = gdal.SpatialReference(self.proj4text)
+ return self.srs
+ except Exception, msg:
+ pass
+
+ raise Exception('Could not get OSR SpatialReference from WKT: %s\nError:\n%s' % (self.wkt, msg))
+ else:
+ raise Exception('GDAL is not installed.')
+
+ @property
+ def ellipsoid(self):
+ """
+ Returns a tuple of the ellipsoid parameters:
+ (semimajor axis, semiminor axis, and inverse flattening).
+ """
+ if gdal.HAS_GDAL:
+ return self.srs.ellipsoid
+ else:
+ m = self.spheroid_regex.match(self.wkt)
+ if m: return (float(m.group('major')), float(m.group('flattening')))
+ else: return None
+
+ @property
+ def name(self):
+ "Returns the projection name."
+ return self.srs.name
+
+ @property
+ def spheroid(self):
+ "Returns the spheroid name for this spatial reference."
+ return self.srs['spheroid']
+
+ @property
+ def datum(self):
+ "Returns the datum for this spatial reference."
+ return self.srs['datum']
+
+ @property
+ def projected(self):
+ "Is this Spatial Reference projected?"
+ if gdal.HAS_GDAL:
+ return self.srs.projected
+ else:
+ return self.wkt.startswith('PROJCS')
+
+ @property
+ def local(self):
+ "Is this Spatial Reference local?"
+ if gdal.HAS_GDAL:
+ return self.srs.local
+ else:
+ return self.wkt.startswith('LOCAL_CS')
+
+ @property
+ def geographic(self):
+ "Is this Spatial Reference geographic?"
+ if gdal.HAS_GDAL:
+ return self.srs.geographic
+ else:
+ return self.wkt.startswith('GEOGCS')
+
+ @property
+ def linear_name(self):
+ "Returns the linear units name."
+ if gdal.HAS_GDAL:
+ return self.srs.linear_name
+ elif self.geographic:
+ return None
+ else:
+ m = self.units_regex.match(self.wkt)
+ return m.group('unit_name')
+
+ @property
+ def linear_units(self):
+ "Returns the linear units."
+ if gdal.HAS_GDAL:
+ return self.srs.linear_units
+ elif self.geographic:
+ return None
+ else:
+ m = self.units_regex.match(self.wkt)
+ return m.group('unit')
+
+ @property
+ def angular_name(self):
+ "Returns the name of the angular units."
+ if gdal.HAS_GDAL:
+ return self.srs.angular_name
+ elif self.projected:
+ return None
+ else:
+ m = self.units_regex.match(self.wkt)
+ return m.group('unit_name')
+
+ @property
+ def angular_units(self):
+ "Returns the angular units."
+ if gdal.HAS_GDAL:
+ return self.srs.angular_units
+ elif self.projected:
+ return None
+ else:
+ m = self.units_regex.match(self.wkt)
+ return m.group('unit')
+
+ @property
+ def units(self):
+ "Returns a tuple of the units and the name."
+ if self.projected or self.local:
+ return (self.linear_units, self.linear_name)
+ elif self.geographic:
+ return (self.angular_units, self.angular_name)
+ else:
+ return (None, None)
+
+ @classmethod
+ def get_units(cls, wkt):
+ """
+ Class method used by GeometryField on initialization to
+ retrive the units on the given WKT, without having to use
+ any of the database fields.
+ """
+ if gdal.HAS_GDAL:
+ return gdal.SpatialReference(wkt).units
+ else:
+ m = cls.units_regex.match(wkt)
+ return m.group('unit'), m.group('unit_name')
+
+ @classmethod
+ def get_spheroid(cls, wkt, string=True):
+ """
+ Class method used by GeometryField on initialization to
+ retrieve the `SPHEROID[..]` parameters from the given WKT.
+ """
+ if gdal.HAS_GDAL:
+ srs = gdal.SpatialReference(wkt)
+ sphere_params = srs.ellipsoid
+ sphere_name = srs['spheroid']
+ else:
+ m = cls.spheroid_regex.match(wkt)
+ if m:
+ sphere_params = (float(m.group('major')), float(m.group('flattening')))
+ sphere_name = m.group('name')
+ else:
+ return None
+
+ if not string:
+ return sphere_name, sphere_params
+ else:
+ # `string` parameter used to place in format acceptable by PostGIS
+ if len(sphere_params) == 3:
+ radius, flattening = sphere_params[0], sphere_params[2]
+ else:
+ radius, flattening = sphere_params
+ return 'SPHEROID["%s",%s,%s]' % (sphere_name, radius, flattening)
+
+ def __unicode__(self):
+ """
+ Returns the string representation. If GDAL is installed,
+ it will be 'pretty' OGC WKT.
+ """
+ try:
+ return unicode(self.srs)
+ except:
+ return unicode(self.wkt)
diff --git a/parts/django/django/contrib/gis/db/backends/mysql/__init__.py b/parts/django/django/contrib/gis/db/backends/mysql/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/mysql/__init__.py
diff --git a/parts/django/django/contrib/gis/db/backends/mysql/base.py b/parts/django/django/contrib/gis/db/backends/mysql/base.py
new file mode 100644
index 0000000..7f0fc48
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/mysql/base.py
@@ -0,0 +1,13 @@
+from django.db.backends.mysql.base import *
+from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper
+from django.contrib.gis.db.backends.mysql.creation import MySQLCreation
+from django.contrib.gis.db.backends.mysql.introspection import MySQLIntrospection
+from django.contrib.gis.db.backends.mysql.operations import MySQLOperations
+
+class DatabaseWrapper(MySQLDatabaseWrapper):
+
+ def __init__(self, *args, **kwargs):
+ super(DatabaseWrapper, self).__init__(*args, **kwargs)
+ self.creation = MySQLCreation(self)
+ self.ops = MySQLOperations()
+ self.introspection = MySQLIntrospection(self)
diff --git a/parts/django/django/contrib/gis/db/backends/mysql/creation.py b/parts/django/django/contrib/gis/db/backends/mysql/creation.py
new file mode 100644
index 0000000..dda77ea
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/mysql/creation.py
@@ -0,0 +1,18 @@
+from django.db.backends.mysql.creation import DatabaseCreation
+
+class MySQLCreation(DatabaseCreation):
+
+ def sql_indexes_for_field(self, model, f, style):
+ from django.contrib.gis.db.models.fields import GeometryField
+ output = super(MySQLCreation, self).sql_indexes_for_field(model, f, style)
+
+ if isinstance(f, GeometryField) and f.spatial_index:
+ qn = self.connection.ops.quote_name
+ db_table = model._meta.db_table
+ idx_name = '%s_%s_id' % (db_table, f.column)
+ output.append(style.SQL_KEYWORD('CREATE SPATIAL INDEX ') +
+ style.SQL_TABLE(qn(idx_name)) +
+ style.SQL_KEYWORD(' ON ') +
+ style.SQL_TABLE(qn(db_table)) + '(' +
+ style.SQL_FIELD(qn(f.column)) + ');')
+ return output
diff --git a/parts/django/django/contrib/gis/db/backends/mysql/introspection.py b/parts/django/django/contrib/gis/db/backends/mysql/introspection.py
new file mode 100644
index 0000000..59d0f62
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/mysql/introspection.py
@@ -0,0 +1,32 @@
+from MySQLdb.constants import FIELD_TYPE
+
+from django.contrib.gis.gdal import OGRGeomType
+from django.db.backends.mysql.introspection import DatabaseIntrospection
+
+class MySQLIntrospection(DatabaseIntrospection):
+ # Updating the data_types_reverse dictionary with the appropriate
+ # type for Geometry fields.
+ data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
+ data_types_reverse[FIELD_TYPE.GEOMETRY] = 'GeometryField'
+
+ def get_geometry_type(self, table_name, geo_col):
+ cursor = self.connection.cursor()
+ try:
+ # In order to get the specific geometry type of the field,
+ # we introspect on the table definition using `DESCRIBE`.
+ cursor.execute('DESCRIBE %s' %
+ self.connection.ops.quote_name(table_name))
+ # Increment over description info until we get to the geometry
+ # column.
+ for column, typ, null, key, default, extra in cursor.fetchall():
+ if column == geo_col:
+ # Using OGRGeomType to convert from OGC name to Django field.
+ # MySQL does not support 3D or SRIDs, so the field params
+ # are empty.
+ field_type = OGRGeomType(typ).django
+ field_params = {}
+ break
+ finally:
+ cursor.close()
+
+ return field_type, field_params
diff --git a/parts/django/django/contrib/gis/db/backends/mysql/operations.py b/parts/django/django/contrib/gis/db/backends/mysql/operations.py
new file mode 100644
index 0000000..1653636
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/mysql/operations.py
@@ -0,0 +1,65 @@
+from django.db.backends.mysql.base import DatabaseOperations
+
+from django.contrib.gis.db.backends.adapter import WKTAdapter
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+
+class MySQLOperations(DatabaseOperations, BaseSpatialOperations):
+
+ compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+ mysql = True
+ name = 'mysql'
+ select = 'AsText(%s)'
+ from_wkb = 'GeomFromWKB'
+ from_text = 'GeomFromText'
+
+ Adapter = WKTAdapter
+ Adaptor = Adapter # Backwards-compatibility alias.
+
+ geometry_functions = {
+ 'bbcontains' : 'MBRContains', # For consistency w/PostGIS API
+ 'bboverlaps' : 'MBROverlaps', # .. ..
+ 'contained' : 'MBRWithin', # .. ..
+ 'contains' : 'MBRContains',
+ 'disjoint' : 'MBRDisjoint',
+ 'equals' : 'MBREqual',
+ 'exact' : 'MBREqual',
+ 'intersects' : 'MBRIntersects',
+ 'overlaps' : 'MBROverlaps',
+ 'same_as' : 'MBREqual',
+ 'touches' : 'MBRTouches',
+ 'within' : 'MBRWithin',
+ }
+
+ gis_terms = dict([(term, None) for term in geometry_functions.keys() + ['isnull']])
+
+ def geo_db_type(self, f):
+ return f.geom_type
+
+ def get_geom_placeholder(self, value, srid):
+ """
+ The placeholder here has to include MySQL's WKT constructor. Because
+ MySQL does not support spatial transformations, there is no need to
+ modify the placeholder based on the contents of the given value.
+ """
+ if hasattr(value, 'expression'):
+ placeholder = '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+ else:
+ placeholder = '%s(%%s)' % self.from_text
+ return placeholder
+
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+ alias, col, db_type = lvalue
+
+ geo_col = '%s.%s' % (qn(alias), qn(col))
+
+ lookup_info = self.geometry_functions.get(lookup_type, False)
+ if lookup_info:
+ return "%s(%s, %s)" % (lookup_info, geo_col,
+ self.get_geom_placeholder(value, field.srid))
+
+ # TODO: Is this really necessary? MySQL can't handle NULL geometries
+ # in its spatial indexes anyways.
+ if lookup_type == 'isnull':
+ return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/__init__.py b/parts/django/django/contrib/gis/db/backends/oracle/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/__init__.py
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/adapter.py b/parts/django/django/contrib/gis/db/backends/oracle/adapter.py
new file mode 100644
index 0000000..ea340d9
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/adapter.py
@@ -0,0 +1,5 @@
+from cx_Oracle import CLOB
+from django.contrib.gis.db.backends.adapter import WKTAdapter
+
+class OracleSpatialAdapter(WKTAdapter):
+ input_size = CLOB
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/base.py b/parts/django/django/contrib/gis/db/backends/oracle/base.py
new file mode 100644
index 0000000..398b3d3
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/base.py
@@ -0,0 +1,12 @@
+from django.db.backends.oracle.base import *
+from django.db.backends.oracle.base import DatabaseWrapper as OracleDatabaseWrapper
+from django.contrib.gis.db.backends.oracle.creation import OracleCreation
+from django.contrib.gis.db.backends.oracle.introspection import OracleIntrospection
+from django.contrib.gis.db.backends.oracle.operations import OracleOperations
+
+class DatabaseWrapper(OracleDatabaseWrapper):
+ def __init__(self, *args, **kwargs):
+ super(DatabaseWrapper, self).__init__(*args, **kwargs)
+ self.ops = OracleOperations(self)
+ self.creation = OracleCreation(self)
+ self.introspection = OracleIntrospection(self)
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/compiler.py b/parts/django/django/contrib/gis/db/backends/oracle/compiler.py
new file mode 100644
index 0000000..f0eb5ca
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/compiler.py
@@ -0,0 +1,44 @@
+from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler
+from django.db.backends.oracle import compiler
+
+SQLCompiler = compiler.SQLCompiler
+
+class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler):
+ pass
+
+class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
+ def placeholder(self, field, val):
+ if field is None:
+ # A field value of None means the value is raw.
+ return val
+ elif hasattr(field, 'get_placeholder'):
+ # Some fields (e.g. geo fields) need special munging before
+ # they can be inserted.
+ ph = field.get_placeholder(val, self.connection)
+ if ph == 'NULL':
+ # If the placeholder returned is 'NULL', then we need to
+ # to remove None from the Query parameters. Specifically,
+ # cx_Oracle will assume a CHAR type when a placeholder ('%s')
+ # is used for columns of MDSYS.SDO_GEOMETRY. Thus, we use
+ # 'NULL' for the value, and remove None from the query params.
+ # See also #10888.
+ param_idx = self.query.columns.index(field.column)
+ params = list(self.query.params)
+ params.pop(param_idx)
+ self.query.params = tuple(params)
+ return ph
+ else:
+ # Return the common case for the placeholder
+ return '%s'
+
+class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
+ pass
+
+class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler):
+ pass
+
+class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
+ pass
+
+class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
+ pass
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/creation.py b/parts/django/django/contrib/gis/db/backends/oracle/creation.py
new file mode 100644
index 0000000..043da91
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/creation.py
@@ -0,0 +1,42 @@
+from django.db.backends.oracle.creation import DatabaseCreation
+from django.db.backends.util import truncate_name
+
+class OracleCreation(DatabaseCreation):
+
+ def sql_indexes_for_field(self, model, f, style):
+ "Return any spatial index creation SQL for the field."
+ from django.contrib.gis.db.models.fields import GeometryField
+
+ output = super(OracleCreation, self).sql_indexes_for_field(model, f, style)
+
+ if isinstance(f, GeometryField):
+ gqn = self.connection.ops.geo_quote_name
+ qn = self.connection.ops.quote_name
+ db_table = model._meta.db_table
+
+ output.append(style.SQL_KEYWORD('INSERT INTO ') +
+ style.SQL_TABLE('USER_SDO_GEOM_METADATA') +
+ ' (%s, %s, %s, %s)\n ' % tuple(map(qn, ['TABLE_NAME', 'COLUMN_NAME', 'DIMINFO', 'SRID'])) +
+ style.SQL_KEYWORD(' VALUES ') + '(\n ' +
+ style.SQL_TABLE(gqn(db_table)) + ',\n ' +
+ style.SQL_FIELD(gqn(f.column)) + ',\n ' +
+ style.SQL_KEYWORD("MDSYS.SDO_DIM_ARRAY") + '(\n ' +
+ style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
+ ("('LONG', %s, %s, %s),\n " % (f._extent[0], f._extent[2], f._tolerance)) +
+ style.SQL_KEYWORD("MDSYS.SDO_DIM_ELEMENT") +
+ ("('LAT', %s, %s, %s)\n ),\n" % (f._extent[1], f._extent[3], f._tolerance)) +
+ ' %s\n );' % f.srid)
+
+ if f.spatial_index:
+ # Getting the index name, Oracle doesn't allow object
+ # names > 30 characters.
+ idx_name = truncate_name('%s_%s_id' % (db_table, f.column), 30)
+
+ output.append(style.SQL_KEYWORD('CREATE INDEX ') +
+ style.SQL_TABLE(qn(idx_name)) +
+ style.SQL_KEYWORD(' ON ') +
+ style.SQL_TABLE(qn(db_table)) + '(' +
+ style.SQL_FIELD(qn(f.column)) + ') ' +
+ style.SQL_KEYWORD('INDEXTYPE IS ') +
+ style.SQL_TABLE('MDSYS.SPATIAL_INDEX') + ';')
+ return output
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/introspection.py b/parts/django/django/contrib/gis/db/backends/oracle/introspection.py
new file mode 100644
index 0000000..58dd3f3
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/introspection.py
@@ -0,0 +1,39 @@
+import cx_Oracle
+from django.db.backends.oracle.introspection import DatabaseIntrospection
+
+class OracleIntrospection(DatabaseIntrospection):
+ # Associating any OBJECTVAR instances with GeometryField. Of course,
+ # this won't work right on Oracle objects that aren't MDSYS.SDO_GEOMETRY,
+ # but it is the only object type supported within Django anyways.
+ data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
+ data_types_reverse[cx_Oracle.OBJECT] = 'GeometryField'
+
+ def get_geometry_type(self, table_name, geo_col):
+ cursor = self.connection.cursor()
+ try:
+ # Querying USER_SDO_GEOM_METADATA to get the SRID and dimension information.
+ try:
+ cursor.execute('SELECT "DIMINFO", "SRID" FROM "USER_SDO_GEOM_METADATA" WHERE "TABLE_NAME"=%s AND "COLUMN_NAME"=%s',
+ (table_name.upper(), geo_col.upper()))
+ row = cursor.fetchone()
+ except Exception, msg:
+ raise Exception('Could not find entry in USER_SDO_GEOM_METADATA corresponding to "%s"."%s"\n'
+ 'Error message: %s.' % (table_name, geo_col, msg))
+
+ # TODO: Research way to find a more specific geometry field type for
+ # the column's contents.
+ field_type = 'GeometryField'
+
+ # Getting the field parameters.
+ field_params = {}
+ dim, srid = row
+ if srid != 4326:
+ field_params['srid'] = srid
+ # Length of object array ( SDO_DIM_ARRAY ) is number of dimensions.
+ dim = len(dim)
+ if dim != 2:
+ field_params['dim'] = dim
+ finally:
+ cursor.close()
+
+ return field_type, field_params
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/models.py b/parts/django/django/contrib/gis/db/backends/oracle/models.py
new file mode 100644
index 0000000..de757ff
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/models.py
@@ -0,0 +1,65 @@
+"""
+ The GeometryColumns and SpatialRefSys models for the Oracle spatial
+ backend.
+
+ It should be noted that Oracle Spatial does not have database tables
+ named according to the OGC standard, so the closest analogs are used.
+ For example, the `USER_SDO_GEOM_METADATA` is used for the GeometryColumns
+ model and the `SDO_COORD_REF_SYS` is used for the SpatialRefSys model.
+"""
+from django.contrib.gis.db import models
+from django.contrib.gis.db.models.fields import GeometryField
+from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+
+class GeometryColumns(models.Model):
+ "Maps to the Oracle USER_SDO_GEOM_METADATA table."
+ table_name = models.CharField(max_length=32)
+ column_name = models.CharField(max_length=1024)
+ srid = models.IntegerField(primary_key=True)
+ # TODO: Add support for `diminfo` column (type MDSYS.SDO_DIM_ARRAY).
+ class Meta:
+ db_table = 'USER_SDO_GEOM_METADATA'
+ managed = False
+
+ @classmethod
+ def table_name_col(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature table name.
+ """
+ return 'table_name'
+
+ @classmethod
+ def geom_col_name(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature geometry column.
+ """
+ return 'column_name'
+
+ def __unicode__(self):
+ return '%s - %s (SRID: %s)' % (self.table_name, self.column_name, self.srid)
+
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
+ "Maps to the Oracle MDSYS.CS_SRS table."
+ cs_name = models.CharField(max_length=68)
+ srid = models.IntegerField(primary_key=True)
+ auth_srid = models.IntegerField()
+ auth_name = models.CharField(max_length=256)
+ wktext = models.CharField(max_length=2046)
+ # Optional geometry representing the bounds of this coordinate
+ # system. By default, all are NULL in the table.
+ cs_bounds = models.PolygonField(null=True)
+ objects = models.GeoManager()
+
+ class Meta:
+ db_table = 'CS_SRS'
+ managed = False
+
+ @property
+ def wkt(self):
+ return self.wktext
+
+ @classmethod
+ def wkt_col(cls):
+ return 'wktext'
diff --git a/parts/django/django/contrib/gis/db/backends/oracle/operations.py b/parts/django/django/contrib/gis/db/backends/oracle/operations.py
new file mode 100644
index 0000000..97f7b6c
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/oracle/operations.py
@@ -0,0 +1,293 @@
+"""
+ This module contains the spatial lookup types, and the `get_geo_where_clause`
+ routine for Oracle Spatial.
+
+ Please note that WKT support is broken on the XE version, and thus
+ this backend will not work on such platforms. Specifically, XE lacks
+ support for an internal JVM, and Java libraries are required to use
+ the WKT constructors.
+"""
+import re
+from decimal import Decimal
+
+from django.db.backends.oracle.base import DatabaseOperations
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter
+from django.contrib.gis.db.backends.util import SpatialFunction
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Distance
+
+class SDOOperation(SpatialFunction):
+ "Base class for SDO* Oracle operations."
+ sql_template = "%(function)s(%(geo_col)s, %(geometry)s) %(operator)s '%(result)s'"
+
+ def __init__(self, func, **kwargs):
+ kwargs.setdefault('operator', '=')
+ kwargs.setdefault('result', 'TRUE')
+ super(SDOOperation, self).__init__(func, **kwargs)
+
+class SDODistance(SpatialFunction):
+ "Class for Distance queries."
+ sql_template = ('%(function)s(%(geo_col)s, %(geometry)s, %(tolerance)s) '
+ '%(operator)s %(result)s')
+ dist_func = 'SDO_GEOM.SDO_DISTANCE'
+ def __init__(self, op, tolerance=0.05):
+ super(SDODistance, self).__init__(self.dist_func,
+ tolerance=tolerance,
+ operator=op, result='%s')
+
+class SDODWithin(SpatialFunction):
+ dwithin_func = 'SDO_WITHIN_DISTANCE'
+ sql_template = "%(function)s(%(geo_col)s, %(geometry)s, %%s) = 'TRUE'"
+ def __init__(self):
+ super(SDODWithin, self).__init__(self.dwithin_func)
+
+class SDOGeomRelate(SpatialFunction):
+ "Class for using SDO_GEOM.RELATE."
+ relate_func = 'SDO_GEOM.RELATE'
+ sql_template = ("%(function)s(%(geo_col)s, '%(mask)s', %(geometry)s, "
+ "%(tolerance)s) %(operator)s '%(mask)s'")
+ def __init__(self, mask, tolerance=0.05):
+ # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
+ # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
+ super(SDOGeomRelate, self).__init__(self.relate_func, operator='=',
+ mask=mask, tolerance=tolerance)
+
+class SDORelate(SpatialFunction):
+ "Class for using SDO_RELATE."
+ masks = 'TOUCH|OVERLAPBDYDISJOINT|OVERLAPBDYINTERSECT|EQUAL|INSIDE|COVEREDBY|CONTAINS|COVERS|ANYINTERACT|ON'
+ mask_regex = re.compile(r'^(%s)(\+(%s))*$' % (masks, masks), re.I)
+ sql_template = "%(function)s(%(geo_col)s, %(geometry)s, 'mask=%(mask)s') = 'TRUE'"
+ relate_func = 'SDO_RELATE'
+ def __init__(self, mask):
+ if not self.mask_regex.match(mask):
+ raise ValueError('Invalid %s mask: "%s"' % (self.relate_func, mask))
+ super(SDORelate, self).__init__(self.relate_func, mask=mask)
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+
+class OracleOperations(DatabaseOperations, BaseSpatialOperations):
+ compiler_module = "django.contrib.gis.db.backends.oracle.compiler"
+
+ name = 'oracle'
+ oracle = True
+ valid_aggregates = dict([(a, None) for a in ('Union', 'Extent')])
+
+ Adapter = OracleSpatialAdapter
+ Adaptor = Adapter # Backwards-compatibility alias.
+
+ area = 'SDO_GEOM.SDO_AREA'
+ gml= 'SDO_UTIL.TO_GMLGEOMETRY'
+ centroid = 'SDO_GEOM.SDO_CENTROID'
+ difference = 'SDO_GEOM.SDO_DIFFERENCE'
+ distance = 'SDO_GEOM.SDO_DISTANCE'
+ extent= 'SDO_AGGR_MBR'
+ intersection= 'SDO_GEOM.SDO_INTERSECTION'
+ length = 'SDO_GEOM.SDO_LENGTH'
+ num_geom = 'SDO_UTIL.GETNUMELEM'
+ num_points = 'SDO_UTIL.GETNUMVERTICES'
+ perimeter = length
+ point_on_surface = 'SDO_GEOM.SDO_POINTONSURFACE'
+ reverse = 'SDO_UTIL.REVERSE_LINESTRING'
+ sym_difference = 'SDO_GEOM.SDO_XOR'
+ transform = 'SDO_CS.TRANSFORM'
+ union = 'SDO_GEOM.SDO_UNION'
+ unionagg = 'SDO_AGGR_UNION'
+
+ # We want to get SDO Geometries as WKT because it is much easier to
+ # instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
+ # However, this adversely affects performance (i.e., Java is called
+ # to convert to WKT on every query). If someone wishes to write a
+ # SDO_GEOMETRY(...) parser in Python, let me know =)
+ select = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
+
+ distance_functions = {
+ 'distance_gt' : (SDODistance('>'), dtypes),
+ 'distance_gte' : (SDODistance('>='), dtypes),
+ 'distance_lt' : (SDODistance('<'), dtypes),
+ 'distance_lte' : (SDODistance('<='), dtypes),
+ 'dwithin' : (SDODWithin(), dtypes),
+ }
+
+ geometry_functions = {
+ 'contains' : SDOOperation('SDO_CONTAINS'),
+ 'coveredby' : SDOOperation('SDO_COVEREDBY'),
+ 'covers' : SDOOperation('SDO_COVERS'),
+ 'disjoint' : SDOGeomRelate('DISJOINT'),
+ 'intersects' : SDOOperation('SDO_OVERLAPBDYINTERSECT'), # TODO: Is this really the same as ST_Intersects()?
+ 'equals' : SDOOperation('SDO_EQUAL'),
+ 'exact' : SDOOperation('SDO_EQUAL'),
+ 'overlaps' : SDOOperation('SDO_OVERLAPS'),
+ 'same_as' : SDOOperation('SDO_EQUAL'),
+ 'relate' : (SDORelate, basestring), # Oracle uses a different syntax, e.g., 'mask=inside+touch'
+ 'touches' : SDOOperation('SDO_TOUCH'),
+ 'within' : SDOOperation('SDO_INSIDE'),
+ }
+ geometry_functions.update(distance_functions)
+
+ gis_terms = ['isnull']
+ gis_terms += geometry_functions.keys()
+ gis_terms = dict([(term, None) for term in gis_terms])
+
+ truncate_params = {'relate' : None}
+
+ def __init__(self, connection):
+ super(OracleOperations, self).__init__()
+ self.connection = connection
+
+ def convert_extent(self, clob):
+ if clob:
+ # Generally, Oracle returns a polygon for the extent -- however,
+ # it can return a single point if there's only one Point in the
+ # table.
+ ext_geom = Geometry(clob.read())
+ gtype = str(ext_geom.geom_type)
+ if gtype == 'Polygon':
+ # Construct the 4-tuple from the coordinates in the polygon.
+ shell = ext_geom.shell
+ ll, ur = shell[0][:2], shell[2][:2]
+ elif gtype == 'Point':
+ ll = ext_geom.coords[:2]
+ ur = ll
+ else:
+ raise Exception('Unexpected geometry type returned for extent: %s' % gtype)
+ xmin, ymin = ll
+ xmax, ymax = ur
+ return (xmin, ymin, xmax, ymax)
+ else:
+ return None
+
+ def convert_geom(self, clob, geo_field):
+ if clob:
+ return Geometry(clob.read(), geo_field.srid)
+ else:
+ return None
+
+ def geo_db_type(self, f):
+ """
+ Returns the geometry database type for Oracle. Unlike other spatial
+ backends, no stored procedure is necessary and it's the same for all
+ geometry types.
+ """
+ return 'MDSYS.SDO_GEOMETRY'
+
+ def get_distance(self, f, value, lookup_type):
+ """
+ Returns the distance parameters given the value and the lookup type.
+ On Oracle, geometry columns with a geodetic coordinate system behave
+ implicitly like a geography column, and thus meters will be used as
+ the distance parameter on them.
+ """
+ if not value:
+ return []
+ value = value[0]
+ if isinstance(value, Distance):
+ if f.geodetic(self.connection):
+ dist_param = value.m
+ else:
+ dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+ else:
+ dist_param = value
+
+ # dwithin lookups on oracle require a special string parameter
+ # that starts with "distance=".
+ if lookup_type == 'dwithin':
+ dist_param = 'distance=%s' % dist_param
+
+ return [dist_param]
+
+ def get_geom_placeholder(self, f, value):
+ """
+ Provides a proper substitution value for Geometries that are not in the
+ SRID of the field. Specifically, this routine will substitute in the
+ SDO_CS.TRANSFORM() function call.
+ """
+ if value is None:
+ return 'NULL'
+
+ def transform_value(val, srid):
+ return val.srid != srid
+
+ if hasattr(value, 'expression'):
+ if transform_value(value, f.srid):
+ placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
+ else:
+ placeholder = '%s'
+ # No geometry value used for F expression, substitue in
+ # the column name instead.
+ return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+ else:
+ if transform_value(value, f.srid):
+ return '%s(SDO_GEOMETRY(%%s, %s), %s)' % (self.transform, value.srid, f.srid)
+ else:
+ return 'SDO_GEOMETRY(%%s, %s)' % f.srid
+
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+ "Returns the SQL WHERE clause for use in Oracle spatial SQL construction."
+ alias, col, db_type = lvalue
+
+ # Getting the quoted table name as `geo_col`.
+ geo_col = '%s.%s' % (qn(alias), qn(col))
+
+ # See if a Oracle Geometry function matches the lookup type next
+ lookup_info = self.geometry_functions.get(lookup_type, False)
+ if lookup_info:
+ # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+ # 'dwithin' lookup types.
+ if isinstance(lookup_info, tuple):
+ # First element of tuple is lookup type, second element is the type
+ # of the expected argument (e.g., str, float)
+ sdo_op, arg_type = lookup_info
+ geom = value[0]
+
+ # Ensuring that a tuple _value_ was passed in from the user
+ if not isinstance(value, tuple):
+ raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+ if len(value) != 2:
+ raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
+
+ # Ensuring the argument type matches what we expect.
+ if not isinstance(value[1], arg_type):
+ raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+ if lookup_type == 'relate':
+ # The SDORelate class handles construction for these queries,
+ # and verifies the mask argument.
+ return sdo_op(value[1]).as_sql(geo_col, self.get_geom_placeholder(field, geom))
+ else:
+ # Otherwise, just call the `as_sql` method on the SDOOperation instance.
+ return sdo_op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
+ else:
+ # Lookup info is a SDOOperation instance, whose `as_sql` method returns
+ # the SQL necessary for the geometry function call. For example:
+ # SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
+ return lookup_info.as_sql(geo_col, self.get_geom_placeholder(field, value))
+ elif lookup_type == 'isnull':
+ # Handling 'isnull' lookup type
+ return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+ def spatial_aggregate_sql(self, agg):
+ """
+ Returns the spatial aggregate SQL template and function for the
+ given Aggregate instance.
+ """
+ agg_name = agg.__class__.__name__.lower()
+ if agg_name == 'union' : agg_name += 'agg'
+ if agg.is_extent:
+ sql_template = '%(function)s(%(field)s)'
+ else:
+ sql_template = '%(function)s(SDOAGGRTYPE(%(field)s,%(tolerance)s))'
+ sql_function = getattr(self, agg_name)
+ return self.select % sql_template, sql_function
+
+ # Routines for getting the OGC-compliant models.
+ def geometry_columns(self):
+ from django.contrib.gis.db.backends.oracle.models import GeometryColumns
+ return GeometryColumns
+
+ def spatial_ref_sys(self):
+ from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
+ return SpatialRefSys
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/__init__.py b/parts/django/django/contrib/gis/db/backends/postgis/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/__init__.py
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/adapter.py b/parts/django/django/contrib/gis/db/backends/postgis/adapter.py
new file mode 100644
index 0000000..3f8603e
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/adapter.py
@@ -0,0 +1,35 @@
+"""
+ This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
+"""
+
+from psycopg2 import Binary
+from psycopg2.extensions import ISQLQuote
+
+class PostGISAdapter(object):
+ def __init__(self, geom):
+ "Initializes on the geometry."
+ # Getting the WKB (in string form, to allow easy pickling of
+ # the adaptor) and the SRID from the geometry.
+ self.ewkb = str(geom.ewkb)
+ self.srid = geom.srid
+
+ def __conform__(self, proto):
+ # Does the given protocol conform to what Psycopg2 expects?
+ if proto == ISQLQuote:
+ return self
+ else:
+ raise Exception('Error implementing psycopg2 protocol. Is psycopg2 installed?')
+
+ def __eq__(self, other):
+ return (self.ewkb == other.ewkb) and (self.srid == other.srid)
+
+ def __str__(self):
+ return self.getquoted()
+
+ def getquoted(self):
+ "Returns a properly quoted string for use in PostgreSQL/PostGIS."
+ # Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
+ return 'ST_GeomFromEWKB(E%s)' % Binary(self.ewkb)
+
+ def prepare_database_save(self, unused):
+ return self
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/base.py b/parts/django/django/contrib/gis/db/backends/postgis/base.py
new file mode 100644
index 0000000..634a7d5
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/base.py
@@ -0,0 +1,12 @@
+from django.db.backends.postgresql_psycopg2.base import *
+from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as Psycopg2DatabaseWrapper
+from django.contrib.gis.db.backends.postgis.creation import PostGISCreation
+from django.contrib.gis.db.backends.postgis.introspection import PostGISIntrospection
+from django.contrib.gis.db.backends.postgis.operations import PostGISOperations
+
+class DatabaseWrapper(Psycopg2DatabaseWrapper):
+ def __init__(self, *args, **kwargs):
+ super(DatabaseWrapper, self).__init__(*args, **kwargs)
+ self.creation = PostGISCreation(self)
+ self.ops = PostGISOperations(self)
+ self.introspection = PostGISIntrospection(self)
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/creation.py b/parts/django/django/contrib/gis/db/backends/postgis/creation.py
new file mode 100644
index 0000000..e14c792
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/creation.py
@@ -0,0 +1,60 @@
+from django.conf import settings
+from django.db.backends.postgresql.creation import DatabaseCreation
+
+class PostGISCreation(DatabaseCreation):
+ geom_index_type = 'GIST'
+ geom_index_opts = 'GIST_GEOMETRY_OPS'
+
+ def sql_indexes_for_field(self, model, f, style):
+ "Return any spatial index creation SQL for the field."
+ from django.contrib.gis.db.models.fields import GeometryField
+
+ output = super(PostGISCreation, self).sql_indexes_for_field(model, f, style)
+
+ if isinstance(f, GeometryField):
+ gqn = self.connection.ops.geo_quote_name
+ qn = self.connection.ops.quote_name
+ db_table = model._meta.db_table
+
+ if f.geography:
+ # Geogrophy columns are created normally.
+ pass
+ else:
+ # Geometry columns are created by `AddGeometryColumn`
+ # stored procedure.
+ output.append(style.SQL_KEYWORD('SELECT ') +
+ style.SQL_TABLE('AddGeometryColumn') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(f.column)) + ', ' +
+ style.SQL_FIELD(str(f.srid)) + ', ' +
+ style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
+ style.SQL_KEYWORD(str(f.dim)) + ');')
+
+ if not f.null:
+ # Add a NOT NULL constraint to the field
+ output.append(style.SQL_KEYWORD('ALTER TABLE ') +
+ style.SQL_TABLE(qn(db_table)) +
+ style.SQL_KEYWORD(' ALTER ') +
+ style.SQL_FIELD(qn(f.column)) +
+ style.SQL_KEYWORD(' SET NOT NULL') + ';')
+
+
+ if f.spatial_index:
+ # Spatial indexes created the same way for both Geometry and
+ # Geography columns
+ if f.geography:
+ index_opts = ''
+ else:
+ index_opts = ' ' + style.SQL_KEYWORD(self.geom_index_opts)
+ output.append(style.SQL_KEYWORD('CREATE INDEX ') +
+ style.SQL_TABLE(qn('%s_%s_id' % (db_table, f.column))) +
+ style.SQL_KEYWORD(' ON ') +
+ style.SQL_TABLE(qn(db_table)) +
+ style.SQL_KEYWORD(' USING ') +
+ style.SQL_COLTYPE(self.geom_index_type) + ' ( ' +
+ style.SQL_FIELD(qn(f.column)) + index_opts + ' );')
+ return output
+
+ def sql_table_creation_suffix(self):
+ qn = self.connection.ops.quote_name
+ return ' TEMPLATE %s' % qn(getattr(settings, 'POSTGIS_TEMPLATE', 'template_postgis'))
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/introspection.py b/parts/django/django/contrib/gis/db/backends/postgis/introspection.py
new file mode 100644
index 0000000..7962d19
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/introspection.py
@@ -0,0 +1,95 @@
+from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
+from django.contrib.gis.gdal import OGRGeomType
+
+class GeoIntrospectionError(Exception):
+ pass
+
+class PostGISIntrospection(DatabaseIntrospection):
+ # Reverse dictionary for PostGIS geometry types not populated until
+ # introspection is actually performed.
+ postgis_types_reverse = {}
+
+ def get_postgis_types(self):
+ """
+ Returns a dictionary with keys that are the PostgreSQL object
+ identification integers for the PostGIS geometry and/or
+ geography types (if supported).
+ """
+ cursor = self.connection.cursor()
+ # The OID integers associated with the geometry type may
+ # be different across versions; hence, this is why we have
+ # to query the PostgreSQL pg_type table corresponding to the
+ # PostGIS custom data types.
+ oid_sql = 'SELECT "oid" FROM "pg_type" WHERE "typname" = %s'
+ try:
+ cursor.execute(oid_sql, ('geometry',))
+ GEOM_TYPE = cursor.fetchone()[0]
+ postgis_types = { GEOM_TYPE : 'GeometryField' }
+ if self.connection.ops.geography:
+ cursor.execute(oid_sql, ('geography',))
+ GEOG_TYPE = cursor.fetchone()[0]
+ # The value for the geography type is actually a tuple
+ # to pass in the `geography=True` keyword to the field
+ # definition.
+ postgis_types[GEOG_TYPE] = ('GeometryField', {'geography' : True})
+ finally:
+ cursor.close()
+
+ return postgis_types
+
+ def get_field_type(self, data_type, description):
+ if not self.postgis_types_reverse:
+ # If the PostGIS types reverse dictionary is not populated, do so
+ # now. In order to prevent unnecessary requests upon connection
+ # intialization, the `data_types_reverse` dictionary is not updated
+ # with the PostGIS custom types until introspection is actually
+ # performed -- in other words, when this function is called.
+ self.postgis_types_reverse = self.get_postgis_types()
+ self.data_types_reverse.update(self.postgis_types_reverse)
+ return super(PostGISIntrospection, self).get_field_type(data_type, description)
+
+ def get_geometry_type(self, table_name, geo_col):
+ """
+ The geometry type OID used by PostGIS does not indicate the particular
+ type of field that a geometry column is (e.g., whether it's a
+ PointField or a PolygonField). Thus, this routine queries the PostGIS
+ metadata tables to determine the geometry type,
+ """
+ cursor = self.connection.cursor()
+ try:
+ try:
+ # First seeing if this geometry column is in the `geometry_columns`
+ cursor.execute('SELECT "coord_dimension", "srid", "type" '
+ 'FROM "geometry_columns" '
+ 'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
+ (table_name, geo_col))
+ row = cursor.fetchone()
+ if not row: raise GeoIntrospectionError
+ except GeoIntrospectionError:
+ if self.connection.ops.geography:
+ cursor.execute('SELECT "coord_dimension", "srid", "type" '
+ 'FROM "geography_columns" '
+ 'WHERE "f_table_name"=%s AND "f_geography_column"=%s',
+ (table_name, geo_col))
+ row = cursor.fetchone()
+
+ if not row:
+ raise Exception('Could not find a geometry or geography column for "%s"."%s"' %
+ (table_name, geo_col))
+
+ # OGRGeomType does not require GDAL and makes it easy to convert
+ # from OGC geom type name to Django field.
+ field_type = OGRGeomType(row[2]).django
+
+ # Getting any GeometryField keyword arguments that are not the default.
+ dim = row[0]
+ srid = row[1]
+ field_params = {}
+ if srid != 4326:
+ field_params['srid'] = srid
+ if dim != 2:
+ field_params['dim'] = dim
+ finally:
+ cursor.close()
+
+ return field_type, field_params
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/models.py b/parts/django/django/contrib/gis/db/backends/postgis/models.py
new file mode 100644
index 0000000..a385983
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/models.py
@@ -0,0 +1,66 @@
+"""
+ The GeometryColumns and SpatialRefSys models for the PostGIS backend.
+"""
+from django.db import models
+from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+
+class GeometryColumns(models.Model):
+ """
+ The 'geometry_columns' table from the PostGIS. See the PostGIS
+ documentation at Ch. 4.2.2.
+ """
+ f_table_catalog = models.CharField(max_length=256)
+ f_table_schema = models.CharField(max_length=256)
+ f_table_name = models.CharField(max_length=256)
+ f_geometry_column = models.CharField(max_length=256)
+ coord_dimension = models.IntegerField()
+ srid = models.IntegerField(primary_key=True)
+ type = models.CharField(max_length=30)
+
+ class Meta:
+ db_table = 'geometry_columns'
+ managed = False
+
+ @classmethod
+ def table_name_col(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature table name.
+ """
+ return 'f_table_name'
+
+ @classmethod
+ def geom_col_name(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature geometry column.
+ """
+ return 'f_geometry_column'
+
+ def __unicode__(self):
+ return "%s.%s - %dD %s field (SRID: %d)" % \
+ (self.f_table_name, self.f_geometry_column,
+ self.coord_dimension, self.type, self.srid)
+
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
+ """
+ The 'spatial_ref_sys' table from PostGIS. See the PostGIS
+ documentaiton at Ch. 4.2.1.
+ """
+ srid = models.IntegerField(primary_key=True)
+ auth_name = models.CharField(max_length=256)
+ auth_srid = models.IntegerField()
+ srtext = models.CharField(max_length=2048)
+ proj4text = models.CharField(max_length=2048)
+
+ class Meta:
+ db_table = 'spatial_ref_sys'
+ managed = False
+
+ @property
+ def wkt(self):
+ return self.srtext
+
+ @classmethod
+ def wkt_col(cls):
+ return 'srtext'
diff --git a/parts/django/django/contrib/gis/db/backends/postgis/operations.py b/parts/django/django/contrib/gis/db/backends/postgis/operations.py
new file mode 100644
index 0000000..5affcf9
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/postgis/operations.py
@@ -0,0 +1,589 @@
+import re
+from decimal import Decimal
+
+from django.conf import settings
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
+from django.contrib.gis.db.backends.postgis.adapter import PostGISAdapter
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Distance
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.postgresql_psycopg2.base import DatabaseOperations
+from django.db.utils import DatabaseError
+
+#### Classes used in constructing PostGIS spatial SQL ####
+class PostGISOperator(SpatialOperation):
+ "For PostGIS operators (e.g. `&&`, `~`)."
+ def __init__(self, operator):
+ super(PostGISOperator, self).__init__(operator=operator)
+
+class PostGISFunction(SpatialFunction):
+ "For PostGIS function calls (e.g., `ST_Contains(table, geom)`)."
+ def __init__(self, prefix, function, **kwargs):
+ super(PostGISFunction, self).__init__(prefix + function, **kwargs)
+
+class PostGISFunctionParam(PostGISFunction):
+ "For PostGIS functions that take another parameter (e.g. DWithin, Relate)."
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
+
+class PostGISDistance(PostGISFunction):
+ "For PostGIS distance operations."
+ dist_func = 'Distance'
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
+
+ def __init__(self, prefix, operator):
+ super(PostGISDistance, self).__init__(prefix, self.dist_func,
+ operator=operator)
+
+class PostGISSpheroidDistance(PostGISFunction):
+ "For PostGIS spherical distance operations (using the spheroid)."
+ dist_func = 'distance_spheroid'
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s) %(operator)s %%s'
+ def __init__(self, prefix, operator):
+ # An extra parameter in `end_subst` is needed for the spheroid string.
+ super(PostGISSpheroidDistance, self).__init__(prefix, self.dist_func,
+ operator=operator)
+
+class PostGISSphereDistance(PostGISDistance):
+ "For PostGIS spherical distance operations."
+ dist_func = 'distance_sphere'
+
+class PostGISRelate(PostGISFunctionParam):
+ "For PostGIS Relate(<geom>, <pattern>) calls."
+ pattern_regex = re.compile(r'^[012TF\*]{9}$')
+ def __init__(self, prefix, pattern):
+ if not self.pattern_regex.match(pattern):
+ raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
+ super(PostGISRelate, self).__init__(prefix, 'Relate')
+
+
+class PostGISOperations(DatabaseOperations, BaseSpatialOperations):
+ compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+ name = 'postgis'
+ postgis = True
+ version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
+ valid_aggregates = dict([(k, None) for k in
+ ('Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union')])
+
+ Adapter = PostGISAdapter
+ Adaptor = Adapter # Backwards-compatibility alias.
+
+ def __init__(self, connection):
+ super(PostGISOperations, self).__init__(connection)
+
+ # Trying to get the PostGIS version because the function
+ # signatures will depend on the version used. The cost
+ # here is a database query to determine the version, which
+ # can be mitigated by setting `POSTGIS_VERSION` with a 3-tuple
+ # comprising user-supplied values for the major, minor, and
+ # subminor revision of PostGIS.
+ try:
+ if hasattr(settings, 'POSTGIS_VERSION'):
+ vtup = settings.POSTGIS_VERSION
+ if len(vtup) == 3:
+ # The user-supplied PostGIS version.
+ version = vtup
+ else:
+ # This was the old documented way, but it's stupid to
+ # include the string.
+ version = vtup[1:4]
+ else:
+ vtup = self.postgis_version_tuple()
+ version = vtup[1:]
+
+ # Getting the prefix -- even though we don't officially support
+ # PostGIS 1.2 anymore, keeping it anyway in case a prefix change
+ # for something else is necessary.
+ if version >= (1, 2, 2):
+ prefix = 'ST_'
+ else:
+ prefix = ''
+
+ self.geom_func_prefix = prefix
+ self.spatial_version = version
+ except DatabaseError:
+ raise ImproperlyConfigured('Cannot determine PostGIS version for database "%s". '
+ 'GeoDjango requires at least PostGIS version 1.3. '
+ 'Was the database created from a spatial database '
+ 'template?' % self.connection.settings_dict['NAME']
+ )
+ except Exception, e:
+ # TODO: Raise helpful exceptions as they become known.
+ raise
+
+ # PostGIS-specific operators. The commented descriptions of these
+ # operators come from Section 7.6 of the PostGIS 1.4 documentation.
+ self.geometry_operators = {
+ # The "&<" operator returns true if A's bounding box overlaps or
+ # is to the left of B's bounding box.
+ 'overlaps_left' : PostGISOperator('&<'),
+ # The "&>" operator returns true if A's bounding box overlaps or
+ # is to the right of B's bounding box.
+ 'overlaps_right' : PostGISOperator('&>'),
+ # The "<<" operator returns true if A's bounding box is strictly
+ # to the left of B's bounding box.
+ 'left' : PostGISOperator('<<'),
+ # The ">>" operator returns true if A's bounding box is strictly
+ # to the right of B's bounding box.
+ 'right' : PostGISOperator('>>'),
+ # The "&<|" operator returns true if A's bounding box overlaps or
+ # is below B's bounding box.
+ 'overlaps_below' : PostGISOperator('&<|'),
+ # The "|&>" operator returns true if A's bounding box overlaps or
+ # is above B's bounding box.
+ 'overlaps_above' : PostGISOperator('|&>'),
+ # The "<<|" operator returns true if A's bounding box is strictly
+ # below B's bounding box.
+ 'strictly_below' : PostGISOperator('<<|'),
+ # The "|>>" operator returns true if A's bounding box is strictly
+ # above B's bounding box.
+ 'strictly_above' : PostGISOperator('|>>'),
+ # The "~=" operator is the "same as" operator. It tests actual
+ # geometric equality of two features. So if A and B are the same feature,
+ # vertex-by-vertex, the operator returns true.
+ 'same_as' : PostGISOperator('~='),
+ 'exact' : PostGISOperator('~='),
+ # The "@" operator returns true if A's bounding box is completely contained
+ # by B's bounding box.
+ 'contained' : PostGISOperator('@'),
+ # The "~" operator returns true if A's bounding box completely contains
+ # by B's bounding box.
+ 'bbcontains' : PostGISOperator('~'),
+ # The "&&" operator returns true if A's bounding box overlaps
+ # B's bounding box.
+ 'bboverlaps' : PostGISOperator('&&'),
+ }
+
+ self.geometry_functions = {
+ 'equals' : PostGISFunction(prefix, 'Equals'),
+ 'disjoint' : PostGISFunction(prefix, 'Disjoint'),
+ 'touches' : PostGISFunction(prefix, 'Touches'),
+ 'crosses' : PostGISFunction(prefix, 'Crosses'),
+ 'within' : PostGISFunction(prefix, 'Within'),
+ 'overlaps' : PostGISFunction(prefix, 'Overlaps'),
+ 'contains' : PostGISFunction(prefix, 'Contains'),
+ 'intersects' : PostGISFunction(prefix, 'Intersects'),
+ 'relate' : (PostGISRelate, basestring),
+ }
+
+ # Valid distance types and substitutions
+ dtypes = (Decimal, Distance, float, int, long)
+ def get_dist_ops(operator):
+ "Returns operations for both regular and spherical distances."
+ return {'cartesian' : PostGISDistance(prefix, operator),
+ 'sphere' : PostGISSphereDistance(prefix, operator),
+ 'spheroid' : PostGISSpheroidDistance(prefix, operator),
+ }
+ self.distance_functions = {
+ 'distance_gt' : (get_dist_ops('>'), dtypes),
+ 'distance_gte' : (get_dist_ops('>='), dtypes),
+ 'distance_lt' : (get_dist_ops('<'), dtypes),
+ 'distance_lte' : (get_dist_ops('<='), dtypes),
+ }
+
+ # Versions 1.2.2+ have KML serialization support.
+ if version < (1, 2, 2):
+ ASKML = False
+ else:
+ ASKML = 'ST_AsKML'
+ self.geometry_functions.update(
+ {'coveredby' : PostGISFunction(prefix, 'CoveredBy'),
+ 'covers' : PostGISFunction(prefix, 'Covers'),
+ })
+ self.distance_functions['dwithin'] = (PostGISFunctionParam(prefix, 'DWithin'), dtypes)
+
+ # Adding the distance functions to the geometries lookup.
+ self.geometry_functions.update(self.distance_functions)
+
+ # The union aggregate and topology operation use the same signature
+ # in versions 1.3+.
+ if version < (1, 3, 0):
+ UNIONAGG = 'GeomUnion'
+ UNION = 'Union'
+ MAKELINE = False
+ else:
+ UNIONAGG = 'ST_Union'
+ UNION = 'ST_Union'
+ MAKELINE = 'ST_MakeLine'
+
+ # Only PostGIS versions 1.3.4+ have GeoJSON serialization support.
+ if version < (1, 3, 4):
+ GEOJSON = False
+ else:
+ GEOJSON = prefix + 'AsGeoJson'
+
+ # ST_ContainsProperly ST_MakeLine, and ST_GeoHash added in 1.4.
+ if version >= (1, 4, 0):
+ GEOHASH = 'ST_GeoHash'
+ BOUNDINGCIRCLE = 'ST_MinimumBoundingCircle'
+ self.geometry_functions['contains_properly'] = PostGISFunction(prefix, 'ContainsProperly')
+ else:
+ GEOHASH, BOUNDINGCIRCLE = False, False
+
+ # Geography type support added in 1.5.
+ if version >= (1, 5, 0):
+ self.geography = True
+ # Only a subset of the operators and functions are available
+ # for the geography type.
+ self.geography_functions = self.distance_functions.copy()
+ self.geography_functions.update({
+ 'coveredby' : self.geometry_functions['coveredby'],
+ 'covers' : self.geometry_functions['covers'],
+ 'intersects' : self.geometry_functions['intersects'],
+ })
+ self.geography_operators = {
+ 'bboverlaps' : PostGISOperator('&&'),
+ }
+
+ # Creating a dictionary lookup of all GIS terms for PostGIS.
+ gis_terms = ['isnull']
+ gis_terms += self.geometry_operators.keys()
+ gis_terms += self.geometry_functions.keys()
+ self.gis_terms = dict([(term, None) for term in gis_terms])
+
+ self.area = prefix + 'Area'
+ self.bounding_circle = BOUNDINGCIRCLE
+ self.centroid = prefix + 'Centroid'
+ self.collect = prefix + 'Collect'
+ self.difference = prefix + 'Difference'
+ self.distance = prefix + 'Distance'
+ self.distance_sphere = prefix + 'distance_sphere'
+ self.distance_spheroid = prefix + 'distance_spheroid'
+ self.envelope = prefix + 'Envelope'
+ self.extent = prefix + 'Extent'
+ self.extent3d = prefix + 'Extent3D'
+ self.force_rhr = prefix + 'ForceRHR'
+ self.geohash = GEOHASH
+ self.geojson = GEOJSON
+ self.gml = prefix + 'AsGML'
+ self.intersection = prefix + 'Intersection'
+ self.kml = ASKML
+ self.length = prefix + 'Length'
+ self.length3d = prefix + 'Length3D'
+ self.length_spheroid = prefix + 'length_spheroid'
+ self.makeline = MAKELINE
+ self.mem_size = prefix + 'mem_size'
+ self.num_geom = prefix + 'NumGeometries'
+ self.num_points =prefix + 'npoints'
+ self.perimeter = prefix + 'Perimeter'
+ self.perimeter3d = prefix + 'Perimeter3D'
+ self.point_on_surface = prefix + 'PointOnSurface'
+ self.polygonize = prefix + 'Polygonize'
+ self.reverse = prefix + 'Reverse'
+ self.scale = prefix + 'Scale'
+ self.snap_to_grid = prefix + 'SnapToGrid'
+ self.svg = prefix + 'AsSVG'
+ self.sym_difference = prefix + 'SymDifference'
+ self.transform = prefix + 'Transform'
+ self.translate = prefix + 'Translate'
+ self.union = UNION
+ self.unionagg = UNIONAGG
+
+ def check_aggregate_support(self, aggregate):
+ """
+ Checks if the given aggregate name is supported (that is, if it's
+ in `self.valid_aggregates`).
+ """
+ agg_name = aggregate.__class__.__name__
+ return agg_name in self.valid_aggregates
+
+ def convert_extent(self, box):
+ """
+ Returns a 4-tuple extent for the `Extent` aggregate by converting
+ the bounding box text returned by PostGIS (`box` argument), for
+ example: "BOX(-90.0 30.0, -85.0 40.0)".
+ """
+ ll, ur = box[4:-1].split(',')
+ xmin, ymin = map(float, ll.split())
+ xmax, ymax = map(float, ur.split())
+ return (xmin, ymin, xmax, ymax)
+
+ def convert_extent3d(self, box3d):
+ """
+ Returns a 6-tuple extent for the `Extent3D` aggregate by converting
+ the 3d bounding-box text returnded by PostGIS (`box3d` argument), for
+ example: "BOX3D(-90.0 30.0 1, -85.0 40.0 2)".
+ """
+ ll, ur = box3d[6:-1].split(',')
+ xmin, ymin, zmin = map(float, ll.split())
+ xmax, ymax, zmax = map(float, ur.split())
+ return (xmin, ymin, zmin, xmax, ymax, zmax)
+
+ def convert_geom(self, hex, geo_field):
+ """
+ Converts the geometry returned from PostGIS aggretates.
+ """
+ if hex:
+ return Geometry(hex)
+ else:
+ return None
+
+ def geo_db_type(self, f):
+ """
+ Return the database field type for the given geometry field.
+ Typically this is `None` because geometry columns are added via
+ the `AddGeometryColumn` stored procedure, unless the field
+ has been specified to be of geography type instead.
+ """
+ if f.geography:
+ if not self.geography:
+ raise NotImplementedError('PostGIS 1.5 required for geography column support.')
+
+ if f.srid != 4326:
+ raise NotImplementedError('PostGIS 1.5 supports geography columns '
+ 'only with an SRID of 4326.')
+
+ return 'geography(%s,%d)'% (f.geom_type, f.srid)
+ else:
+ return None
+
+ def get_distance(self, f, dist_val, lookup_type):
+ """
+ Retrieve the distance parameters for the given geometry field,
+ distance lookup value, and the distance lookup type.
+
+ This is the most complex implementation of the spatial backends due to
+ what is supported on geodetic geometry columns vs. what's available on
+ projected geometry columns. In addition, it has to take into account
+ the newly introduced geography column type introudced in PostGIS 1.5.
+ """
+ # Getting the distance parameter and any options.
+ if len(dist_val) == 1:
+ value, option = dist_val[0], None
+ else:
+ value, option = dist_val
+
+ # Shorthand boolean flags.
+ geodetic = f.geodetic(self.connection)
+ geography = f.geography and self.geography
+
+ if isinstance(value, Distance):
+ if geography:
+ dist_param = value.m
+ elif geodetic:
+ if lookup_type == 'dwithin':
+ raise ValueError('Only numeric values of degree units are '
+ 'allowed on geographic DWithin queries.')
+ dist_param = value.m
+ else:
+ dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+ else:
+ # Assuming the distance is in the units of the field.
+ dist_param = value
+
+ if (not geography and geodetic and lookup_type != 'dwithin'
+ and option == 'spheroid'):
+ # using distance_spheroid requires the spheroid of the field as
+ # a parameter.
+ return [f._spheroid, dist_param]
+ else:
+ return [dist_param]
+
+ def get_geom_placeholder(self, f, value):
+ """
+ Provides a proper substitution value for Geometries that are not in the
+ SRID of the field. Specifically, this routine will substitute in the
+ ST_Transform() function call.
+ """
+ if value is None or value.srid == f.srid:
+ placeholder = '%s'
+ else:
+ # Adding Transform() to the SQL placeholder.
+ placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
+
+ if hasattr(value, 'expression'):
+ # If this is an F expression, then we don't really want
+ # a placeholder and instead substitute in the column
+ # of the expression.
+ placeholder = placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+
+ return placeholder
+
+ def _get_postgis_func(self, func):
+ """
+ Helper routine for calling PostGIS functions and returning their result.
+ """
+ cursor = self.connection._cursor()
+ try:
+ try:
+ cursor.execute('SELECT %s()' % func)
+ row = cursor.fetchone()
+ except:
+ # Responsibility of callers to perform error handling.
+ raise
+ finally:
+ # Close out the connection. See #9437.
+ self.connection.close()
+ return row[0]
+
+ def postgis_geos_version(self):
+ "Returns the version of the GEOS library used with PostGIS."
+ return self._get_postgis_func('postgis_geos_version')
+
+ def postgis_lib_version(self):
+ "Returns the version number of the PostGIS library used with PostgreSQL."
+ return self._get_postgis_func('postgis_lib_version')
+
+ def postgis_proj_version(self):
+ "Returns the version of the PROJ.4 library used with PostGIS."
+ return self._get_postgis_func('postgis_proj_version')
+
+ def postgis_version(self):
+ "Returns PostGIS version number and compile-time options."
+ return self._get_postgis_func('postgis_version')
+
+ def postgis_full_version(self):
+ "Returns PostGIS version number and compile-time options."
+ return self._get_postgis_func('postgis_full_version')
+
+ def postgis_version_tuple(self):
+ """
+ Returns the PostGIS version as a tuple (version string, major,
+ minor, subminor).
+ """
+ # Getting the PostGIS version
+ version = self.postgis_lib_version()
+ m = self.version_regex.match(version)
+
+ if m:
+ major = int(m.group('major'))
+ minor1 = int(m.group('minor1'))
+ minor2 = int(m.group('minor2'))
+ else:
+ raise Exception('Could not parse PostGIS version string: %s' % version)
+
+ return (version, major, minor1, minor2)
+
+ def proj_version_tuple(self):
+ """
+ Return the version of PROJ.4 used by PostGIS as a tuple of the
+ major, minor, and subminor release numbers.
+ """
+ proj_regex = re.compile(r'(\d+)\.(\d+)\.(\d+)')
+ proj_ver_str = self.postgis_proj_version()
+ m = proj_regex.search(proj_ver_str)
+ if m:
+ return tuple(map(int, [m.group(1), m.group(2), m.group(3)]))
+ else:
+ raise Exception('Could not determine PROJ.4 version from PostGIS.')
+
+ def num_params(self, lookup_type, num_param):
+ """
+ Helper routine that returns a boolean indicating whether the number of
+ parameters is correct for the lookup type.
+ """
+ def exactly_two(np): return np == 2
+ def two_to_three(np): return np >= 2 and np <=3
+ if (lookup_type in self.distance_functions and
+ lookup_type != 'dwithin'):
+ return two_to_three(num_param)
+ else:
+ return exactly_two(num_param)
+
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+ """
+ Constructs spatial SQL from the given lookup value tuple a
+ (alias, col, db_type), the lookup type string, lookup value, and
+ the geometry field.
+ """
+ alias, col, db_type = lvalue
+
+ # Getting the quoted geometry column.
+ geo_col = '%s.%s' % (qn(alias), qn(col))
+
+ if lookup_type in self.geometry_operators:
+ if field.geography and not lookup_type in self.geography_operators:
+ raise ValueError('PostGIS geography does not support the '
+ '"%s" lookup.' % lookup_type)
+ # Handling a PostGIS operator.
+ op = self.geometry_operators[lookup_type]
+ return op.as_sql(geo_col, self.get_geom_placeholder(field, value))
+ elif lookup_type in self.geometry_functions:
+ if field.geography and not lookup_type in self.geography_functions:
+ raise ValueError('PostGIS geography type does not support the '
+ '"%s" lookup.' % lookup_type)
+
+ # See if a PostGIS geometry function matches the lookup type.
+ tmp = self.geometry_functions[lookup_type]
+
+ # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+ # distance lookups.
+ if isinstance(tmp, tuple):
+ # First element of tuple is the PostGISOperation instance, and the
+ # second element is either the type or a tuple of acceptable types
+ # that may passed in as further parameters for the lookup type.
+ op, arg_type = tmp
+
+ # Ensuring that a tuple _value_ was passed in from the user
+ if not isinstance(value, (tuple, list)):
+ raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+
+ # Geometry is first element of lookup tuple.
+ geom = value[0]
+
+ # Number of valid tuple parameters depends on the lookup type.
+ nparams = len(value)
+ if not self.num_params(lookup_type, nparams):
+ raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
+
+ # Ensuring the argument type matches what we expect.
+ if not isinstance(value[1], arg_type):
+ raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+ # For lookup type `relate`, the op instance is not yet created (has
+ # to be instantiated here to check the pattern parameter).
+ if lookup_type == 'relate':
+ op = op(self.geom_func_prefix, value[1])
+ elif lookup_type in self.distance_functions and lookup_type != 'dwithin':
+ if not field.geography and field.geodetic(self.connection):
+ # Geodetic distances are only availble from Points to
+ # PointFields on PostGIS 1.4 and below.
+ if not self.connection.ops.geography:
+ if field.geom_type != 'POINT':
+ raise ValueError('PostGIS spherical operations are only valid on PointFields.')
+
+ if str(geom.geom_type) != 'Point':
+ raise ValueError('PostGIS geometry distance parameter is required to be of type Point.')
+
+ # Setting up the geodetic operation appropriately.
+ if nparams == 3 and value[2] == 'spheroid':
+ op = op['spheroid']
+ else:
+ op = op['sphere']
+ else:
+ op = op['cartesian']
+ else:
+ op = tmp
+ geom = value
+
+ # Calling the `as_sql` function on the operation instance.
+ return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
+
+ elif lookup_type == 'isnull':
+ # Handling 'isnull' lookup type
+ return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+ def spatial_aggregate_sql(self, agg):
+ """
+ Returns the spatial aggregate SQL template and function for the
+ given Aggregate instance.
+ """
+ agg_name = agg.__class__.__name__
+ if not self.check_aggregate_support(agg):
+ raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
+ agg_name = agg_name.lower()
+ if agg_name == 'union': agg_name += 'agg'
+ sql_template = '%(function)s(%(field)s)'
+ sql_function = getattr(self, agg_name)
+ return sql_template, sql_function
+
+ # Routines for getting the OGC-compliant models.
+ def geometry_columns(self):
+ from django.contrib.gis.db.backends.postgis.models import GeometryColumns
+ return GeometryColumns
+
+ def spatial_ref_sys(self):
+ from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
+ return SpatialRefSys
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/__init__.py b/parts/django/django/contrib/gis/db/backends/spatialite/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/__init__.py
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/adapter.py b/parts/django/django/contrib/gis/db/backends/spatialite/adapter.py
new file mode 100644
index 0000000..d8fefba
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/adapter.py
@@ -0,0 +1,8 @@
+from django.db.backends.sqlite3.base import Database
+from django.contrib.gis.db.backends.adapter import WKTAdapter
+
+class SpatiaLiteAdapter(WKTAdapter):
+ "SQLite adaptor for geometry objects."
+ def __conform__(self, protocol):
+ if protocol is Database.PrepareProtocol:
+ return str(self)
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/base.py b/parts/django/django/contrib/gis/db/backends/spatialite/base.py
new file mode 100644
index 0000000..729fc15
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/base.py
@@ -0,0 +1,77 @@
+from ctypes.util import find_library
+from django.conf import settings
+
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.sqlite3.base import *
+from django.db.backends.sqlite3.base import DatabaseWrapper as SqliteDatabaseWrapper, \
+ _sqlite_extract, _sqlite_date_trunc, _sqlite_regexp
+from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient
+from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation
+from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection
+from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations
+
+class DatabaseWrapper(SqliteDatabaseWrapper):
+ def __init__(self, *args, **kwargs):
+ # Before we get too far, make sure pysqlite 2.5+ is installed.
+ if Database.version_info < (2, 5, 0):
+ raise ImproperlyConfigured('Only versions of pysqlite 2.5+ are '
+ 'compatible with SpatiaLite and GeoDjango.')
+
+ # Trying to find the location of the SpatiaLite library.
+ # Here we are figuring out the path to the SpatiaLite library
+ # (`libspatialite`). If it's not in the system library path (e.g., it
+ # cannot be found by `ctypes.util.find_library`), then it may be set
+ # manually in the settings via the `SPATIALITE_LIBRARY_PATH` setting.
+ self.spatialite_lib = getattr(settings, 'SPATIALITE_LIBRARY_PATH',
+ find_library('spatialite'))
+ if not self.spatialite_lib:
+ raise ImproperlyConfigured('Unable to locate the SpatiaLite library. '
+ 'Make sure it is in your library path, or set '
+ 'SPATIALITE_LIBRARY_PATH in your settings.'
+ )
+ super(DatabaseWrapper, self).__init__(*args, **kwargs)
+ self.ops = SpatiaLiteOperations(self)
+ self.client = SpatiaLiteClient(self)
+ self.creation = SpatiaLiteCreation(self)
+ self.introspection = SpatiaLiteIntrospection(self)
+
+ def _cursor(self):
+ if self.connection is None:
+ ## The following is the same as in django.db.backends.sqlite3.base ##
+ settings_dict = self.settings_dict
+ if not settings_dict['NAME']:
+ raise ImproperlyConfigured("Please fill out the database NAME in the settings module before using the database.")
+ kwargs = {
+ 'database': settings_dict['NAME'],
+ 'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
+ }
+ kwargs.update(settings_dict['OPTIONS'])
+ self.connection = Database.connect(**kwargs)
+ # Register extract, date_trunc, and regexp functions.
+ self.connection.create_function("django_extract", 2, _sqlite_extract)
+ self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc)
+ self.connection.create_function("regexp", 2, _sqlite_regexp)
+ connection_created.send(sender=self.__class__, connection=self)
+
+ ## From here on, customized for GeoDjango ##
+
+ # Enabling extension loading on the SQLite connection.
+ try:
+ self.connection.enable_load_extension(True)
+ except AttributeError:
+ raise ImproperlyConfigured('The pysqlite library does not support C extension loading. '
+ 'Both SQLite and pysqlite must be configured to allow '
+ 'the loading of extensions to use SpatiaLite.'
+ )
+
+ # Loading the SpatiaLite library extension on the connection, and returning
+ # the created cursor.
+ cur = self.connection.cursor(factory=SQLiteCursorWrapper)
+ try:
+ cur.execute("SELECT load_extension(%s)", (self.spatialite_lib,))
+ except Exception, msg:
+ raise ImproperlyConfigured('Unable to load the SpatiaLite library extension '
+ '"%s" because: %s' % (self.spatialite_lib, msg))
+ return cur
+ else:
+ return self.connection.cursor(factory=SQLiteCursorWrapper)
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/client.py b/parts/django/django/contrib/gis/db/backends/spatialite/client.py
new file mode 100644
index 0000000..536065a
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/client.py
@@ -0,0 +1,5 @@
+from django.db.backends.sqlite3.client import DatabaseClient
+
+class SpatiaLiteClient(DatabaseClient):
+ executable_name = 'spatialite'
+
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/creation.py b/parts/django/django/contrib/gis/db/backends/spatialite/creation.py
new file mode 100644
index 0000000..cbe4a29
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/creation.py
@@ -0,0 +1,96 @@
+import os
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.core.management import call_command
+from django.db.backends.sqlite3.creation import DatabaseCreation
+
+class SpatiaLiteCreation(DatabaseCreation):
+
+ def create_test_db(self, verbosity=1, autoclobber=False):
+ """
+ Creates a test database, prompting the user for confirmation if the
+ database already exists. Returns the name of the test database created.
+
+ This method is overloaded to load up the SpatiaLite initialization
+ SQL prior to calling the `syncdb` command.
+ """
+ if verbosity >= 1:
+ print "Creating test database '%s'..." % self.connection.alias
+
+ test_database_name = self._create_test_db(verbosity, autoclobber)
+
+ self.connection.close()
+
+ self.connection.settings_dict["NAME"] = test_database_name
+ can_rollback = self._rollback_works()
+ self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
+ # Need to load the SpatiaLite initialization SQL before running `syncdb`.
+ self.load_spatialite_sql()
+ call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
+
+ if settings.CACHE_BACKEND.startswith('db://'):
+ from django.core.cache import parse_backend_uri
+ _, cache_name, _ = parse_backend_uri(settings.CACHE_BACKEND)
+ call_command('createcachetable', cache_name)
+
+ # Get a cursor (even though we don't need one yet). This has
+ # the side effect of initializing the test database.
+ cursor = self.connection.cursor()
+
+ return test_database_name
+
+ def sql_indexes_for_field(self, model, f, style):
+ "Return any spatial index creation SQL for the field."
+ from django.contrib.gis.db.models.fields import GeometryField
+
+ output = super(SpatiaLiteCreation, self).sql_indexes_for_field(model, f, style)
+
+ if isinstance(f, GeometryField):
+ gqn = self.connection.ops.geo_quote_name
+ qn = self.connection.ops.quote_name
+ db_table = model._meta.db_table
+
+ output.append(style.SQL_KEYWORD('SELECT ') +
+ style.SQL_TABLE('AddGeometryColumn') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(f.column)) + ', ' +
+ style.SQL_FIELD(str(f.srid)) + ', ' +
+ style.SQL_COLTYPE(gqn(f.geom_type)) + ', ' +
+ style.SQL_KEYWORD(str(f.dim)) + ', ' +
+ style.SQL_KEYWORD(str(int(not f.null))) +
+ ');')
+
+ if f.spatial_index:
+ output.append(style.SQL_KEYWORD('SELECT ') +
+ style.SQL_TABLE('CreateSpatialIndex') + '(' +
+ style.SQL_TABLE(gqn(db_table)) + ', ' +
+ style.SQL_FIELD(gqn(f.column)) + ');')
+
+ return output
+
+ def load_spatialite_sql(self):
+ """
+ This routine loads up the SpatiaLite SQL file.
+ """
+ # Getting the location of the SpatiaLite SQL file, and confirming
+ # it exists.
+ spatialite_sql = self.spatialite_init_file()
+ if not os.path.isfile(spatialite_sql):
+ raise ImproperlyConfigured('Could not find the required SpatiaLite initialization '
+ 'SQL file (necessary for testing): %s' % spatialite_sql)
+
+ # Opening up the SpatiaLite SQL initialization file and executing
+ # as a script.
+ sql_fh = open(spatialite_sql, 'r')
+ try:
+ cur = self.connection._cursor()
+ cur.executescript(sql_fh.read())
+ finally:
+ sql_fh.close()
+
+ def spatialite_init_file(self):
+ # SPATIALITE_SQL may be placed in settings to tell GeoDjango
+ # to use a specific path to the SpatiaLite initilization SQL.
+ return getattr(settings, 'SPATIALITE_SQL',
+ 'init_spatialite-%s.%s.sql' %
+ self.connection.ops.spatial_version[:2])
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/introspection.py b/parts/django/django/contrib/gis/db/backends/spatialite/introspection.py
new file mode 100644
index 0000000..1b5952c
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/introspection.py
@@ -0,0 +1,51 @@
+from django.contrib.gis.gdal import OGRGeomType
+from django.db.backends.sqlite3.introspection import DatabaseIntrospection, FlexibleFieldLookupDict
+
+class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict):
+ """
+ Sublcass that includes updates the `base_data_types_reverse` dict
+ for geometry field types.
+ """
+ base_data_types_reverse = FlexibleFieldLookupDict.base_data_types_reverse.copy()
+ base_data_types_reverse.update(
+ {'point' : 'GeometryField',
+ 'linestring' : 'GeometryField',
+ 'polygon' : 'GeometryField',
+ 'multipoint' : 'GeometryField',
+ 'multilinestring' : 'GeometryField',
+ 'multipolygon' : 'GeometryField',
+ 'geometrycollection' : 'GeometryField',
+ })
+
+class SpatiaLiteIntrospection(DatabaseIntrospection):
+ data_types_reverse = GeoFlexibleFieldLookupDict()
+
+ def get_geometry_type(self, table_name, geo_col):
+ cursor = self.connection.cursor()
+ try:
+ # Querying the `geometry_columns` table to get additional metadata.
+ cursor.execute('SELECT "coord_dimension", "srid", "type" '
+ 'FROM "geometry_columns" '
+ 'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
+ (table_name, geo_col))
+ row = cursor.fetchone()
+ if not row:
+ raise Exception('Could not find a geometry column for "%s"."%s"' %
+ (table_name, geo_col))
+
+ # OGRGeomType does not require GDAL and makes it easy to convert
+ # from OGC geom type name to Django field.
+ field_type = OGRGeomType(row[2]).django
+
+ # Getting any GeometryField keyword arguments that are not the default.
+ dim = row[0]
+ srid = row[1]
+ field_params = {}
+ if srid != 4326:
+ field_params['srid'] = srid
+ if isinstance(dim, basestring) and 'Z' in dim:
+ field_params['dim'] = 3
+ finally:
+ cursor.close()
+
+ return field_type, field_params
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/models.py b/parts/django/django/contrib/gis/db/backends/spatialite/models.py
new file mode 100644
index 0000000..684c5d8
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/models.py
@@ -0,0 +1,60 @@
+"""
+ The GeometryColumns and SpatialRefSys models for the SpatiaLite backend.
+"""
+from django.db import models
+from django.contrib.gis.db.backends.base import SpatialRefSysMixin
+
+class GeometryColumns(models.Model):
+ """
+ The 'geometry_columns' table from SpatiaLite.
+ """
+ f_table_name = models.CharField(max_length=256)
+ f_geometry_column = models.CharField(max_length=256)
+ type = models.CharField(max_length=30)
+ coord_dimension = models.IntegerField()
+ srid = models.IntegerField(primary_key=True)
+ spatial_index_enabled = models.IntegerField()
+
+ class Meta:
+ db_table = 'geometry_columns'
+ managed = False
+
+ @classmethod
+ def table_name_col(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature table name.
+ """
+ return 'f_table_name'
+
+ @classmethod
+ def geom_col_name(cls):
+ """
+ Returns the name of the metadata column used to store the
+ the feature geometry column.
+ """
+ return 'f_geometry_column'
+
+ def __unicode__(self):
+ return "%s.%s - %dD %s field (SRID: %d)" % \
+ (self.f_table_name, self.f_geometry_column,
+ self.coord_dimension, self.type, self.srid)
+
+class SpatialRefSys(models.Model, SpatialRefSysMixin):
+ """
+ The 'spatial_ref_sys' table from SpatiaLite.
+ """
+ srid = models.IntegerField(primary_key=True)
+ auth_name = models.CharField(max_length=256)
+ auth_srid = models.IntegerField()
+ ref_sys_name = models.CharField(max_length=256)
+ proj4text = models.CharField(max_length=2048)
+
+ @property
+ def wkt(self):
+ from django.contrib.gis.gdal import SpatialReference
+ return SpatialReference(self.proj4text).wkt
+
+ class Meta:
+ db_table = 'spatial_ref_sys'
+ managed = False
diff --git a/parts/django/django/contrib/gis/db/backends/spatialite/operations.py b/parts/django/django/contrib/gis/db/backends/spatialite/operations.py
new file mode 100644
index 0000000..e6f8409
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/spatialite/operations.py
@@ -0,0 +1,343 @@
+import re
+from decimal import Decimal
+
+from django.contrib.gis.db.backends.base import BaseSpatialOperations
+from django.contrib.gis.db.backends.util import SpatialOperation, SpatialFunction
+from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Distance
+from django.core.exceptions import ImproperlyConfigured
+from django.db.backends.sqlite3.base import DatabaseOperations
+from django.db.utils import DatabaseError
+
+class SpatiaLiteOperator(SpatialOperation):
+ "For SpatiaLite operators (e.g. `&&`, `~`)."
+ def __init__(self, operator):
+ super(SpatiaLiteOperator, self).__init__(operator=operator)
+
+class SpatiaLiteFunction(SpatialFunction):
+ "For SpatiaLite function calls."
+ def __init__(self, function, **kwargs):
+ super(SpatiaLiteFunction, self).__init__(function, **kwargs)
+
+class SpatiaLiteFunctionParam(SpatiaLiteFunction):
+ "For SpatiaLite functions that take another parameter."
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s, %%s)'
+
+class SpatiaLiteDistance(SpatiaLiteFunction):
+ "For SpatiaLite distance operations."
+ dist_func = 'Distance'
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s) %(operator)s %%s'
+
+ def __init__(self, operator):
+ super(SpatiaLiteDistance, self).__init__(self.dist_func,
+ operator=operator)
+
+class SpatiaLiteRelate(SpatiaLiteFunctionParam):
+ "For SpatiaLite Relate(<geom>, <pattern>) calls."
+ pattern_regex = re.compile(r'^[012TF\*]{9}$')
+ def __init__(self, pattern):
+ if not self.pattern_regex.match(pattern):
+ raise ValueError('Invalid intersection matrix pattern "%s".' % pattern)
+ super(SpatiaLiteRelate, self).__init__('Relate')
+
+# Valid distance types and substitutions
+dtypes = (Decimal, Distance, float, int, long)
+def get_dist_ops(operator):
+ "Returns operations for regular distances; spherical distances are not currently supported."
+ return (SpatiaLiteDistance(operator),)
+
+class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations):
+ compiler_module = 'django.contrib.gis.db.models.sql.compiler'
+ name = 'spatialite'
+ spatialite = True
+ version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
+ valid_aggregates = dict([(k, None) for k in ('Extent', 'Union')])
+
+ Adapter = SpatiaLiteAdapter
+ Adaptor = Adapter # Backwards-compatibility alias.
+
+ area = 'Area'
+ centroid = 'Centroid'
+ contained = 'MbrWithin'
+ difference = 'Difference'
+ distance = 'Distance'
+ envelope = 'Envelope'
+ intersection = 'Intersection'
+ length = 'GLength' # OpenGis defines Length, but this conflicts with an SQLite reserved keyword
+ num_geom = 'NumGeometries'
+ num_points = 'NumPoints'
+ point_on_surface = 'PointOnSurface'
+ scale = 'ScaleCoords'
+ svg = 'AsSVG'
+ sym_difference = 'SymDifference'
+ transform = 'Transform'
+ translate = 'ShiftCoords'
+ union = 'GUnion' # OpenGis defines Union, but this conflicts with an SQLite reserved keyword
+ unionagg = 'GUnion'
+
+ from_text = 'GeomFromText'
+ from_wkb = 'GeomFromWKB'
+ select = 'AsText(%s)'
+
+ geometry_functions = {
+ 'equals' : SpatiaLiteFunction('Equals'),
+ 'disjoint' : SpatiaLiteFunction('Disjoint'),
+ 'touches' : SpatiaLiteFunction('Touches'),
+ 'crosses' : SpatiaLiteFunction('Crosses'),
+ 'within' : SpatiaLiteFunction('Within'),
+ 'overlaps' : SpatiaLiteFunction('Overlaps'),
+ 'contains' : SpatiaLiteFunction('Contains'),
+ 'intersects' : SpatiaLiteFunction('Intersects'),
+ 'relate' : (SpatiaLiteRelate, basestring),
+ # Retruns true if B's bounding box completely contains A's bounding box.
+ 'contained' : SpatiaLiteFunction('MbrWithin'),
+ # Returns true if A's bounding box completely contains B's bounding box.
+ 'bbcontains' : SpatiaLiteFunction('MbrContains'),
+ # Returns true if A's bounding box overlaps B's bounding box.
+ 'bboverlaps' : SpatiaLiteFunction('MbrOverlaps'),
+ # These are implemented here as synonyms for Equals
+ 'same_as' : SpatiaLiteFunction('Equals'),
+ 'exact' : SpatiaLiteFunction('Equals'),
+ }
+
+ distance_functions = {
+ 'distance_gt' : (get_dist_ops('>'), dtypes),
+ 'distance_gte' : (get_dist_ops('>='), dtypes),
+ 'distance_lt' : (get_dist_ops('<'), dtypes),
+ 'distance_lte' : (get_dist_ops('<='), dtypes),
+ }
+ geometry_functions.update(distance_functions)
+
+ def __init__(self, connection):
+ super(DatabaseOperations, self).__init__()
+ self.connection = connection
+
+ # Determine the version of the SpatiaLite library.
+ try:
+ vtup = self.spatialite_version_tuple()
+ version = vtup[1:]
+ if version < (2, 3, 0):
+ raise ImproperlyConfigured('GeoDjango only supports SpatiaLite versions '
+ '2.3.0 and above')
+ self.spatial_version = version
+ except ImproperlyConfigured:
+ raise
+ except Exception, msg:
+ raise ImproperlyConfigured('Cannot determine the SpatiaLite version for the "%s" '
+ 'database (error was "%s"). Was the SpatiaLite initialization '
+ 'SQL loaded on this database?' %
+ (self.connection.settings_dict['NAME'], msg))
+
+ # Creating the GIS terms dictionary.
+ gis_terms = ['isnull']
+ gis_terms += self.geometry_functions.keys()
+ self.gis_terms = dict([(term, None) for term in gis_terms])
+
+ def check_aggregate_support(self, aggregate):
+ """
+ Checks if the given aggregate name is supported (that is, if it's
+ in `self.valid_aggregates`).
+ """
+ agg_name = aggregate.__class__.__name__
+ return agg_name in self.valid_aggregates
+
+ def convert_geom(self, wkt, geo_field):
+ """
+ Converts geometry WKT returned from a SpatiaLite aggregate.
+ """
+ if wkt:
+ return Geometry(wkt, geo_field.srid)
+ else:
+ return None
+
+ def geo_db_type(self, f):
+ """
+ Returns None because geometry columnas are added via the
+ `AddGeometryColumn` stored procedure on SpatiaLite.
+ """
+ return None
+
+ def get_distance(self, f, value, lookup_type):
+ """
+ Returns the distance parameters for the given geometry field,
+ lookup value, and lookup type. SpatiaLite only supports regular
+ cartesian-based queries (no spheroid/sphere calculations for point
+ geometries like PostGIS).
+ """
+ if not value:
+ return []
+ value = value[0]
+ if isinstance(value, Distance):
+ if f.geodetic(self.connection):
+ raise ValueError('SpatiaLite does not support distance queries on '
+ 'geometry fields with a geodetic coordinate system. '
+ 'Distance objects; use a numeric value of your '
+ 'distance in degrees instead.')
+ else:
+ dist_param = getattr(value, Distance.unit_attname(f.units_name(self.connection)))
+ else:
+ dist_param = value
+ return [dist_param]
+
+ def get_geom_placeholder(self, f, value):
+ """
+ Provides a proper substitution value for Geometries that are not in the
+ SRID of the field. Specifically, this routine will substitute in the
+ Transform() and GeomFromText() function call(s).
+ """
+ def transform_value(value, srid):
+ return not (value is None or value.srid == srid)
+ if hasattr(value, 'expression'):
+ if transform_value(value, f.srid):
+ placeholder = '%s(%%s, %s)' % (self.transform, f.srid)
+ else:
+ placeholder = '%s'
+ # No geometry value used for F expression, substitue in
+ # the column name instead.
+ return placeholder % '%s.%s' % tuple(map(self.quote_name, value.cols[value.expression]))
+ else:
+ if transform_value(value, f.srid):
+ # Adding Transform() to the SQL placeholder.
+ return '%s(%s(%%s,%s), %s)' % (self.transform, self.from_text, value.srid, f.srid)
+ else:
+ return '%s(%%s,%s)' % (self.from_text, f.srid)
+
+ def _get_spatialite_func(self, func):
+ """
+ Helper routine for calling SpatiaLite functions and returning
+ their result.
+ """
+ cursor = self.connection._cursor()
+ try:
+ try:
+ cursor.execute('SELECT %s' % func)
+ row = cursor.fetchone()
+ except:
+ # Responsibility of caller to perform error handling.
+ raise
+ finally:
+ cursor.close()
+ return row[0]
+
+ def geos_version(self):
+ "Returns the version of GEOS used by SpatiaLite as a string."
+ return self._get_spatialite_func('geos_version()')
+
+ def proj4_version(self):
+ "Returns the version of the PROJ.4 library used by SpatiaLite."
+ return self._get_spatialite_func('proj4_version()')
+
+ def spatialite_version(self):
+ "Returns the SpatiaLite library version as a string."
+ return self._get_spatialite_func('spatialite_version()')
+
+ def spatialite_version_tuple(self):
+ """
+ Returns the SpatiaLite version as a tuple (version string, major,
+ minor, subminor).
+ """
+ # Getting the SpatiaLite version.
+ try:
+ version = self.spatialite_version()
+ except DatabaseError:
+ # The `spatialite_version` function first appeared in version 2.3.1
+ # of SpatiaLite, so doing a fallback test for 2.3.0 (which is
+ # used by popular Debian/Ubuntu packages).
+ version = None
+ try:
+ tmp = self._get_spatialite_func("X(GeomFromText('POINT(1 1)'))")
+ if tmp == 1.0: version = '2.3.0'
+ except DatabaseError:
+ pass
+ # If no version string defined, then just re-raise the original
+ # exception.
+ if version is None: raise
+
+ m = self.version_regex.match(version)
+ if m:
+ major = int(m.group('major'))
+ minor1 = int(m.group('minor1'))
+ minor2 = int(m.group('minor2'))
+ else:
+ raise Exception('Could not parse SpatiaLite version string: %s' % version)
+
+ return (version, major, minor1, minor2)
+
+ def spatial_aggregate_sql(self, agg):
+ """
+ Returns the spatial aggregate SQL template and function for the
+ given Aggregate instance.
+ """
+ agg_name = agg.__class__.__name__
+ if not self.check_aggregate_support(agg):
+ raise NotImplementedError('%s spatial aggregate is not implmented for this backend.' % agg_name)
+ agg_name = agg_name.lower()
+ if agg_name == 'union': agg_name += 'agg'
+ sql_template = self.select % '%(function)s(%(field)s)'
+ sql_function = getattr(self, agg_name)
+ return sql_template, sql_function
+
+ def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn):
+ """
+ Returns the SpatiaLite-specific SQL for the given lookup value
+ [a tuple of (alias, column, db_type)], lookup type, lookup
+ value, the model field, and the quoting function.
+ """
+ alias, col, db_type = lvalue
+
+ # Getting the quoted field as `geo_col`.
+ geo_col = '%s.%s' % (qn(alias), qn(col))
+
+ if lookup_type in self.geometry_functions:
+ # See if a SpatiaLite geometry function matches the lookup type.
+ tmp = self.geometry_functions[lookup_type]
+
+ # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
+ # distance lookups.
+ if isinstance(tmp, tuple):
+ # First element of tuple is the SpatiaLiteOperation instance, and the
+ # second element is either the type or a tuple of acceptable types
+ # that may passed in as further parameters for the lookup type.
+ op, arg_type = tmp
+
+ # Ensuring that a tuple _value_ was passed in from the user
+ if not isinstance(value, (tuple, list)):
+ raise ValueError('Tuple required for `%s` lookup type.' % lookup_type)
+
+ # Geometry is first element of lookup tuple.
+ geom = value[0]
+
+ # Number of valid tuple parameters depends on the lookup type.
+ if len(value) != 2:
+ raise ValueError('Incorrect number of parameters given for `%s` lookup type.' % lookup_type)
+
+ # Ensuring the argument type matches what we expect.
+ if not isinstance(value[1], arg_type):
+ raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
+
+ # For lookup type `relate`, the op instance is not yet created (has
+ # to be instantiated here to check the pattern parameter).
+ if lookup_type == 'relate':
+ op = op(value[1])
+ elif lookup_type in self.distance_functions:
+ op = op[0]
+ else:
+ op = tmp
+ geom = value
+ # Calling the `as_sql` function on the operation instance.
+ return op.as_sql(geo_col, self.get_geom_placeholder(field, geom))
+ elif lookup_type == 'isnull':
+ # Handling 'isnull' lookup type
+ return "%s IS %sNULL" % (geo_col, (not value and 'NOT ' or ''))
+
+ raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
+
+ # Routines for getting the OGC-compliant models.
+ def geometry_columns(self):
+ from django.contrib.gis.db.backends.spatialite.models import GeometryColumns
+ return GeometryColumns
+
+ def spatial_ref_sys(self):
+ from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
+ return SpatialRefSys
diff --git a/parts/django/django/contrib/gis/db/backends/util.py b/parts/django/django/contrib/gis/db/backends/util.py
new file mode 100644
index 0000000..b50c8e2
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/backends/util.py
@@ -0,0 +1,56 @@
+"""
+A collection of utility routines and classes used by the spatial
+backends.
+"""
+
+def gqn(val):
+ """
+ The geographic quote name function; used for quoting tables and
+ geometries (they use single rather than the double quotes of the
+ backend quotename function).
+ """
+ if isinstance(val, basestring):
+ if isinstance(val, unicode): val = val.encode('ascii')
+ return "'%s'" % val
+ else:
+ return str(val)
+
+class SpatialOperation(object):
+ """
+ Base class for generating spatial SQL.
+ """
+ sql_template = '%(geo_col)s %(operator)s %(geometry)s'
+
+ def __init__(self, function='', operator='', result='', **kwargs):
+ self.function = function
+ self.operator = operator
+ self.result = result
+ self.extra = kwargs
+
+ def as_sql(self, geo_col, geometry='%s'):
+ return self.sql_template % self.params(geo_col, geometry)
+
+ def params(self, geo_col, geometry):
+ params = {'function' : self.function,
+ 'geo_col' : geo_col,
+ 'geometry' : geometry,
+ 'operator' : self.operator,
+ 'result' : self.result,
+ }
+ params.update(self.extra)
+ return params
+
+class SpatialFunction(SpatialOperation):
+ """
+ Base class for generating spatial SQL related to a function.
+ """
+ sql_template = '%(function)s(%(geo_col)s, %(geometry)s)'
+
+ def __init__(self, func, result='', operator='', **kwargs):
+ # Getting the function prefix.
+ default = {'function' : func,
+ 'operator' : operator,
+ 'result' : result
+ }
+ kwargs.update(default)
+ super(SpatialFunction, self).__init__(**kwargs)
diff --git a/parts/django/django/contrib/gis/db/models/__init__.py b/parts/django/django/contrib/gis/db/models/__init__.py
new file mode 100644
index 0000000..87e2b68
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/__init__.py
@@ -0,0 +1,14 @@
+# Want to get everything from the 'normal' models package.
+from django.db.models import *
+
+# Geographic aggregate functions
+from django.contrib.gis.db.models.aggregates import *
+
+# The GeoManager
+from django.contrib.gis.db.models.manager import GeoManager
+
+# The geographic-enabled fields.
+from django.contrib.gis.db.models.fields import \
+ GeometryField, PointField, LineStringField, PolygonField, \
+ MultiPointField, MultiLineStringField, MultiPolygonField, \
+ GeometryCollectionField
diff --git a/parts/django/django/contrib/gis/db/models/aggregates.py b/parts/django/django/contrib/gis/db/models/aggregates.py
new file mode 100644
index 0000000..cd26839
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/aggregates.py
@@ -0,0 +1,17 @@
+from django.db.models import Aggregate
+from django.contrib.gis.db.models.sql import GeomField
+
+class Collect(Aggregate):
+ name = 'Collect'
+
+class Extent(Aggregate):
+ name = 'Extent'
+
+class Extent3D(Aggregate):
+ name = 'Extent3D'
+
+class MakeLine(Aggregate):
+ name = 'MakeLine'
+
+class Union(Aggregate):
+ name = 'Union'
diff --git a/parts/django/django/contrib/gis/db/models/fields.py b/parts/django/django/contrib/gis/db/models/fields.py
new file mode 100644
index 0000000..2b16607
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/fields.py
@@ -0,0 +1,294 @@
+from django.db.models.fields import Field
+from django.db.models.sql.expressions import SQLEvaluator
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.gis import forms
+from django.contrib.gis.db.models.proxy import GeometryProxy
+from django.contrib.gis.geometry.backend import Geometry, GeometryException
+
+# Local cache of the spatial_ref_sys table, which holds SRID data for each
+# spatial database alias. This cache exists so that the database isn't queried
+# for SRID info each time a distance query is constructed.
+_srid_cache = {}
+
+def get_srid_info(srid, connection):
+ """
+ Returns the units, unit name, and spheroid WKT associated with the
+ given SRID from the `spatial_ref_sys` (or equivalent) spatial database
+ table for the given database connection. These results are cached.
+ """
+ global _srid_cache
+
+ try:
+ # The SpatialRefSys model for the spatial backend.
+ SpatialRefSys = connection.ops.spatial_ref_sys()
+ except NotImplementedError:
+ # No `spatial_ref_sys` table in spatial backend (e.g., MySQL).
+ return None, None, None
+
+ if not connection.alias in _srid_cache:
+ # Initialize SRID dictionary for database if it doesn't exist.
+ _srid_cache[connection.alias] = {}
+
+ if not srid in _srid_cache[connection.alias]:
+ # Use `SpatialRefSys` model to query for spatial reference info.
+ sr = SpatialRefSys.objects.using(connection.alias).get(srid=srid)
+ units, units_name = sr.units
+ spheroid = SpatialRefSys.get_spheroid(sr.wkt)
+ _srid_cache[connection.alias][srid] = (units, units_name, spheroid)
+
+ return _srid_cache[connection.alias][srid]
+
+class GeometryField(Field):
+ "The base GIS field -- maps to the OpenGIS Specification Geometry type."
+
+ # The OpenGIS Geometry name.
+ geom_type = 'GEOMETRY'
+
+ # Geodetic units.
+ geodetic_units = ('Decimal Degree', 'degree')
+
+ description = _("The base GIS field -- maps to the OpenGIS Specification Geometry type.")
+
+ def __init__(self, verbose_name=None, srid=4326, spatial_index=True, dim=2,
+ geography=False, **kwargs):
+ """
+ The initialization function for geometry fields. Takes the following
+ as keyword arguments:
+
+ srid:
+ The spatial reference system identifier, an OGC standard.
+ Defaults to 4326 (WGS84).
+
+ spatial_index:
+ Indicates whether to create a spatial index. Defaults to True.
+ Set this instead of 'db_index' for geographic fields since index
+ creation is different for geometry columns.
+
+ dim:
+ The number of dimensions for this geometry. Defaults to 2.
+
+ extent:
+ Customize the extent, in a 4-tuple of WGS 84 coordinates, for the
+ geometry field entry in the `USER_SDO_GEOM_METADATA` table. Defaults
+ to (-180.0, -90.0, 180.0, 90.0).
+
+ tolerance:
+ Define the tolerance, in meters, to use for the geometry field
+ entry in the `USER_SDO_GEOM_METADATA` table. Defaults to 0.05.
+ """
+
+ # Setting the index flag with the value of the `spatial_index` keyword.
+ self.spatial_index = spatial_index
+
+ # Setting the SRID and getting the units. Unit information must be
+ # easily available in the field instance for distance queries.
+ self.srid = srid
+
+ # Setting the dimension of the geometry field.
+ self.dim = dim
+
+ # Setting the verbose_name keyword argument with the positional
+ # first parameter, so this works like normal fields.
+ kwargs['verbose_name'] = verbose_name
+
+ # Is this a geography rather than a geometry column?
+ self.geography = geography
+
+ # Oracle-specific private attributes for creating the entrie in
+ # `USER_SDO_GEOM_METADATA`
+ self._extent = kwargs.pop('extent', (-180.0, -90.0, 180.0, 90.0))
+ self._tolerance = kwargs.pop('tolerance', 0.05)
+
+ super(GeometryField, self).__init__(**kwargs)
+
+ # The following functions are used to get the units, their name, and
+ # the spheroid corresponding to the SRID of the GeometryField.
+ def _get_srid_info(self, connection):
+ # Get attributes from `get_srid_info`.
+ self._units, self._units_name, self._spheroid = get_srid_info(self.srid, connection)
+
+ def spheroid(self, connection):
+ if not hasattr(self, '_spheroid'):
+ self._get_srid_info(connection)
+ return self._spheroid
+
+ def units(self, connection):
+ if not hasattr(self, '_units'):
+ self._get_srid_info(connection)
+ return self._units
+
+ def units_name(self, connection):
+ if not hasattr(self, '_units_name'):
+ self._get_srid_info(connection)
+ return self._units_name
+
+ ### Routines specific to GeometryField ###
+ def geodetic(self, connection):
+ """
+ Returns true if this field's SRID corresponds with a coordinate
+ system that uses non-projected units (e.g., latitude/longitude).
+ """
+ return self.units_name(connection) in self.geodetic_units
+
+ def get_distance(self, value, lookup_type, connection):
+ """
+ Returns a distance number in units of the field. For example, if
+ `D(km=1)` was passed in and the units of the field were in meters,
+ then 1000 would be returned.
+ """
+ return connection.ops.get_distance(self, value, lookup_type)
+
+ def get_prep_value(self, value):
+ """
+ Spatial lookup values are either a parameter that is (or may be
+ converted to) a geometry, or a sequence of lookup values that
+ begins with a geometry. This routine will setup the geometry
+ value properly, and preserve any other lookup parameters before
+ returning to the caller.
+ """
+ if isinstance(value, SQLEvaluator):
+ return value
+ elif isinstance(value, (tuple, list)):
+ geom = value[0]
+ seq_value = True
+ else:
+ geom = value
+ seq_value = False
+
+ # When the input is not a GEOS geometry, attempt to construct one
+ # from the given string input.
+ if isinstance(geom, Geometry):
+ pass
+ elif isinstance(geom, basestring) or hasattr(geom, '__geo_interface__'):
+ try:
+ geom = Geometry(geom)
+ except GeometryException:
+ raise ValueError('Could not create geometry from lookup value.')
+ else:
+ raise ValueError('Cannot use object with type %s for a geometry lookup parameter.' % type(geom).__name__)
+
+ # Assigning the SRID value.
+ geom.srid = self.get_srid(geom)
+
+ if seq_value:
+ lookup_val = [geom]
+ lookup_val.extend(value[1:])
+ return tuple(lookup_val)
+ else:
+ return geom
+
+ def get_srid(self, geom):
+ """
+ Returns the default SRID for the given geometry, taking into account
+ the SRID set for the field. For example, if the input geometry
+ has no SRID, then that of the field will be returned.
+ """
+ gsrid = geom.srid # SRID of given geometry.
+ if gsrid is None or self.srid == -1 or (gsrid == -1 and self.srid != -1):
+ return self.srid
+ else:
+ return gsrid
+
+ ### Routines overloaded from Field ###
+ def contribute_to_class(self, cls, name):
+ super(GeometryField, self).contribute_to_class(cls, name)
+
+ # Setup for lazy-instantiated Geometry object.
+ setattr(cls, self.attname, GeometryProxy(Geometry, self))
+
+ def db_type(self, connection):
+ return connection.ops.geo_db_type(self)
+
+ def formfield(self, **kwargs):
+ defaults = {'form_class' : forms.GeometryField,
+ 'null' : self.null,
+ 'geom_type' : self.geom_type,
+ 'srid' : self.srid,
+ }
+ defaults.update(kwargs)
+ return super(GeometryField, self).formfield(**defaults)
+
+ def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
+ """
+ Prepare for the database lookup, and return any spatial parameters
+ necessary for the query. This includes wrapping any geometry
+ parameters with a backend-specific adapter and formatting any distance
+ parameters into the correct units for the coordinate system of the
+ field.
+ """
+ if lookup_type in connection.ops.gis_terms:
+ # special case for isnull lookup
+ if lookup_type == 'isnull':
+ return []
+
+ # Populating the parameters list, and wrapping the Geometry
+ # with the Adapter of the spatial backend.
+ if isinstance(value, (tuple, list)):
+ params = [connection.ops.Adapter(value[0])]
+ if lookup_type in connection.ops.distance_functions:
+ # Getting the distance parameter in the units of the field.
+ params += self.get_distance(value[1:], lookup_type, connection)
+ elif lookup_type in connection.ops.truncate_params:
+ # Lookup is one where SQL parameters aren't needed from the
+ # given lookup value.
+ pass
+ else:
+ params += value[1:]
+ elif isinstance(value, SQLEvaluator):
+ params = []
+ else:
+ params = [connection.ops.Adapter(value)]
+
+ return params
+ else:
+ raise ValueError('%s is not a valid spatial lookup for %s.' %
+ (lookup_type, self.__class__.__name__))
+
+ def get_prep_lookup(self, lookup_type, value):
+ if lookup_type == 'isnull':
+ return bool(value)
+ else:
+ return self.get_prep_value(value)
+
+ def get_db_prep_save(self, value, connection):
+ "Prepares the value for saving in the database."
+ if value is None:
+ return None
+ else:
+ return connection.ops.Adapter(self.get_prep_value(value))
+
+ def get_placeholder(self, value, connection):
+ """
+ Returns the placeholder for the geometry column for the
+ given value.
+ """
+ return connection.ops.get_geom_placeholder(self, value)
+
+# The OpenGIS Geometry Type Fields
+class PointField(GeometryField):
+ geom_type = 'POINT'
+ description = _("Point")
+
+class LineStringField(GeometryField):
+ geom_type = 'LINESTRING'
+ description = _("Line string")
+
+class PolygonField(GeometryField):
+ geom_type = 'POLYGON'
+ description = _("Polygon")
+
+class MultiPointField(GeometryField):
+ geom_type = 'MULTIPOINT'
+ description = _("Multi-point")
+
+class MultiLineStringField(GeometryField):
+ geom_type = 'MULTILINESTRING'
+ description = _("Multi-line string")
+
+class MultiPolygonField(GeometryField):
+ geom_type = 'MULTIPOLYGON'
+ description = _("Multi polygon")
+
+class GeometryCollectionField(GeometryField):
+ geom_type = 'GEOMETRYCOLLECTION'
+ description = _("Geometry collection")
diff --git a/parts/django/django/contrib/gis/db/models/manager.py b/parts/django/django/contrib/gis/db/models/manager.py
new file mode 100644
index 0000000..61fb821
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/manager.py
@@ -0,0 +1,103 @@
+from django.db.models.manager import Manager
+from django.contrib.gis.db.models.query import GeoQuerySet
+
+class GeoManager(Manager):
+ "Overrides Manager to return Geographic QuerySets."
+
+ # This manager should be used for queries on related fields
+ # so that geometry columns on Oracle and MySQL are selected
+ # properly.
+ use_for_related_fields = True
+
+ def get_query_set(self):
+ return GeoQuerySet(self.model, using=self._db)
+
+ def area(self, *args, **kwargs):
+ return self.get_query_set().area(*args, **kwargs)
+
+ def centroid(self, *args, **kwargs):
+ return self.get_query_set().centroid(*args, **kwargs)
+
+ def collect(self, *args, **kwargs):
+ return self.get_query_set().collect(*args, **kwargs)
+
+ def difference(self, *args, **kwargs):
+ return self.get_query_set().difference(*args, **kwargs)
+
+ def distance(self, *args, **kwargs):
+ return self.get_query_set().distance(*args, **kwargs)
+
+ def envelope(self, *args, **kwargs):
+ return self.get_query_set().envelope(*args, **kwargs)
+
+ def extent(self, *args, **kwargs):
+ return self.get_query_set().extent(*args, **kwargs)
+
+ def extent3d(self, *args, **kwargs):
+ return self.get_query_set().extent3d(*args, **kwargs)
+
+ def force_rhr(self, *args, **kwargs):
+ return self.get_query_set().force_rhr(*args, **kwargs)
+
+ def geohash(self, *args, **kwargs):
+ return self.get_query_set().geohash(*args, **kwargs)
+
+ def geojson(self, *args, **kwargs):
+ return self.get_query_set().geojson(*args, **kwargs)
+
+ def gml(self, *args, **kwargs):
+ return self.get_query_set().gml(*args, **kwargs)
+
+ def intersection(self, *args, **kwargs):
+ return self.get_query_set().intersection(*args, **kwargs)
+
+ def kml(self, *args, **kwargs):
+ return self.get_query_set().kml(*args, **kwargs)
+
+ def length(self, *args, **kwargs):
+ return self.get_query_set().length(*args, **kwargs)
+
+ def make_line(self, *args, **kwargs):
+ return self.get_query_set().make_line(*args, **kwargs)
+
+ def mem_size(self, *args, **kwargs):
+ return self.get_query_set().mem_size(*args, **kwargs)
+
+ def num_geom(self, *args, **kwargs):
+ return self.get_query_set().num_geom(*args, **kwargs)
+
+ def num_points(self, *args, **kwargs):
+ return self.get_query_set().num_points(*args, **kwargs)
+
+ def perimeter(self, *args, **kwargs):
+ return self.get_query_set().perimeter(*args, **kwargs)
+
+ def point_on_surface(self, *args, **kwargs):
+ return self.get_query_set().point_on_surface(*args, **kwargs)
+
+ def reverse_geom(self, *args, **kwargs):
+ return self.get_query_set().reverse_geom(*args, **kwargs)
+
+ def scale(self, *args, **kwargs):
+ return self.get_query_set().scale(*args, **kwargs)
+
+ def snap_to_grid(self, *args, **kwargs):
+ return self.get_query_set().snap_to_grid(*args, **kwargs)
+
+ def svg(self, *args, **kwargs):
+ return self.get_query_set().svg(*args, **kwargs)
+
+ def sym_difference(self, *args, **kwargs):
+ return self.get_query_set().sym_difference(*args, **kwargs)
+
+ def transform(self, *args, **kwargs):
+ return self.get_query_set().transform(*args, **kwargs)
+
+ def translate(self, *args, **kwargs):
+ return self.get_query_set().translate(*args, **kwargs)
+
+ def union(self, *args, **kwargs):
+ return self.get_query_set().union(*args, **kwargs)
+
+ def unionagg(self, *args, **kwargs):
+ return self.get_query_set().unionagg(*args, **kwargs)
diff --git a/parts/django/django/contrib/gis/db/models/proxy.py b/parts/django/django/contrib/gis/db/models/proxy.py
new file mode 100644
index 0000000..e569dd5
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/proxy.py
@@ -0,0 +1,64 @@
+"""
+The GeometryProxy object, allows for lazy-geometries. The proxy uses
+Python descriptors for instantiating and setting Geometry objects
+corresponding to geographic model fields.
+
+Thanks to Robert Coup for providing this functionality (see #4322).
+"""
+
+class GeometryProxy(object):
+ def __init__(self, klass, field):
+ """
+ Proxy initializes on the given Geometry class (not an instance) and
+ the GeometryField.
+ """
+ self._field = field
+ self._klass = klass
+
+ def __get__(self, obj, type=None):
+ """
+ This accessor retrieves the geometry, initializing it using the geometry
+ class specified during initialization and the HEXEWKB value of the field.
+ Currently, only GEOS or OGR geometries are supported.
+ """
+ if obj is None:
+ # Accessed on a class, not an instance
+ return self
+
+ # Getting the value of the field.
+ geom_value = obj.__dict__[self._field.attname]
+
+ if isinstance(geom_value, self._klass):
+ geom = geom_value
+ elif (geom_value is None) or (geom_value==''):
+ geom = None
+ else:
+ # Otherwise, a Geometry object is built using the field's contents,
+ # and the model's corresponding attribute is set.
+ geom = self._klass(geom_value)
+ setattr(obj, self._field.attname, geom)
+ return geom
+
+ def __set__(self, obj, value):
+ """
+ This accessor sets the proxied geometry with the geometry class
+ specified during initialization. Values of None, HEXEWKB, or WKT may
+ be used to set the geometry as well.
+ """
+ # The OGC Geometry type of the field.
+ gtype = self._field.geom_type
+
+ # The geometry type must match that of the field -- unless the
+ # general GeometryField is used.
+ if isinstance(value, self._klass) and (str(value.geom_type).upper() == gtype or gtype == 'GEOMETRY'):
+ # Assigning the SRID to the geometry.
+ if value.srid is None: value.srid = self._field.srid
+ elif value is None or isinstance(value, (basestring, buffer)):
+ # Set with None, WKT, HEX, or WKB
+ pass
+ else:
+ raise TypeError('cannot set %s GeometryProxy with value of type: %s' % (obj.__class__.__name__, type(value)))
+
+ # Setting the objects dictionary with the value, and returning.
+ obj.__dict__[self._field.attname] = value
+ return value
diff --git a/parts/django/django/contrib/gis/db/models/query.py b/parts/django/django/contrib/gis/db/models/query.py
new file mode 100644
index 0000000..4df1a3a
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/query.py
@@ -0,0 +1,777 @@
+from django.db import connections
+from django.db.models.query import QuerySet, Q, ValuesQuerySet, ValuesListQuerySet
+
+from django.contrib.gis.db.models import aggregates
+from django.contrib.gis.db.models.fields import get_srid_info, GeometryField, PointField, LineStringField
+from django.contrib.gis.db.models.sql import AreaField, DistanceField, GeomField, GeoQuery, GeoWhereNode
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Area, Distance
+
+class GeoQuerySet(QuerySet):
+ "The Geographic QuerySet."
+
+ ### Methods overloaded from QuerySet ###
+ def __init__(self, model=None, query=None, using=None):
+ super(GeoQuerySet, self).__init__(model=model, query=query, using=using)
+ self.query = query or GeoQuery(self.model)
+
+ def values(self, *fields):
+ return self._clone(klass=GeoValuesQuerySet, setup=True, _fields=fields)
+
+ def values_list(self, *fields, **kwargs):
+ flat = kwargs.pop('flat', False)
+ if kwargs:
+ raise TypeError('Unexpected keyword arguments to values_list: %s'
+ % (kwargs.keys(),))
+ if flat and len(fields) > 1:
+ raise TypeError("'flat' is not valid when values_list is called with more than one field.")
+ return self._clone(klass=GeoValuesListQuerySet, setup=True, flat=flat,
+ _fields=fields)
+
+ ### GeoQuerySet Methods ###
+ def area(self, tolerance=0.05, **kwargs):
+ """
+ Returns the area of the geographic field in an `area` attribute on
+ each element of this GeoQuerySet.
+ """
+ # Peforming setup here rather than in `_spatial_attribute` so that
+ # we can get the units for `AreaField`.
+ procedure_args, geo_field = self._spatial_setup('area', field_name=kwargs.get('field_name', None))
+ s = {'procedure_args' : procedure_args,
+ 'geo_field' : geo_field,
+ 'setup' : False,
+ }
+ connection = connections[self.db]
+ backend = connection.ops
+ if backend.oracle:
+ s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
+ s['procedure_args']['tolerance'] = tolerance
+ s['select_field'] = AreaField('sq_m') # Oracle returns area in units of meters.
+ elif backend.postgis or backend.spatialite:
+ if backend.geography:
+ # Geography fields support area calculation, returns square meters.
+ s['select_field'] = AreaField('sq_m')
+ elif not geo_field.geodetic(connection):
+ # Getting the area units of the geographic field.
+ s['select_field'] = AreaField(Area.unit_attname(geo_field.units_name(connection)))
+ else:
+ # TODO: Do we want to support raw number areas for geodetic fields?
+ raise Exception('Area on geodetic coordinate systems not supported.')
+ return self._spatial_attribute('area', s, **kwargs)
+
+ def centroid(self, **kwargs):
+ """
+ Returns the centroid of the geographic field in a `centroid`
+ attribute on each element of this GeoQuerySet.
+ """
+ return self._geom_attribute('centroid', **kwargs)
+
+ def collect(self, **kwargs):
+ """
+ Performs an aggregate collect operation on the given geometry field.
+ This is analagous to a union operation, but much faster because
+ boundaries are not dissolved.
+ """
+ return self._spatial_aggregate(aggregates.Collect, **kwargs)
+
+ def difference(self, geom, **kwargs):
+ """
+ Returns the spatial difference of the geographic field in a `difference`
+ attribute on each element of this GeoQuerySet.
+ """
+ return self._geomset_attribute('difference', geom, **kwargs)
+
+ def distance(self, geom, **kwargs):
+ """
+ Returns the distance from the given geographic field name to the
+ given geometry in a `distance` attribute on each element of the
+ GeoQuerySet.
+
+ Keyword Arguments:
+ `spheroid` => If the geometry field is geodetic and PostGIS is
+ the spatial database, then the more accurate
+ spheroid calculation will be used instead of the
+ quicker sphere calculation.
+
+ `tolerance` => Used only for Oracle. The tolerance is
+ in meters -- a default of 5 centimeters (0.05)
+ is used.
+ """
+ return self._distance_attribute('distance', geom, **kwargs)
+
+ def envelope(self, **kwargs):
+ """
+ Returns a Geometry representing the bounding box of the
+ Geometry field in an `envelope` attribute on each element of
+ the GeoQuerySet.
+ """
+ return self._geom_attribute('envelope', **kwargs)
+
+ def extent(self, **kwargs):
+ """
+ Returns the extent (aggregate) of the features in the GeoQuerySet. The
+ extent will be returned as a 4-tuple, consisting of (xmin, ymin, xmax, ymax).
+ """
+ return self._spatial_aggregate(aggregates.Extent, **kwargs)
+
+ def extent3d(self, **kwargs):
+ """
+ Returns the aggregate extent, in 3D, of the features in the
+ GeoQuerySet. It is returned as a 6-tuple, comprising:
+ (xmin, ymin, zmin, xmax, ymax, zmax).
+ """
+ return self._spatial_aggregate(aggregates.Extent3D, **kwargs)
+
+ def force_rhr(self, **kwargs):
+ """
+ Returns a modified version of the Polygon/MultiPolygon in which
+ all of the vertices follow the Right-Hand-Rule. By default,
+ this is attached as the `force_rhr` attribute on each element
+ of the GeoQuerySet.
+ """
+ return self._geom_attribute('force_rhr', **kwargs)
+
+ def geojson(self, precision=8, crs=False, bbox=False, **kwargs):
+ """
+ Returns a GeoJSON representation of the geomtry field in a `geojson`
+ attribute on each element of the GeoQuerySet.
+
+ The `crs` and `bbox` keywords may be set to True if the users wants
+ the coordinate reference system and the bounding box to be included
+ in the GeoJSON representation of the geometry.
+ """
+ backend = connections[self.db].ops
+ if not backend.geojson:
+ raise NotImplementedError('Only PostGIS 1.3.4+ supports GeoJSON serialization.')
+
+ if not isinstance(precision, (int, long)):
+ raise TypeError('Precision keyword must be set with an integer.')
+
+ # Setting the options flag -- which depends on which version of
+ # PostGIS we're using.
+ if backend.spatial_version >= (1, 4, 0):
+ options = 0
+ if crs and bbox: options = 3
+ elif bbox: options = 1
+ elif crs: options = 2
+ else:
+ options = 0
+ if crs and bbox: options = 3
+ elif crs: options = 1
+ elif bbox: options = 2
+ s = {'desc' : 'GeoJSON',
+ 'procedure_args' : {'precision' : precision, 'options' : options},
+ 'procedure_fmt' : '%(geo_col)s,%(precision)s,%(options)s',
+ }
+ return self._spatial_attribute('geojson', s, **kwargs)
+
+ def geohash(self, precision=20, **kwargs):
+ """
+ Returns a GeoHash representation of the given field in a `geohash`
+ attribute on each element of the GeoQuerySet.
+
+ The `precision` keyword may be used to custom the number of
+ _characters_ used in the output GeoHash, the default is 20.
+ """
+ s = {'desc' : 'GeoHash',
+ 'procedure_args': {'precision': precision},
+ 'procedure_fmt': '%(geo_col)s,%(precision)s',
+ }
+ return self._spatial_attribute('geohash', s, **kwargs)
+
+ def gml(self, precision=8, version=2, **kwargs):
+ """
+ Returns GML representation of the given field in a `gml` attribute
+ on each element of the GeoQuerySet.
+ """
+ backend = connections[self.db].ops
+ s = {'desc' : 'GML', 'procedure_args' : {'precision' : precision}}
+ if backend.postgis:
+ # PostGIS AsGML() aggregate function parameter order depends on the
+ # version -- uggh.
+ if backend.spatial_version > (1, 3, 1):
+ procedure_fmt = '%(version)s,%(geo_col)s,%(precision)s'
+ else:
+ procedure_fmt = '%(geo_col)s,%(precision)s,%(version)s'
+ s['procedure_args'] = {'precision' : precision, 'version' : version}
+
+ return self._spatial_attribute('gml', s, **kwargs)
+
+ def intersection(self, geom, **kwargs):
+ """
+ Returns the spatial intersection of the Geometry field in
+ an `intersection` attribute on each element of this
+ GeoQuerySet.
+ """
+ return self._geomset_attribute('intersection', geom, **kwargs)
+
+ def kml(self, **kwargs):
+ """
+ Returns KML representation of the geometry field in a `kml`
+ attribute on each element of this GeoQuerySet.
+ """
+ s = {'desc' : 'KML',
+ 'procedure_fmt' : '%(geo_col)s,%(precision)s',
+ 'procedure_args' : {'precision' : kwargs.pop('precision', 8)},
+ }
+ return self._spatial_attribute('kml', s, **kwargs)
+
+ def length(self, **kwargs):
+ """
+ Returns the length of the geometry field as a `Distance` object
+ stored in a `length` attribute on each element of this GeoQuerySet.
+ """
+ return self._distance_attribute('length', None, **kwargs)
+
+ def make_line(self, **kwargs):
+ """
+ Creates a linestring from all of the PointField geometries in the
+ this GeoQuerySet and returns it. This is a spatial aggregate
+ method, and thus returns a geometry rather than a GeoQuerySet.
+ """
+ return self._spatial_aggregate(aggregates.MakeLine, geo_field_type=PointField, **kwargs)
+
+ def mem_size(self, **kwargs):
+ """
+ Returns the memory size (number of bytes) that the geometry field takes
+ in a `mem_size` attribute on each element of this GeoQuerySet.
+ """
+ return self._spatial_attribute('mem_size', {}, **kwargs)
+
+ def num_geom(self, **kwargs):
+ """
+ Returns the number of geometries if the field is a
+ GeometryCollection or Multi* Field in a `num_geom`
+ attribute on each element of this GeoQuerySet; otherwise
+ the sets with None.
+ """
+ return self._spatial_attribute('num_geom', {}, **kwargs)
+
+ def num_points(self, **kwargs):
+ """
+ Returns the number of points in the first linestring in the
+ Geometry field in a `num_points` attribute on each element of
+ this GeoQuerySet; otherwise sets with None.
+ """
+ return self._spatial_attribute('num_points', {}, **kwargs)
+
+ def perimeter(self, **kwargs):
+ """
+ Returns the perimeter of the geometry field as a `Distance` object
+ stored in a `perimeter` attribute on each element of this GeoQuerySet.
+ """
+ return self._distance_attribute('perimeter', None, **kwargs)
+
+ def point_on_surface(self, **kwargs):
+ """
+ Returns a Point geometry guaranteed to lie on the surface of the
+ Geometry field in a `point_on_surface` attribute on each element
+ of this GeoQuerySet; otherwise sets with None.
+ """
+ return self._geom_attribute('point_on_surface', **kwargs)
+
+ def reverse_geom(self, **kwargs):
+ """
+ Reverses the coordinate order of the geometry, and attaches as a
+ `reverse` attribute on each element of this GeoQuerySet.
+ """
+ s = {'select_field' : GeomField(),}
+ kwargs.setdefault('model_att', 'reverse_geom')
+ if connections[self.db].ops.oracle:
+ s['geo_field_type'] = LineStringField
+ return self._spatial_attribute('reverse', s, **kwargs)
+
+ def scale(self, x, y, z=0.0, **kwargs):
+ """
+ Scales the geometry to a new size by multiplying the ordinates
+ with the given x,y,z scale factors.
+ """
+ if connections[self.db].ops.spatialite:
+ if z != 0.0:
+ raise NotImplementedError('SpatiaLite does not support 3D scaling.')
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
+ 'procedure_args' : {'x' : x, 'y' : y},
+ 'select_field' : GeomField(),
+ }
+ else:
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s',
+ 'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
+ 'select_field' : GeomField(),
+ }
+ return self._spatial_attribute('scale', s, **kwargs)
+
+ def snap_to_grid(self, *args, **kwargs):
+ """
+ Snap all points of the input geometry to the grid. How the
+ geometry is snapped to the grid depends on how many arguments
+ were given:
+ - 1 argument : A single size to snap both the X and Y grids to.
+ - 2 arguments: X and Y sizes to snap the grid to.
+ - 4 arguments: X, Y sizes and the X, Y origins.
+ """
+ if False in [isinstance(arg, (float, int, long)) for arg in args]:
+ raise TypeError('Size argument(s) for the grid must be a float or integer values.')
+
+ nargs = len(args)
+ if nargs == 1:
+ size = args[0]
+ procedure_fmt = '%(geo_col)s,%(size)s'
+ procedure_args = {'size' : size}
+ elif nargs == 2:
+ xsize, ysize = args
+ procedure_fmt = '%(geo_col)s,%(xsize)s,%(ysize)s'
+ procedure_args = {'xsize' : xsize, 'ysize' : ysize}
+ elif nargs == 4:
+ xsize, ysize, xorigin, yorigin = args
+ procedure_fmt = '%(geo_col)s,%(xorigin)s,%(yorigin)s,%(xsize)s,%(ysize)s'
+ procedure_args = {'xsize' : xsize, 'ysize' : ysize,
+ 'xorigin' : xorigin, 'yorigin' : yorigin}
+ else:
+ raise ValueError('Must provide 1, 2, or 4 arguments to `snap_to_grid`.')
+
+ s = {'procedure_fmt' : procedure_fmt,
+ 'procedure_args' : procedure_args,
+ 'select_field' : GeomField(),
+ }
+
+ return self._spatial_attribute('snap_to_grid', s, **kwargs)
+
+ def svg(self, relative=False, precision=8, **kwargs):
+ """
+ Returns SVG representation of the geographic field in a `svg`
+ attribute on each element of this GeoQuerySet.
+
+ Keyword Arguments:
+ `relative` => If set to True, this will evaluate the path in
+ terms of relative moves (rather than absolute).
+
+ `precision` => May be used to set the maximum number of decimal
+ digits used in output (defaults to 8).
+ """
+ relative = int(bool(relative))
+ if not isinstance(precision, (int, long)):
+ raise TypeError('SVG precision keyword argument must be an integer.')
+ s = {'desc' : 'SVG',
+ 'procedure_fmt' : '%(geo_col)s,%(rel)s,%(precision)s',
+ 'procedure_args' : {'rel' : relative,
+ 'precision' : precision,
+ }
+ }
+ return self._spatial_attribute('svg', s, **kwargs)
+
+ def sym_difference(self, geom, **kwargs):
+ """
+ Returns the symmetric difference of the geographic field in a
+ `sym_difference` attribute on each element of this GeoQuerySet.
+ """
+ return self._geomset_attribute('sym_difference', geom, **kwargs)
+
+ def translate(self, x, y, z=0.0, **kwargs):
+ """
+ Translates the geometry to a new location using the given numeric
+ parameters as offsets.
+ """
+ if connections[self.db].ops.spatialite:
+ if z != 0.0:
+ raise NotImplementedError('SpatiaLite does not support 3D translation.')
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s',
+ 'procedure_args' : {'x' : x, 'y' : y},
+ 'select_field' : GeomField(),
+ }
+ else:
+ s = {'procedure_fmt' : '%(geo_col)s,%(x)s,%(y)s,%(z)s',
+ 'procedure_args' : {'x' : x, 'y' : y, 'z' : z},
+ 'select_field' : GeomField(),
+ }
+ return self._spatial_attribute('translate', s, **kwargs)
+
+ def transform(self, srid=4326, **kwargs):
+ """
+ Transforms the given geometry field to the given SRID. If no SRID is
+ provided, the transformation will default to using 4326 (WGS84).
+ """
+ if not isinstance(srid, (int, long)):
+ raise TypeError('An integer SRID must be provided.')
+ field_name = kwargs.get('field_name', None)
+ tmp, geo_field = self._spatial_setup('transform', field_name=field_name)
+
+ # Getting the selection SQL for the given geographic field.
+ field_col = self._geocol_select(geo_field, field_name)
+
+ # Why cascading substitutions? Because spatial backends like
+ # Oracle and MySQL already require a function call to convert to text, thus
+ # when there's also a transformation we need to cascade the substitutions.
+ # For example, 'SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM( ... )'
+ geo_col = self.query.custom_select.get(geo_field, field_col)
+
+ # Setting the key for the field's column with the custom SELECT SQL to
+ # override the geometry column returned from the database.
+ custom_sel = '%s(%s, %s)' % (connections[self.db].ops.transform, geo_col, srid)
+ # TODO: Should we have this as an alias?
+ # custom_sel = '(%s(%s, %s)) AS %s' % (SpatialBackend.transform, geo_col, srid, qn(geo_field.name))
+ self.query.transformed_srid = srid # So other GeoQuerySet methods
+ self.query.custom_select[geo_field] = custom_sel
+ return self._clone()
+
+ def union(self, geom, **kwargs):
+ """
+ Returns the union of the geographic field with the given
+ Geometry in a `union` attribute on each element of this GeoQuerySet.
+ """
+ return self._geomset_attribute('union', geom, **kwargs)
+
+ def unionagg(self, **kwargs):
+ """
+ Performs an aggregate union on the given geometry field. Returns
+ None if the GeoQuerySet is empty. The `tolerance` keyword is for
+ Oracle backends only.
+ """
+ return self._spatial_aggregate(aggregates.Union, **kwargs)
+
+ ### Private API -- Abstracted DRY routines. ###
+ def _spatial_setup(self, att, desc=None, field_name=None, geo_field_type=None):
+ """
+ Performs set up for executing the spatial function.
+ """
+ # Does the spatial backend support this?
+ connection = connections[self.db]
+ func = getattr(connection.ops, att, False)
+ if desc is None: desc = att
+ if not func:
+ raise NotImplementedError('%s stored procedure not available on '
+ 'the %s backend.' %
+ (desc, connection.ops.name))
+
+ # Initializing the procedure arguments.
+ procedure_args = {'function' : func}
+
+ # Is there a geographic field in the model to perform this
+ # operation on?
+ geo_field = self.query._geo_field(field_name)
+ if not geo_field:
+ raise TypeError('%s output only available on GeometryFields.' % func)
+
+ # If the `geo_field_type` keyword was used, then enforce that
+ # type limitation.
+ if not geo_field_type is None and not isinstance(geo_field, geo_field_type):
+ raise TypeError('"%s" stored procedures may only be called on %ss.' % (func, geo_field_type.__name__))
+
+ # Setting the procedure args.
+ procedure_args['geo_col'] = self._geocol_select(geo_field, field_name)
+
+ return procedure_args, geo_field
+
+ def _spatial_aggregate(self, aggregate, field_name=None,
+ geo_field_type=None, tolerance=0.05):
+ """
+ DRY routine for calling aggregate spatial stored procedures and
+ returning their result to the caller of the function.
+ """
+ # Getting the field the geographic aggregate will be called on.
+ geo_field = self.query._geo_field(field_name)
+ if not geo_field:
+ raise TypeError('%s aggregate only available on GeometryFields.' % aggregate.name)
+
+ # Checking if there are any geo field type limitations on this
+ # aggregate (e.g. ST_Makeline only operates on PointFields).
+ if not geo_field_type is None and not isinstance(geo_field, geo_field_type):
+ raise TypeError('%s aggregate may only be called on %ss.' % (aggregate.name, geo_field_type.__name__))
+
+ # Getting the string expression of the field name, as this is the
+ # argument taken by `Aggregate` objects.
+ agg_col = field_name or geo_field.name
+
+ # Adding any keyword parameters for the Aggregate object. Oracle backends
+ # in particular need an additional `tolerance` parameter.
+ agg_kwargs = {}
+ if connections[self.db].ops.oracle: agg_kwargs['tolerance'] = tolerance
+
+ # Calling the QuerySet.aggregate, and returning only the value of the aggregate.
+ return self.aggregate(geoagg=aggregate(agg_col, **agg_kwargs))['geoagg']
+
+ def _spatial_attribute(self, att, settings, field_name=None, model_att=None):
+ """
+ DRY routine for calling a spatial stored procedure on a geometry column
+ and attaching its output as an attribute of the model.
+
+ Arguments:
+ att:
+ The name of the spatial attribute that holds the spatial
+ SQL function to call.
+
+ settings:
+ Dictonary of internal settings to customize for the spatial procedure.
+
+ Public Keyword Arguments:
+
+ field_name:
+ The name of the geographic field to call the spatial
+ function on. May also be a lookup to a geometry field
+ as part of a foreign key relation.
+
+ model_att:
+ The name of the model attribute to attach the output of
+ the spatial function to.
+ """
+ # Default settings.
+ settings.setdefault('desc', None)
+ settings.setdefault('geom_args', ())
+ settings.setdefault('geom_field', None)
+ settings.setdefault('procedure_args', {})
+ settings.setdefault('procedure_fmt', '%(geo_col)s')
+ settings.setdefault('select_params', [])
+
+ connection = connections[self.db]
+ backend = connection.ops
+
+ # Performing setup for the spatial column, unless told not to.
+ if settings.get('setup', True):
+ default_args, geo_field = self._spatial_setup(att, desc=settings['desc'], field_name=field_name,
+ geo_field_type=settings.get('geo_field_type', None))
+ for k, v in default_args.iteritems(): settings['procedure_args'].setdefault(k, v)
+ else:
+ geo_field = settings['geo_field']
+
+ # The attribute to attach to the model.
+ if not isinstance(model_att, basestring): model_att = att
+
+ # Special handling for any argument that is a geometry.
+ for name in settings['geom_args']:
+ # Using the field's get_placeholder() routine to get any needed
+ # transformation SQL.
+ geom = geo_field.get_prep_value(settings['procedure_args'][name])
+ params = geo_field.get_db_prep_lookup('contains', geom, connection=connection)
+ geom_placeholder = geo_field.get_placeholder(geom, connection)
+
+ # Replacing the procedure format with that of any needed
+ # transformation SQL.
+ old_fmt = '%%(%s)s' % name
+ new_fmt = geom_placeholder % '%%s'
+ settings['procedure_fmt'] = settings['procedure_fmt'].replace(old_fmt, new_fmt)
+ settings['select_params'].extend(params)
+
+ # Getting the format for the stored procedure.
+ fmt = '%%(function)s(%s)' % settings['procedure_fmt']
+
+ # If the result of this function needs to be converted.
+ if settings.get('select_field', False):
+ sel_fld = settings['select_field']
+ if isinstance(sel_fld, GeomField) and backend.select:
+ self.query.custom_select[model_att] = backend.select
+ if connection.ops.oracle:
+ sel_fld.empty_strings_allowed = False
+ self.query.extra_select_fields[model_att] = sel_fld
+
+ # Finally, setting the extra selection attribute with
+ # the format string expanded with the stored procedure
+ # arguments.
+ return self.extra(select={model_att : fmt % settings['procedure_args']},
+ select_params=settings['select_params'])
+
+ def _distance_attribute(self, func, geom=None, tolerance=0.05, spheroid=False, **kwargs):
+ """
+ DRY routine for GeoQuerySet distance attribute routines.
+ """
+ # Setting up the distance procedure arguments.
+ procedure_args, geo_field = self._spatial_setup(func, field_name=kwargs.get('field_name', None))
+
+ # If geodetic defaulting distance attribute to meters (Oracle and
+ # PostGIS spherical distances return meters). Otherwise, use the
+ # units of the geometry field.
+ connection = connections[self.db]
+ geodetic = geo_field.geodetic(connection)
+ geography = geo_field.geography
+
+ if geodetic:
+ dist_att = 'm'
+ else:
+ dist_att = Distance.unit_attname(geo_field.units_name(connection))
+
+ # Shortcut booleans for what distance function we're using and
+ # whether the geometry field is 3D.
+ distance = func == 'distance'
+ length = func == 'length'
+ perimeter = func == 'perimeter'
+ if not (distance or length or perimeter):
+ raise ValueError('Unknown distance function: %s' % func)
+ geom_3d = geo_field.dim == 3
+
+ # The field's get_db_prep_lookup() is used to get any
+ # extra distance parameters. Here we set up the
+ # parameters that will be passed in to field's function.
+ lookup_params = [geom or 'POINT (0 0)', 0]
+
+ # Getting the spatial backend operations.
+ backend = connection.ops
+
+ # If the spheroid calculation is desired, either by the `spheroid`
+ # keyword or when calculating the length of geodetic field, make
+ # sure the 'spheroid' distance setting string is passed in so we
+ # get the correct spatial stored procedure.
+ if spheroid or (backend.postgis and geodetic and
+ (not geography) and length):
+ lookup_params.append('spheroid')
+ lookup_params = geo_field.get_prep_value(lookup_params)
+ params = geo_field.get_db_prep_lookup('distance_lte', lookup_params, connection=connection)
+
+ # The `geom_args` flag is set to true if a geometry parameter was
+ # passed in.
+ geom_args = bool(geom)
+
+ if backend.oracle:
+ if distance:
+ procedure_fmt = '%(geo_col)s,%(geom)s,%(tolerance)s'
+ elif length or perimeter:
+ procedure_fmt = '%(geo_col)s,%(tolerance)s'
+ procedure_args['tolerance'] = tolerance
+ else:
+ # Getting whether this field is in units of degrees since the field may have
+ # been transformed via the `transform` GeoQuerySet method.
+ if self.query.transformed_srid:
+ u, unit_name, s = get_srid_info(self.query.transformed_srid, connection)
+ geodetic = unit_name in geo_field.geodetic_units
+
+ if backend.spatialite and geodetic:
+ raise ValueError('SQLite does not support linear distance calculations on geodetic coordinate systems.')
+
+ if distance:
+ if self.query.transformed_srid:
+ # Setting the `geom_args` flag to false because we want to handle
+ # transformation SQL here, rather than the way done by default
+ # (which will transform to the original SRID of the field rather
+ # than to what was transformed to).
+ geom_args = False
+ procedure_fmt = '%s(%%(geo_col)s, %s)' % (backend.transform, self.query.transformed_srid)
+ if geom.srid is None or geom.srid == self.query.transformed_srid:
+ # If the geom parameter srid is None, it is assumed the coordinates
+ # are in the transformed units. A placeholder is used for the
+ # geometry parameter. `GeomFromText` constructor is also needed
+ # to wrap geom placeholder for SpatiaLite.
+ if backend.spatialite:
+ procedure_fmt += ', %s(%%%%s, %s)' % (backend.from_text, self.query.transformed_srid)
+ else:
+ procedure_fmt += ', %%s'
+ else:
+ # We need to transform the geom to the srid specified in `transform()`,
+ # so wrapping the geometry placeholder in transformation SQL.
+ # SpatiaLite also needs geometry placeholder wrapped in `GeomFromText`
+ # constructor.
+ if backend.spatialite:
+ procedure_fmt += ', %s(%s(%%%%s, %s), %s)' % (backend.transform, backend.from_text,
+ geom.srid, self.query.transformed_srid)
+ else:
+ procedure_fmt += ', %s(%%%%s, %s)' % (backend.transform, self.query.transformed_srid)
+ else:
+ # `transform()` was not used on this GeoQuerySet.
+ procedure_fmt = '%(geo_col)s,%(geom)s'
+
+ if not geography and geodetic:
+ # Spherical distance calculation is needed (because the geographic
+ # field is geodetic). However, the PostGIS ST_distance_sphere/spheroid()
+ # procedures may only do queries from point columns to point geometries
+ # some error checking is required.
+ if not backend.geography:
+ if not isinstance(geo_field, PointField):
+ raise ValueError('Spherical distance calculation only supported on PointFields.')
+ if not str(Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
+ raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
+ # The `function` procedure argument needs to be set differently for
+ # geodetic distance calculations.
+ if spheroid:
+ # Call to distance_spheroid() requires spheroid param as well.
+ procedure_fmt += ",'%(spheroid)s'"
+ procedure_args.update({'function' : backend.distance_spheroid, 'spheroid' : params[1]})
+ else:
+ procedure_args.update({'function' : backend.distance_sphere})
+ elif length or perimeter:
+ procedure_fmt = '%(geo_col)s'
+ if not geography and geodetic and length:
+ # There's no `length_sphere`, and `length_spheroid` also
+ # works on 3D geometries.
+ procedure_fmt += ",'%(spheroid)s'"
+ procedure_args.update({'function' : backend.length_spheroid, 'spheroid' : params[1]})
+ elif geom_3d and backend.postgis:
+ # Use 3D variants of perimeter and length routines on PostGIS.
+ if perimeter:
+ procedure_args.update({'function' : backend.perimeter3d})
+ elif length:
+ procedure_args.update({'function' : backend.length3d})
+
+ # Setting up the settings for `_spatial_attribute`.
+ s = {'select_field' : DistanceField(dist_att),
+ 'setup' : False,
+ 'geo_field' : geo_field,
+ 'procedure_args' : procedure_args,
+ 'procedure_fmt' : procedure_fmt,
+ }
+ if geom_args:
+ s['geom_args'] = ('geom',)
+ s['procedure_args']['geom'] = geom
+ elif geom:
+ # The geometry is passed in as a parameter because we handled
+ # transformation conditions in this routine.
+ s['select_params'] = [backend.Adapter(geom)]
+ return self._spatial_attribute(func, s, **kwargs)
+
+ def _geom_attribute(self, func, tolerance=0.05, **kwargs):
+ """
+ DRY routine for setting up a GeoQuerySet method that attaches a
+ Geometry attribute (e.g., `centroid`, `point_on_surface`).
+ """
+ s = {'select_field' : GeomField(),}
+ if connections[self.db].ops.oracle:
+ s['procedure_fmt'] = '%(geo_col)s,%(tolerance)s'
+ s['procedure_args'] = {'tolerance' : tolerance}
+ return self._spatial_attribute(func, s, **kwargs)
+
+ def _geomset_attribute(self, func, geom, tolerance=0.05, **kwargs):
+ """
+ DRY routine for setting up a GeoQuerySet method that attaches a
+ Geometry attribute and takes a Geoemtry parameter. This is used
+ for geometry set-like operations (e.g., intersection, difference,
+ union, sym_difference).
+ """
+ s = {'geom_args' : ('geom',),
+ 'select_field' : GeomField(),
+ 'procedure_fmt' : '%(geo_col)s,%(geom)s',
+ 'procedure_args' : {'geom' : geom},
+ }
+ if connections[self.db].ops.oracle:
+ s['procedure_fmt'] += ',%(tolerance)s'
+ s['procedure_args']['tolerance'] = tolerance
+ return self._spatial_attribute(func, s, **kwargs)
+
+ def _geocol_select(self, geo_field, field_name):
+ """
+ Helper routine for constructing the SQL to select the geographic
+ column. Takes into account if the geographic field is in a
+ ForeignKey relation to the current model.
+ """
+ opts = self.model._meta
+ if not geo_field in opts.fields:
+ # Is this operation going to be on a related geographic field?
+ # If so, it'll have to be added to the select related information
+ # (e.g., if 'location__point' was given as the field name).
+ self.query.add_select_related([field_name])
+ compiler = self.query.get_compiler(self.db)
+ compiler.pre_sql_setup()
+ rel_table, rel_col = self.query.related_select_cols[self.query.related_select_fields.index(geo_field)]
+ return compiler._field_column(geo_field, rel_table)
+ elif not geo_field in opts.local_fields:
+ # This geographic field is inherited from another model, so we have to
+ # use the db table for the _parent_ model instead.
+ tmp_fld, parent_model, direct, m2m = opts.get_field_by_name(geo_field.name)
+ return self.query.get_compiler(self.db)._field_column(geo_field, parent_model._meta.db_table)
+ else:
+ return self.query.get_compiler(self.db)._field_column(geo_field)
+
+class GeoValuesQuerySet(ValuesQuerySet):
+ def __init__(self, *args, **kwargs):
+ super(GeoValuesQuerySet, self).__init__(*args, **kwargs)
+ # This flag tells `resolve_columns` to run the values through
+ # `convert_values`. This ensures that Geometry objects instead
+ # of string values are returned with `values()` or `values_list()`.
+ self.query.geo_values = True
+
+class GeoValuesListQuerySet(GeoValuesQuerySet, ValuesListQuerySet):
+ pass
diff --git a/parts/django/django/contrib/gis/db/models/sql/__init__.py b/parts/django/django/contrib/gis/db/models/sql/__init__.py
new file mode 100644
index 0000000..38d9507
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/sql/__init__.py
@@ -0,0 +1,3 @@
+from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
+from django.contrib.gis.db.models.sql.query import GeoQuery
+from django.contrib.gis.db.models.sql.where import GeoWhereNode
diff --git a/parts/django/django/contrib/gis/db/models/sql/aggregates.py b/parts/django/django/contrib/gis/db/models/sql/aggregates.py
new file mode 100644
index 0000000..fed2a2e
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/sql/aggregates.py
@@ -0,0 +1,61 @@
+from django.db.models.sql.aggregates import *
+from django.contrib.gis.db.models.fields import GeometryField
+from django.contrib.gis.db.models.sql.conversion import GeomField
+
+class GeoAggregate(Aggregate):
+ # Default SQL template for spatial aggregates.
+ sql_template = '%(function)s(%(field)s)'
+
+ # Conversion class, if necessary.
+ conversion_class = None
+
+ # Flags for indicating the type of the aggregate.
+ is_extent = False
+
+ def __init__(self, col, source=None, is_summary=False, tolerance=0.05, **extra):
+ super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
+
+ # Required by some Oracle aggregates.
+ self.tolerance = tolerance
+
+ # Can't use geographic aggregates on non-geometry fields.
+ if not isinstance(self.source, GeometryField):
+ raise ValueError('Geospatial aggregates only allowed on geometry fields.')
+
+ def as_sql(self, qn, connection):
+ "Return the aggregate, rendered as SQL."
+
+ if connection.ops.oracle:
+ self.extra['tolerance'] = self.tolerance
+
+ if hasattr(self.col, 'as_sql'):
+ field_name = self.col.as_sql(qn, connection)
+ elif isinstance(self.col, (list, tuple)):
+ field_name = '.'.join([qn(c) for c in self.col])
+ else:
+ field_name = self.col
+
+ sql_template, sql_function = connection.ops.spatial_aggregate_sql(self)
+
+ params = {
+ 'function': sql_function,
+ 'field': field_name
+ }
+ params.update(self.extra)
+
+ return sql_template % params
+
+class Collect(GeoAggregate):
+ pass
+
+class Extent(GeoAggregate):
+ is_extent = '2D'
+
+class Extent3D(GeoAggregate):
+ is_extent = '3D'
+
+class MakeLine(GeoAggregate):
+ pass
+
+class Union(GeoAggregate):
+ pass
diff --git a/parts/django/django/contrib/gis/db/models/sql/compiler.py b/parts/django/django/contrib/gis/db/models/sql/compiler.py
new file mode 100644
index 0000000..dea0fd3
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/sql/compiler.py
@@ -0,0 +1,278 @@
+from itertools import izip
+from django.db.backends.util import truncate_name
+from django.db.models.sql import compiler
+from django.db.models.sql.constants import TABLE_NAME
+from django.db.models.sql.query import get_proxied_model
+
+SQLCompiler = compiler.SQLCompiler
+
+class GeoSQLCompiler(compiler.SQLCompiler):
+
+ def get_columns(self, with_aliases=False):
+ """
+ Return the list of columns to use in the select statement. If no
+ columns have been specified, returns all columns relating to fields in
+ the model.
+
+ If 'with_aliases' is true, any column names that are duplicated
+ (without the table names) are given unique aliases. This is needed in
+ some cases to avoid ambiguitity with nested queries.
+
+ This routine is overridden from Query to handle customized selection of
+ geometry columns.
+ """
+ qn = self.quote_name_unless_alias
+ qn2 = self.connection.ops.quote_name
+ result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
+ for alias, col in self.query.extra_select.iteritems()]
+ aliases = set(self.query.extra_select.keys())
+ if with_aliases:
+ col_aliases = aliases.copy()
+ else:
+ col_aliases = set()
+ if self.query.select:
+ only_load = self.deferred_to_columns()
+ # This loop customized for GeoQuery.
+ for col, field in izip(self.query.select, self.query.select_fields):
+ if isinstance(col, (list, tuple)):
+ alias, column = col
+ table = self.query.alias_map[alias][TABLE_NAME]
+ if table in only_load and col not in only_load[table]:
+ continue
+ r = self.get_field_select(field, alias, column)
+ if with_aliases:
+ if col[1] in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (r, c_alias))
+ aliases.add(c_alias)
+ col_aliases.add(c_alias)
+ else:
+ result.append('%s AS %s' % (r, qn2(col[1])))
+ aliases.add(r)
+ col_aliases.add(col[1])
+ else:
+ result.append(r)
+ aliases.add(r)
+ col_aliases.add(col[1])
+ else:
+ result.append(col.as_sql(qn, self.connection))
+
+ if hasattr(col, 'alias'):
+ aliases.add(col.alias)
+ col_aliases.add(col.alias)
+
+ elif self.query.default_cols:
+ cols, new_aliases = self.get_default_columns(with_aliases,
+ col_aliases)
+ result.extend(cols)
+ aliases.update(new_aliases)
+
+ max_name_length = self.connection.ops.max_name_length()
+ result.extend([
+ '%s%s' % (
+ self.get_extra_select_format(alias) % aggregate.as_sql(qn, self.connection),
+ alias is not None
+ and ' AS %s' % qn(truncate_name(alias, max_name_length))
+ or ''
+ )
+ for alias, aggregate in self.query.aggregate_select.items()
+ ])
+
+ # This loop customized for GeoQuery.
+ for (table, col), field in izip(self.query.related_select_cols, self.query.related_select_fields):
+ r = self.get_field_select(field, table, col)
+ if with_aliases and col in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (r, c_alias))
+ aliases.add(c_alias)
+ col_aliases.add(c_alias)
+ else:
+ result.append(r)
+ aliases.add(r)
+ col_aliases.add(col)
+
+ self._select_aliases = aliases
+ return result
+
+ def get_default_columns(self, with_aliases=False, col_aliases=None,
+ start_alias=None, opts=None, as_pairs=False, local_only=False):
+ """
+ Computes the default columns for selecting every field in the base
+ model. Will sometimes be called to pull in related models (e.g. via
+ select_related), in which case "opts" and "start_alias" will be given
+ to provide a starting point for the traversal.
+
+ Returns a list of strings, quoted appropriately for use in SQL
+ directly, as well as a set of aliases used in the select statement (if
+ 'as_pairs' is True, returns a list of (alias, col_name) pairs instead
+ of strings as the first component and None as the second component).
+
+ This routine is overridden from Query to handle customized selection of
+ geometry columns.
+ """
+ result = []
+ if opts is None:
+ opts = self.query.model._meta
+ aliases = set()
+ only_load = self.deferred_to_columns()
+ # Skip all proxy to the root proxied model
+ proxied_model = get_proxied_model(opts)
+
+ if start_alias:
+ seen = {None: start_alias}
+ for field, model in opts.get_fields_with_model():
+ if local_only and model is not None:
+ continue
+ if start_alias:
+ try:
+ alias = seen[model]
+ except KeyError:
+ if model is proxied_model:
+ alias = start_alias
+ else:
+ link_field = opts.get_ancestor_link(model)
+ alias = self.query.join((start_alias, model._meta.db_table,
+ link_field.column, model._meta.pk.column))
+ seen[model] = alias
+ else:
+ # If we're starting from the base model of the queryset, the
+ # aliases will have already been set up in pre_sql_setup(), so
+ # we can save time here.
+ alias = self.query.included_inherited_models[model]
+ table = self.query.alias_map[alias][TABLE_NAME]
+ if table in only_load and field.column not in only_load[table]:
+ continue
+ if as_pairs:
+ result.append((alias, field.column))
+ aliases.add(alias)
+ continue
+ # This part of the function is customized for GeoQuery. We
+ # see if there was any custom selection specified in the
+ # dictionary, and set up the selection format appropriately.
+ field_sel = self.get_field_select(field, alias)
+ if with_aliases and field.column in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (field_sel, c_alias))
+ col_aliases.add(c_alias)
+ aliases.add(c_alias)
+ else:
+ r = field_sel
+ result.append(r)
+ aliases.add(r)
+ if with_aliases:
+ col_aliases.add(field.column)
+ return result, aliases
+
+ def resolve_columns(self, row, fields=()):
+ """
+ This routine is necessary so that distances and geometries returned
+ from extra selection SQL get resolved appropriately into Python
+ objects.
+ """
+ values = []
+ aliases = self.query.extra_select.keys()
+ if self.query.aggregates:
+ # If we have an aggregate annotation, must extend the aliases
+ # so their corresponding row values are included.
+ aliases.extend([None for i in xrange(len(self.query.aggregates))])
+
+ # Have to set a starting row number offset that is used for
+ # determining the correct starting row index -- needed for
+ # doing pagination with Oracle.
+ rn_offset = 0
+ if self.connection.ops.oracle:
+ if self.query.high_mark is not None or self.query.low_mark: rn_offset = 1
+ index_start = rn_offset + len(aliases)
+
+ # Converting any extra selection values (e.g., geometries and
+ # distance objects added by GeoQuerySet methods).
+ values = [self.query.convert_values(v,
+ self.query.extra_select_fields.get(a, None),
+ self.connection)
+ for v, a in izip(row[rn_offset:index_start], aliases)]
+ if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
+ # We resolve the rest of the columns if we're on Oracle or if
+ # the `geo_values` attribute is defined.
+ for value, field in map(None, row[index_start:], fields):
+ values.append(self.query.convert_values(value, field, connection=self.connection))
+ else:
+ values.extend(row[index_start:])
+ return tuple(values)
+
+ #### Routines unique to GeoQuery ####
+ def get_extra_select_format(self, alias):
+ sel_fmt = '%s'
+ if alias in self.query.custom_select:
+ sel_fmt = sel_fmt % self.query.custom_select[alias]
+ return sel_fmt
+
+ def get_field_select(self, field, alias=None, column=None):
+ """
+ Returns the SELECT SQL string for the given field. Figures out
+ if any custom selection SQL is needed for the column The `alias`
+ keyword may be used to manually specify the database table where
+ the column exists, if not in the model associated with this
+ `GeoQuery`. Similarly, `column` may be used to specify the exact
+ column name, rather than using the `column` attribute on `field`.
+ """
+ sel_fmt = self.get_select_format(field)
+ if field in self.query.custom_select:
+ field_sel = sel_fmt % self.query.custom_select[field]
+ else:
+ field_sel = sel_fmt % self._field_column(field, alias, column)
+ return field_sel
+
+ def get_select_format(self, fld):
+ """
+ Returns the selection format string, depending on the requirements
+ of the spatial backend. For example, Oracle and MySQL require custom
+ selection formats in order to retrieve geometries in OGC WKT. For all
+ other fields a simple '%s' format string is returned.
+ """
+ if self.connection.ops.select and hasattr(fld, 'geom_type'):
+ # This allows operations to be done on fields in the SELECT,
+ # overriding their values -- used by the Oracle and MySQL
+ # spatial backends to get database values as WKT, and by the
+ # `transform` method.
+ sel_fmt = self.connection.ops.select
+
+ # Because WKT doesn't contain spatial reference information,
+ # the SRID is prefixed to the returned WKT to ensure that the
+ # transformed geometries have an SRID different than that of the
+ # field -- this is only used by `transform` for Oracle and
+ # SpatiaLite backends.
+ if self.query.transformed_srid and ( self.connection.ops.oracle or
+ self.connection.ops.spatialite ):
+ sel_fmt = "'SRID=%d;'||%s" % (self.query.transformed_srid, sel_fmt)
+ else:
+ sel_fmt = '%s'
+ return sel_fmt
+
+ # Private API utilities, subject to change.
+ def _field_column(self, field, table_alias=None, column=None):
+ """
+ Helper function that returns the database column for the given field.
+ The table and column are returned (quoted) in the proper format, e.g.,
+ `"geoapp_city"."point"`. If `table_alias` is not specified, the
+ database table associated with the model of this `GeoQuery` will be
+ used. If `column` is specified, it will be used instead of the value
+ in `field.column`.
+ """
+ if table_alias is None: table_alias = self.query.model._meta.db_table
+ return "%s.%s" % (self.quote_name_unless_alias(table_alias),
+ self.connection.ops.quote_name(column or field.column))
+
+class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler):
+ pass
+
+class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler):
+ pass
+
+class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler):
+ pass
+
+class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler):
+ pass
+
+class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler):
+ pass
diff --git a/parts/django/django/contrib/gis/db/models/sql/conversion.py b/parts/django/django/contrib/gis/db/models/sql/conversion.py
new file mode 100644
index 0000000..941c257
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/sql/conversion.py
@@ -0,0 +1,27 @@
+"""
+This module holds simple classes used by GeoQuery.convert_values
+to convert geospatial values from the database.
+"""
+
+class BaseField(object):
+ empty_strings_allowed = True
+ def get_internal_type(self):
+ "Overloaded method so OracleQuery.convert_values doesn't balk."
+ return None
+
+class AreaField(BaseField):
+ "Wrapper for Area values."
+ def __init__(self, area_att):
+ self.area_att = area_att
+
+class DistanceField(BaseField):
+ "Wrapper for Distance values."
+ def __init__(self, distance_att):
+ self.distance_att = distance_att
+
+class GeomField(BaseField):
+ """
+ Wrapper for Geometry values. It is a lightweight alternative to
+ using GeometryField (which requires a SQL query upon instantiation).
+ """
+ pass
diff --git a/parts/django/django/contrib/gis/db/models/sql/query.py b/parts/django/django/contrib/gis/db/models/sql/query.py
new file mode 100644
index 0000000..c300dcd
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/sql/query.py
@@ -0,0 +1,119 @@
+from django.db import connections
+from django.db.models.query import sql
+
+from django.contrib.gis.db.models.fields import GeometryField
+from django.contrib.gis.db.models.sql import aggregates as gis_aggregates
+from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField
+from django.contrib.gis.db.models.sql.where import GeoWhereNode
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.measure import Area, Distance
+
+
+ALL_TERMS = dict([(x, None) for x in (
+ 'bbcontains', 'bboverlaps', 'contained', 'contains',
+ 'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint',
+ 'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte',
+ 'dwithin', 'equals', 'exact',
+ 'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within',
+ 'left', 'right', 'overlaps_left', 'overlaps_right',
+ 'overlaps_above', 'overlaps_below',
+ 'strictly_above', 'strictly_below'
+ )])
+ALL_TERMS.update(sql.constants.QUERY_TERMS)
+
+class GeoQuery(sql.Query):
+ """
+ A single spatial SQL query.
+ """
+ # Overridding the valid query terms.
+ query_terms = ALL_TERMS
+ aggregates_module = gis_aggregates
+
+ compiler = 'GeoSQLCompiler'
+
+ #### Methods overridden from the base Query class ####
+ def __init__(self, model, where=GeoWhereNode):
+ super(GeoQuery, self).__init__(model, where)
+ # The following attributes are customized for the GeoQuerySet.
+ # The GeoWhereNode and SpatialBackend classes contain backend-specific
+ # routines and functions.
+ self.custom_select = {}
+ self.transformed_srid = None
+ self.extra_select_fields = {}
+
+ def clone(self, *args, **kwargs):
+ obj = super(GeoQuery, self).clone(*args, **kwargs)
+ # Customized selection dictionary and transformed srid flag have
+ # to also be added to obj.
+ obj.custom_select = self.custom_select.copy()
+ obj.transformed_srid = self.transformed_srid
+ obj.extra_select_fields = self.extra_select_fields.copy()
+ return obj
+
+ def convert_values(self, value, field, connection):
+ """
+ Using the same routines that Oracle does we can convert our
+ extra selection objects into Geometry and Distance objects.
+ TODO: Make converted objects 'lazy' for less overhead.
+ """
+ if connection.ops.oracle:
+ # Running through Oracle's first.
+ value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
+
+ if value is None:
+ # Output from spatial function is NULL (e.g., called
+ # function on a geometry field with NULL value).
+ pass
+ elif isinstance(field, DistanceField):
+ # Using the field's distance attribute, can instantiate
+ # `Distance` with the right context.
+ value = Distance(**{field.distance_att : value})
+ elif isinstance(field, AreaField):
+ value = Area(**{field.area_att : value})
+ elif isinstance(field, (GeomField, GeometryField)) and value:
+ value = Geometry(value)
+ return value
+
+ def get_aggregation(self, using):
+ # Remove any aggregates marked for reduction from the subquery
+ # and move them to the outer AggregateQuery.
+ connection = connections[using]
+ for alias, aggregate in self.aggregate_select.items():
+ if isinstance(aggregate, gis_aggregates.GeoAggregate):
+ if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
+ self.extra_select_fields[alias] = GeomField()
+ return super(GeoQuery, self).get_aggregation(using)
+
+ def resolve_aggregate(self, value, aggregate, connection):
+ """
+ Overridden from GeoQuery's normalize to handle the conversion of
+ GeoAggregate objects.
+ """
+ if isinstance(aggregate, self.aggregates_module.GeoAggregate):
+ if aggregate.is_extent:
+ if aggregate.is_extent == '3D':
+ return connection.ops.convert_extent3d(value)
+ else:
+ return connection.ops.convert_extent(value)
+ else:
+ return connection.ops.convert_geom(value, aggregate.source)
+ else:
+ return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
+
+ # Private API utilities, subject to change.
+ def _geo_field(self, field_name=None):
+ """
+ Returns the first Geometry field encountered; or specified via the
+ `field_name` keyword. The `field_name` may be a string specifying
+ the geometry field on this GeoQuery's model, or a lookup string
+ to a geometry field via a ForeignKey relation.
+ """
+ if field_name is None:
+ # Incrementing until the first geographic field is found.
+ for fld in self.model._meta.fields:
+ if isinstance(fld, GeometryField): return fld
+ return False
+ else:
+ # Otherwise, check by the given field name -- which may be
+ # a lookup to a _related_ geographic field.
+ return GeoWhereNode._check_geo_field(self.model._meta, field_name)
diff --git a/parts/django/django/contrib/gis/db/models/sql/where.py b/parts/django/django/contrib/gis/db/models/sql/where.py
new file mode 100644
index 0000000..17c210b
--- /dev/null
+++ b/parts/django/django/contrib/gis/db/models/sql/where.py
@@ -0,0 +1,89 @@
+from django.db.models.fields import Field, FieldDoesNotExist
+from django.db.models.sql.constants import LOOKUP_SEP
+from django.db.models.sql.expressions import SQLEvaluator
+from django.db.models.sql.where import Constraint, WhereNode
+from django.contrib.gis.db.models.fields import GeometryField
+
+class GeoConstraint(Constraint):
+ """
+ This subclass overrides `process` to better handle geographic SQL
+ construction.
+ """
+ def __init__(self, init_constraint):
+ self.alias = init_constraint.alias
+ self.col = init_constraint.col
+ self.field = init_constraint.field
+
+ def process(self, lookup_type, value, connection):
+ if isinstance(value, SQLEvaluator):
+ # Make sure the F Expression destination field exists, and
+ # set an `srid` attribute with the same as that of the
+ # destination.
+ geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name)
+ if not geo_fld:
+ raise ValueError('No geographic field found in expression.')
+ value.srid = geo_fld.srid
+ db_type = self.field.db_type(connection=connection)
+ params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection)
+ return (self.alias, self.col, db_type), params
+
+class GeoWhereNode(WhereNode):
+ """
+ Used to represent the SQL where-clause for spatial databases --
+ these are tied to the GeoQuery class that created it.
+ """
+ def add(self, data, connector):
+ if isinstance(data, (list, tuple)):
+ obj, lookup_type, value = data
+ if ( isinstance(obj, Constraint) and
+ isinstance(obj.field, GeometryField) ):
+ data = (GeoConstraint(obj), lookup_type, value)
+ super(GeoWhereNode, self).add(data, connector)
+
+ def make_atom(self, child, qn, connection):
+ lvalue, lookup_type, value_annot, params_or_value = child
+ if isinstance(lvalue, GeoConstraint):
+ data, params = lvalue.process(lookup_type, params_or_value, connection)
+ spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
+ return spatial_sql, params
+ else:
+ return super(GeoWhereNode, self).make_atom(child, qn, connection)
+
+ @classmethod
+ def _check_geo_field(cls, opts, lookup):
+ """
+ Utility for checking the given lookup with the given model options.
+ The lookup is a string either specifying the geographic field, e.g.
+ 'point, 'the_geom', or a related lookup on a geographic field like
+ 'address__point'.
+
+ If a GeometryField exists according to the given lookup on the model
+ options, it will be returned. Otherwise returns None.
+ """
+ # This takes into account the situation where the lookup is a
+ # lookup to a related geographic field, e.g., 'address__point'.
+ field_list = lookup.split(LOOKUP_SEP)
+
+ # Reversing so list operates like a queue of related lookups,
+ # and popping the top lookup.
+ field_list.reverse()
+ fld_name = field_list.pop()
+
+ try:
+ geo_fld = opts.get_field(fld_name)
+ # If the field list is still around, then it means that the
+ # lookup was for a geometry field across a relationship --
+ # thus we keep on getting the related model options and the
+ # model field associated with the next field in the list
+ # until there's no more left.
+ while len(field_list):
+ opts = geo_fld.rel.to._meta
+ geo_fld = opts.get_field(field_list.pop())
+ except (FieldDoesNotExist, AttributeError):
+ return False
+
+ # Finally, make sure we got a Geographic field and return.
+ if isinstance(geo_fld, GeometryField):
+ return geo_fld
+ else:
+ return False
diff --git a/parts/django/django/contrib/gis/feeds.py b/parts/django/django/contrib/gis/feeds.py
new file mode 100644
index 0000000..4105ef7
--- /dev/null
+++ b/parts/django/django/contrib/gis/feeds.py
@@ -0,0 +1,135 @@
+from django.contrib.syndication.feeds import Feed as BaseFeed, FeedDoesNotExist
+from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
+
+class GeoFeedMixin(object):
+ """
+ This mixin provides the necessary routines for SyndicationFeed subclasses
+ to produce simple GeoRSS or W3C Geo elements.
+ """
+
+ 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, w3c_geo=False):
+ """
+ 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 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, w3c_geo=False):
+ """
+ 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, w3c_geo=w3c_geo)
+ 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 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, w3c_geo=w3c_geo)
+ else:
+ if w3c_geo: raise ValueError('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 ValueError('Geometry type "%s" not supported.' % geom.geom_type)
+
+### SyndicationFeed subclasses ###
+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 W3CGeoFeed(Rss201rev2Feed, GeoFeedMixin):
+ 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, w3c_geo=True)
+
+ def add_root_elements(self, handler):
+ super(W3CGeoFeed, self).add_root_elements(handler)
+ self.add_georss_element(handler, self.feed, w3c_geo=True)
+
+### Feed subclass ###
+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 feed_extra_kwargs(self, obj):
+ return {'geometry' : self.__get_dynamic_attr('geometry', obj)}
+
+ def item_extra_kwargs(self, item):
+ return {'geometry' : self.__get_dynamic_attr('item_geometry', item)}
diff --git a/parts/django/django/contrib/gis/forms/__init__.py b/parts/django/django/contrib/gis/forms/__init__.py
new file mode 100644
index 0000000..82971da
--- /dev/null
+++ b/parts/django/django/contrib/gis/forms/__init__.py
@@ -0,0 +1,2 @@
+from django.forms import *
+from django.contrib.gis.forms.fields import GeometryField
diff --git a/parts/django/django/contrib/gis/forms/fields.py b/parts/django/django/contrib/gis/forms/fields.py
new file mode 100644
index 0000000..f806dcb
--- /dev/null
+++ b/parts/django/django/contrib/gis/forms/fields.py
@@ -0,0 +1,67 @@
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+
+# While this couples the geographic forms to the GEOS library,
+# it decouples from database (by not importing SpatialBackend).
+from django.contrib.gis.geos import GEOSGeometry
+
+class GeometryField(forms.Field):
+ """
+ This is the basic form field for a Geometry. Any textual input that is
+ accepted by GEOSGeometry is accepted by this form. By default,
+ this includes WKT, HEXEWKB, WKB (in a buffer), and GeoJSON.
+ """
+ widget = forms.Textarea
+
+ default_error_messages = {
+ 'no_geom' : _(u'No geometry value provided.'),
+ 'invalid_geom' : _(u'Invalid geometry value.'),
+ 'invalid_geom_type' : _(u'Invalid geometry type.'),
+ 'transform_error' : _(u'An error occurred when transforming the geometry '
+ 'to the SRID of the geometry form field.'),
+ }
+
+ def __init__(self, **kwargs):
+ # Pop out attributes from the database field, or use sensible
+ # defaults (e.g., allow None).
+ self.srid = kwargs.pop('srid', None)
+ self.geom_type = kwargs.pop('geom_type', 'GEOMETRY')
+ self.null = kwargs.pop('null', True)
+ super(GeometryField, self).__init__(**kwargs)
+
+ def clean(self, value):
+ """
+ Validates that the input value can be converted to a Geometry
+ object (which is returned). A ValidationError is raised if
+ the value cannot be instantiated as a Geometry.
+ """
+ if not value:
+ if self.null and not self.required:
+ # The geometry column allows NULL and is not required.
+ return None
+ else:
+ raise forms.ValidationError(self.error_messages['no_geom'])
+
+ # Trying to create a Geometry object from the form value.
+ try:
+ geom = GEOSGeometry(value)
+ except:
+ raise forms.ValidationError(self.error_messages['invalid_geom'])
+
+ # Ensuring that the geometry is of the correct type (indicated
+ # using the OGC string label).
+ if str(geom.geom_type).upper() != self.geom_type and not self.geom_type == 'GEOMETRY':
+ raise forms.ValidationError(self.error_messages['invalid_geom_type'])
+
+ # Transforming the geometry if the SRID was set.
+ if self.srid:
+ if not geom.srid:
+ # Should match that of the field if not given.
+ geom.srid = self.srid
+ elif self.srid != -1 and self.srid != geom.srid:
+ try:
+ geom.transform(self.srid)
+ except:
+ raise forms.ValidationError(self.error_messages['transform_error'])
+
+ return geom
diff --git a/parts/django/django/contrib/gis/gdal/LICENSE b/parts/django/django/contrib/gis/gdal/LICENSE
new file mode 100644
index 0000000..30d410e
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2007-2009, Justin Bronn
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of OGRGeometry nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/parts/django/django/contrib/gis/gdal/__init__.py b/parts/django/django/contrib/gis/gdal/__init__.py
new file mode 100644
index 0000000..cc47ae9
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/__init__.py
@@ -0,0 +1,54 @@
+"""
+ This module houses ctypes interfaces for GDAL objects. The following GDAL
+ objects are supported:
+
+ CoordTransform: Used for coordinate transformations from one spatial
+ reference system to another.
+
+ Driver: Wraps an OGR data source driver.
+
+ DataSource: Wrapper for the OGR data source object, supports
+ OGR-supported data sources.
+
+ Envelope: A ctypes structure for bounding boxes (GDAL library
+ not required).
+
+ OGRGeometry: Object for accessing OGR Geometry functionality.
+
+ OGRGeomType: A class for representing the different OGR Geometry
+ types (GDAL library not required).
+
+ SpatialReference: Represents OSR Spatial Reference objects.
+
+ The GDAL library will be imported from the system path using the default
+ library name for the current OS. The default library path may be overridden
+ by setting `GDAL_LIBRARY_PATH` in your settings with the path to the GDAL C
+ library on your system.
+
+ GDAL links to a large number of external libraries that consume RAM when
+ loaded. Thus, it may desirable to disable GDAL on systems with limited
+ RAM resources -- this may be accomplished by setting `GDAL_LIBRARY_PATH`
+ to a non-existant file location (e.g., `GDAL_LIBRARY_PATH='/null/path'`;
+ setting to None/False/'' will not work as a string must be given).
+"""
+# Attempting to import objects that depend on the GDAL library. The
+# HAS_GDAL flag will be set to True if the library is present on
+# the system.
+try:
+ from django.contrib.gis.gdal.driver import Driver
+ from django.contrib.gis.gdal.datasource import DataSource
+ from django.contrib.gis.gdal.libgdal import gdal_version, gdal_full_version, gdal_release_date, GEOJSON, GDAL_VERSION
+ from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
+ from django.contrib.gis.gdal.geometries import OGRGeometry
+ HAS_GDAL = True
+except:
+ HAS_GDAL, GEOJSON = False, False
+
+try:
+ from django.contrib.gis.gdal.envelope import Envelope
+except ImportError:
+ # No ctypes, but don't raise an exception.
+ pass
+
+from django.contrib.gis.gdal.error import check_err, OGRException, OGRIndexError, SRSException
+from django.contrib.gis.gdal.geomtype import OGRGeomType
diff --git a/parts/django/django/contrib/gis/gdal/base.py b/parts/django/django/contrib/gis/gdal/base.py
new file mode 100644
index 0000000..f9455c7
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/base.py
@@ -0,0 +1,35 @@
+from ctypes import c_void_p
+from types import NoneType
+from django.contrib.gis.gdal.error import GDALException
+
+class GDALBase(object):
+ """
+ Base object for GDAL objects that has a pointer access property
+ that controls access to the underlying C pointer.
+ """
+ # Initially the pointer is NULL.
+ _ptr = None
+
+ # Default allowed pointer type.
+ ptr_type = c_void_p
+
+ # Pointer access property.
+ def _get_ptr(self):
+ # Raise an exception if the pointer isn't valid don't
+ # want to be passing NULL pointers to routines --
+ # that's very bad.
+ if self._ptr: return self._ptr
+ else: raise GDALException('GDAL %s pointer no longer valid.' % self.__class__.__name__)
+
+ def _set_ptr(self, ptr):
+ # Only allow the pointer to be set with pointers of the
+ # compatible type or None (NULL).
+ if isinstance(ptr, (int, long)):
+ self._ptr = self.ptr_type(ptr)
+ elif isinstance(ptr, (self.ptr_type, NoneType)):
+ self._ptr = ptr
+ else:
+ raise TypeError('Incompatible pointer type')
+
+ ptr = property(_get_ptr, _set_ptr)
+
diff --git a/parts/django/django/contrib/gis/gdal/datasource.py b/parts/django/django/contrib/gis/gdal/datasource.py
new file mode 100644
index 0000000..7db5fd9
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/datasource.py
@@ -0,0 +1,128 @@
+"""
+ DataSource is a wrapper for the OGR Data Source object, which provides
+ an interface for reading vector geometry data from many different file
+ formats (including ESRI shapefiles).
+
+ When instantiating a DataSource object, use the filename of a
+ GDAL-supported data source. For example, a SHP file or a
+ TIGER/Line file from the government.
+
+ The ds_driver keyword is used internally when a ctypes pointer
+ is passed in directly.
+
+ Example:
+ ds = DataSource('/home/foo/bar.shp')
+ for layer in ds:
+ for feature in layer:
+ # Getting the geometry for the feature.
+ g = feature.geom
+
+ # Getting the 'description' field for the feature.
+ desc = feature['description']
+
+ # We can also increment through all of the fields
+ # attached to this feature.
+ for field in feature:
+ # Get the name of the field (e.g. 'description')
+ nm = field.name
+
+ # Get the type (integer) of the field, e.g. 0 => OFTInteger
+ t = field.type
+
+ # Returns the value the field; OFTIntegers return ints,
+ # OFTReal returns floats, all else returns string.
+ val = field.value
+"""
+# ctypes prerequisites.
+from ctypes import byref, c_void_p
+
+# The GDAL C library, OGR exceptions, and the Layer object.
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.driver import Driver
+from django.contrib.gis.gdal.error import OGRException, OGRIndexError
+from django.contrib.gis.gdal.layer import Layer
+
+# Getting the ctypes prototypes for the DataSource.
+from django.contrib.gis.gdal.prototypes import ds as capi
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_DS_* routines are relevant here.
+class DataSource(GDALBase):
+ "Wraps an OGR Data Source object."
+
+ #### Python 'magic' routines ####
+ def __init__(self, ds_input, ds_driver=False, write=False):
+ # The write flag.
+ if write:
+ self._write = 1
+ else:
+ self._write = 0
+
+ # Registering all the drivers, this needs to be done
+ # _before_ we try to open up a data source.
+ if not capi.get_driver_count():
+ capi.register_all()
+
+ if isinstance(ds_input, basestring):
+ # The data source driver is a void pointer.
+ ds_driver = Driver.ptr_type()
+ try:
+ # OGROpen will auto-detect the data source type.
+ ds = capi.open_ds(ds_input, self._write, byref(ds_driver))
+ except OGRException:
+ # Making the error message more clear rather than something
+ # like "Invalid pointer returned from OGROpen".
+ raise OGRException('Could not open the datasource at "%s"' % ds_input)
+ elif isinstance(ds_input, self.ptr_type) and isinstance(ds_driver, Driver.ptr_type):
+ ds = ds_input
+ else:
+ raise OGRException('Invalid data source input type: %s' % type(ds_input))
+
+ if bool(ds):
+ self.ptr = ds
+ self.driver = Driver(ds_driver)
+ else:
+ # Raise an exception if the returned pointer is NULL
+ raise OGRException('Invalid data source file "%s"' % ds_input)
+
+ def __del__(self):
+ "Destroys this DataStructure object."
+ if self._ptr: capi.destroy_ds(self._ptr)
+
+ def __iter__(self):
+ "Allows for iteration over the layers in a data source."
+ for i in xrange(self.layer_count):
+ yield self[i]
+
+ def __getitem__(self, index):
+ "Allows use of the index [] operator to get a layer at the index."
+ if isinstance(index, basestring):
+ l = capi.get_layer_by_name(self.ptr, index)
+ if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index)
+ elif isinstance(index, int):
+ if index < 0 or index >= self.layer_count:
+ raise OGRIndexError('index out of range')
+ l = capi.get_layer(self._ptr, index)
+ else:
+ raise TypeError('Invalid index type: %s' % type(index))
+ return Layer(l, self)
+
+ def __len__(self):
+ "Returns the number of layers within the data source."
+ return self.layer_count
+
+ def __str__(self):
+ "Returns OGR GetName and Driver for the Data Source."
+ return '%s (%s)' % (self.name, str(self.driver))
+
+ @property
+ def layer_count(self):
+ "Returns the number of layers in the data source."
+ return capi.get_layer_count(self._ptr)
+
+ @property
+ def name(self):
+ "Returns the name of the data source."
+ return capi.get_ds_name(self._ptr)
diff --git a/parts/django/django/contrib/gis/gdal/driver.py b/parts/django/django/contrib/gis/gdal/driver.py
new file mode 100644
index 0000000..1753db2
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/driver.py
@@ -0,0 +1,65 @@
+# prerequisites imports
+from ctypes import c_void_p
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.error import OGRException
+from django.contrib.gis.gdal.prototypes import ds as capi
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_Dr_* routines are relevant here.
+class Driver(GDALBase):
+ "Wraps an OGR Data Source Driver."
+
+ # Case-insensitive aliases for OGR Drivers.
+ _alias = {'esri' : 'ESRI Shapefile',
+ 'shp' : 'ESRI Shapefile',
+ 'shape' : 'ESRI Shapefile',
+ 'tiger' : 'TIGER',
+ 'tiger/line' : 'TIGER',
+ }
+
+ def __init__(self, dr_input):
+ "Initializes an OGR driver on either a string or integer input."
+
+ if isinstance(dr_input, basestring):
+ # If a string name of the driver was passed in
+ self._register()
+
+ # Checking the alias dictionary (case-insensitive) to see if an alias
+ # exists for the given driver.
+ if dr_input.lower() in self._alias:
+ name = self._alias[dr_input.lower()]
+ else:
+ name = dr_input
+
+ # Attempting to get the OGR driver by the string name.
+ dr = capi.get_driver_by_name(name)
+ elif isinstance(dr_input, int):
+ self._register()
+ dr = capi.get_driver(dr_input)
+ elif isinstance(dr_input, c_void_p):
+ dr = dr_input
+ else:
+ raise OGRException('Unrecognized input type for OGR Driver: %s' % str(type(dr_input)))
+
+ # Making sure we get a valid pointer to the OGR Driver
+ if not dr:
+ raise OGRException('Could not initialize OGR Driver on input: %s' % str(dr_input))
+ self.ptr = dr
+
+ def __str__(self):
+ "Returns the string name of the OGR Driver."
+ return capi.get_driver_name(self.ptr)
+
+ def _register(self):
+ "Attempts to register all the data source drivers."
+ # Only register all if the driver count is 0 (or else all drivers
+ # will be registered over and over again)
+ if not self.driver_count: capi.register_all()
+
+ # Driver properties
+ @property
+ def driver_count(self):
+ "Returns the number of OGR data source drivers registered."
+ return capi.get_driver_count()
diff --git a/parts/django/django/contrib/gis/gdal/envelope.py b/parts/django/django/contrib/gis/gdal/envelope.py
new file mode 100644
index 0000000..0e6aa0e
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/envelope.py
@@ -0,0 +1,175 @@
+"""
+ The GDAL/OGR library uses an Envelope structure to hold the bounding
+ box information for a geometry. The envelope (bounding box) contains
+ two pairs of coordinates, one for the lower left coordinate and one
+ for the upper right coordinate:
+
+ +----------o Upper right; (max_x, max_y)
+ | |
+ | |
+ | |
+ Lower left (min_x, min_y) o----------+
+"""
+from ctypes import Structure, c_double
+from django.contrib.gis.gdal.error import OGRException
+
+# The OGR definition of an Envelope is a C structure containing four doubles.
+# See the 'ogr_core.h' source file for more information:
+# http://www.gdal.org/ogr/ogr__core_8h-source.html
+class OGREnvelope(Structure):
+ "Represents the OGREnvelope C Structure."
+ _fields_ = [("MinX", c_double),
+ ("MaxX", c_double),
+ ("MinY", c_double),
+ ("MaxY", c_double),
+ ]
+
+class Envelope(object):
+ """
+ The Envelope object is a C structure that contains the minimum and
+ maximum X, Y coordinates for a rectangle bounding box. The naming
+ of the variables is compatible with the OGR Envelope structure.
+ """
+
+ def __init__(self, *args):
+ """
+ The initialization function may take an OGREnvelope structure, 4-element
+ tuple or list, or 4 individual arguments.
+ """
+
+ if len(args) == 1:
+ if isinstance(args[0], OGREnvelope):
+ # OGREnvelope (a ctypes Structure) was passed in.
+ self._envelope = args[0]
+ elif isinstance(args[0], (tuple, list)):
+ # A tuple was passed in.
+ if len(args[0]) != 4:
+ raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0]))
+ else:
+ self._from_sequence(args[0])
+ else:
+ raise TypeError('Incorrect type of argument: %s' % str(type(args[0])))
+ elif len(args) == 4:
+ # Individiual parameters passed in.
+ # Thanks to ww for the help
+ self._from_sequence(map(float, args))
+ else:
+ raise OGRException('Incorrect number (%d) of arguments.' % len(args))
+
+ # Checking the x,y coordinates
+ if self.min_x > self.max_x:
+ raise OGRException('Envelope minimum X > maximum X.')
+ if self.min_y > self.max_y:
+ raise OGRException('Envelope minimum Y > maximum Y.')
+
+ def __eq__(self, other):
+ """
+ Returns True if the envelopes are equivalent; can compare against
+ other Envelopes and 4-tuples.
+ """
+ if isinstance(other, Envelope):
+ return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \
+ (self.max_x == other.max_x) and (self.max_y == other.max_y)
+ elif isinstance(other, tuple) and len(other) == 4:
+ return (self.min_x == other[0]) and (self.min_y == other[1]) and \
+ (self.max_x == other[2]) and (self.max_y == other[3])
+ else:
+ raise OGRException('Equivalence testing only works with other Envelopes.')
+
+ def __str__(self):
+ "Returns a string representation of the tuple."
+ return str(self.tuple)
+
+ def _from_sequence(self, seq):
+ "Initializes the C OGR Envelope structure from the given sequence."
+ self._envelope = OGREnvelope()
+ self._envelope.MinX = seq[0]
+ self._envelope.MinY = seq[1]
+ self._envelope.MaxX = seq[2]
+ self._envelope.MaxY = seq[3]
+
+ def expand_to_include(self, *args):
+ """
+ Modifies the envelope to expand to include the boundaries of
+ the passed-in 2-tuple (a point), 4-tuple (an extent) or
+ envelope.
+ """
+ # We provide a number of different signatures for this method,
+ # and the logic here is all about converting them into a
+ # 4-tuple single parameter which does the actual work of
+ # expanding the envelope.
+ if len(args) == 1:
+ if isinstance(args[0], Envelope):
+ return self.expand_to_include(args[0].tuple)
+ elif hasattr(args[0], 'x') and hasattr(args[0], 'y'):
+ return self.expand_to_include(args[0].x, args[0].y, args[0].x, args[0].y)
+ elif isinstance(args[0], (tuple, list)):
+ # A tuple was passed in.
+ if len(args[0]) == 2:
+ return self.expand_to_include((args[0][0], args[0][1], args[0][0], args[0][1]))
+ elif len(args[0]) == 4:
+ (minx, miny, maxx, maxy) = args[0]
+ if minx < self._envelope.MinX:
+ self._envelope.MinX = minx
+ if miny < self._envelope.MinY:
+ self._envelope.MinY = miny
+ if maxx > self._envelope.MaxX:
+ self._envelope.MaxX = maxx
+ if maxy > self._envelope.MaxY:
+ self._envelope.MaxY = maxy
+ else:
+ raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0]))
+ else:
+ raise TypeError('Incorrect type of argument: %s' % str(type(args[0])))
+ elif len(args) == 2:
+ # An x and an y parameter were passed in
+ return self.expand_to_include((args[0], args[1], args[0], args[1]))
+ elif len(args) == 4:
+ # Individiual parameters passed in.
+ return self.expand_to_include(args)
+ else:
+ raise OGRException('Incorrect number (%d) of arguments.' % len(args[0]))
+
+ @property
+ def min_x(self):
+ "Returns the value of the minimum X coordinate."
+ return self._envelope.MinX
+
+ @property
+ def min_y(self):
+ "Returns the value of the minimum Y coordinate."
+ return self._envelope.MinY
+
+ @property
+ def max_x(self):
+ "Returns the value of the maximum X coordinate."
+ return self._envelope.MaxX
+
+ @property
+ def max_y(self):
+ "Returns the value of the maximum Y coordinate."
+ return self._envelope.MaxY
+
+ @property
+ def ur(self):
+ "Returns the upper-right coordinate."
+ return (self.max_x, self.max_y)
+
+ @property
+ def ll(self):
+ "Returns the lower-left coordinate."
+ return (self.min_x, self.min_y)
+
+ @property
+ def tuple(self):
+ "Returns a tuple representing the envelope."
+ return (self.min_x, self.min_y, self.max_x, self.max_y)
+
+ @property
+ def wkt(self):
+ "Returns WKT representing a Polygon for this envelope."
+ # TODO: Fix significant figures.
+ return 'POLYGON((%s %s,%s %s,%s %s,%s %s,%s %s))' % \
+ (self.min_x, self.min_y, self.min_x, self.max_y,
+ self.max_x, self.max_y, self.max_x, self.min_y,
+ self.min_x, self.min_y)
diff --git a/parts/django/django/contrib/gis/gdal/error.py b/parts/django/django/contrib/gis/gdal/error.py
new file mode 100644
index 0000000..58ca891
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/error.py
@@ -0,0 +1,42 @@
+"""
+ This module houses the OGR & SRS Exception objects, and the
+ check_err() routine which checks the status code returned by
+ OGR methods.
+"""
+#### OGR & SRS Exceptions ####
+class GDALException(Exception): pass
+class OGRException(Exception): pass
+class SRSException(Exception): pass
+class OGRIndexError(OGRException, KeyError):
+ """
+ This exception is raised when an invalid index is encountered, and has
+ the 'silent_variable_feature' attribute set to true. This ensures that
+ django's templates proceed to use the next lookup type gracefully when
+ an Exception is raised. Fixes ticket #4740.
+ """
+ silent_variable_failure = True
+
+#### OGR error checking codes and routine ####
+
+# OGR Error Codes
+OGRERR_DICT = { 1 : (OGRException, 'Not enough data.'),
+ 2 : (OGRException, 'Not enough memory.'),
+ 3 : (OGRException, 'Unsupported geometry type.'),
+ 4 : (OGRException, 'Unsupported operation.'),
+ 5 : (OGRException, 'Corrupt data.'),
+ 6 : (OGRException, 'OGR failure.'),
+ 7 : (SRSException, 'Unsupported SRS.'),
+ 8 : (OGRException, 'Invalid handle.'),
+ }
+OGRERR_NONE = 0
+
+def check_err(code):
+ "Checks the given OGRERR, and raises an exception where appropriate."
+
+ if code == OGRERR_NONE:
+ return
+ elif code in OGRERR_DICT:
+ e, msg = OGRERR_DICT[code]
+ raise e, msg
+ else:
+ raise OGRException('Unknown error code: "%s"' % code)
diff --git a/parts/django/django/contrib/gis/gdal/feature.py b/parts/django/django/contrib/gis/gdal/feature.py
new file mode 100644
index 0000000..b5c173a
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/feature.py
@@ -0,0 +1,110 @@
+# The GDAL C library, OGR exception, and the Field object
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.error import OGRException, OGRIndexError
+from django.contrib.gis.gdal.field import Field
+from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType
+from django.contrib.gis.gdal.srs import SpatialReference
+
+# ctypes function prototypes
+from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_F_* routines are relevant here.
+class Feature(GDALBase):
+ "A class that wraps an OGR Feature, needs to be instantiated from a Layer object."
+
+ #### Python 'magic' routines ####
+ def __init__(self, feat, fdefn):
+ "Initializes on the pointers for the feature and the layer definition."
+ if not feat or not fdefn:
+ raise OGRException('Cannot create OGR Feature, invalid pointer given.')
+ self.ptr = feat
+ self._fdefn = fdefn
+
+ def __del__(self):
+ "Releases a reference to this object."
+ if self._ptr: capi.destroy_feature(self._ptr)
+
+ def __getitem__(self, index):
+ """
+ Gets the Field object at the specified index, which may be either
+ an integer or the Field's string label. Note that the Field object
+ is not the field's _value_ -- use the `get` method instead to
+ retrieve the value (e.g. an integer) instead of a Field instance.
+ """
+ if isinstance(index, basestring):
+ i = self.index(index)
+ else:
+ if index < 0 or index > self.num_fields:
+ raise OGRIndexError('index out of range')
+ i = index
+ return Field(self.ptr, i)
+
+ def __iter__(self):
+ "Iterates over each field in the Feature."
+ for i in xrange(self.num_fields):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the count of fields in this feature."
+ return self.num_fields
+
+ def __str__(self):
+ "The string name of the feature."
+ return 'Feature FID %d in Layer<%s>' % (self.fid, self.layer_name)
+
+ def __eq__(self, other):
+ "Does equivalence testing on the features."
+ return bool(capi.feature_equal(self.ptr, other._ptr))
+
+ #### Feature Properties ####
+ @property
+ def fid(self):
+ "Returns the feature identifier."
+ return capi.get_fid(self.ptr)
+
+ @property
+ def layer_name(self):
+ "Returns the name of the layer for the feature."
+ return capi.get_feat_name(self._fdefn)
+
+ @property
+ def num_fields(self):
+ "Returns the number of fields in the Feature."
+ return capi.get_feat_field_count(self.ptr)
+
+ @property
+ def fields(self):
+ "Returns a list of fields in the Feature."
+ return [capi.get_field_name(capi.get_field_defn(self._fdefn, i))
+ for i in xrange(self.num_fields)]
+
+ @property
+ def geom(self):
+ "Returns the OGR Geometry for this Feature."
+ # Retrieving the geometry pointer for the feature.
+ geom_ptr = capi.get_feat_geom_ref(self.ptr)
+ return OGRGeometry(geom_api.clone_geom(geom_ptr))
+
+ @property
+ def geom_type(self):
+ "Returns the OGR Geometry Type for this Feture."
+ return OGRGeomType(capi.get_fd_geom_type(self._fdefn))
+
+ #### Feature Methods ####
+ def get(self, field):
+ """
+ Returns the value of the field, instead of an instance of the Field
+ object. May take a string of the field name or a Field object as
+ parameters.
+ """
+ field_name = getattr(field, 'name', field)
+ return self[field_name].value
+
+ def index(self, field_name):
+ "Returns the index of the given field name."
+ i = capi.get_field_index(self.ptr, field_name)
+ if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name)
+ return i
diff --git a/parts/django/django/contrib/gis/gdal/field.py b/parts/django/django/contrib/gis/gdal/field.py
new file mode 100644
index 0000000..46dbc86
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/field.py
@@ -0,0 +1,178 @@
+from ctypes import byref, c_int
+from datetime import date, datetime, time
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.error import OGRException
+from django.contrib.gis.gdal.prototypes import ds as capi
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_Fld_* routines are relevant here.
+class Field(GDALBase):
+ "A class that wraps an OGR Field, needs to be instantiated from a Feature object."
+
+ #### Python 'magic' routines ####
+ def __init__(self, feat, index):
+ """
+ Initializes on the feature pointer and the integer index of
+ the field within the feature.
+ """
+ # Setting the feature pointer and index.
+ self._feat = feat
+ self._index = index
+
+ # Getting the pointer for this field.
+ fld_ptr = capi.get_feat_field_defn(feat, index)
+ if not fld_ptr:
+ raise OGRException('Cannot create OGR Field, invalid pointer given.')
+ self.ptr = fld_ptr
+
+ # Setting the class depending upon the OGR Field Type (OFT)
+ self.__class__ = OGRFieldTypes[self.type]
+
+ # OFTReal with no precision should be an OFTInteger.
+ if isinstance(self, OFTReal) and self.precision == 0:
+ self.__class__ = OFTInteger
+
+ def __str__(self):
+ "Returns the string representation of the Field."
+ return str(self.value).strip()
+
+ #### Field Methods ####
+ def as_double(self):
+ "Retrieves the Field's value as a double (float)."
+ return capi.get_field_as_double(self._feat, self._index)
+
+ def as_int(self):
+ "Retrieves the Field's value as an integer."
+ return capi.get_field_as_integer(self._feat, self._index)
+
+ def as_string(self):
+ "Retrieves the Field's value as a string."
+ return capi.get_field_as_string(self._feat, self._index)
+
+ def as_datetime(self):
+ "Retrieves the Field's value as a tuple of date & time components."
+ yy, mm, dd, hh, mn, ss, tz = [c_int() for i in range(7)]
+ status = capi.get_field_as_datetime(self._feat, self._index, byref(yy), byref(mm), byref(dd),
+ byref(hh), byref(mn), byref(ss), byref(tz))
+ if status:
+ return (yy, mm, dd, hh, mn, ss, tz)
+ else:
+ raise OGRException('Unable to retrieve date & time information from the field.')
+
+ #### Field Properties ####
+ @property
+ def name(self):
+ "Returns the name of this Field."
+ return capi.get_field_name(self.ptr)
+
+ @property
+ def precision(self):
+ "Returns the precision of this Field."
+ return capi.get_field_precision(self.ptr)
+
+ @property
+ def type(self):
+ "Returns the OGR type of this Field."
+ return capi.get_field_type(self.ptr)
+
+ @property
+ def type_name(self):
+ "Return the OGR field type name for this Field."
+ return capi.get_field_type_name(self.type)
+
+ @property
+ def value(self):
+ "Returns the value of this Field."
+ # Default is to get the field as a string.
+ return self.as_string()
+
+ @property
+ def width(self):
+ "Returns the width of this Field."
+ return capi.get_field_width(self.ptr)
+
+### The Field sub-classes for each OGR Field type. ###
+class OFTInteger(Field):
+ @property
+ def value(self):
+ "Returns an integer contained in this field."
+ return self.as_int()
+
+ @property
+ def type(self):
+ """
+ GDAL uses OFTReals to represent OFTIntegers in created
+ shapefiles -- forcing the type here since the underlying field
+ type may actually be OFTReal.
+ """
+ return 0
+
+class OFTReal(Field):
+ @property
+ def value(self):
+ "Returns a float contained in this field."
+ return self.as_double()
+
+# String & Binary fields, just subclasses
+class OFTString(Field): pass
+class OFTWideString(Field): pass
+class OFTBinary(Field): pass
+
+# OFTDate, OFTTime, OFTDateTime fields.
+class OFTDate(Field):
+ @property
+ def value(self):
+ "Returns a Python `date` object for the OFTDate field."
+ try:
+ yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
+ return date(yy.value, mm.value, dd.value)
+ except (ValueError, OGRException):
+ return None
+
+class OFTDateTime(Field):
+ @property
+ def value(self):
+ "Returns a Python `datetime` object for this OFTDateTime field."
+ # TODO: Adapt timezone information.
+ # See http://lists.maptools.org/pipermail/gdal-dev/2006-February/007990.html
+ # The `tz` variable has values of: 0=unknown, 1=localtime (ambiguous),
+ # 100=GMT, 104=GMT+1, 80=GMT-5, etc.
+ try:
+ yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
+ return datetime(yy.value, mm.value, dd.value, hh.value, mn.value, ss.value)
+ except (ValueError, OGRException):
+ return None
+
+class OFTTime(Field):
+ @property
+ def value(self):
+ "Returns a Python `time` object for this OFTTime field."
+ try:
+ yy, mm, dd, hh, mn, ss, tz = self.as_datetime()
+ return time(hh.value, mn.value, ss.value)
+ except (ValueError, OGRException):
+ return None
+
+# List fields are also just subclasses
+class OFTIntegerList(Field): pass
+class OFTRealList(Field): pass
+class OFTStringList(Field): pass
+class OFTWideStringList(Field): pass
+
+# Class mapping dictionary for OFT Types and reverse mapping.
+OGRFieldTypes = { 0 : OFTInteger,
+ 1 : OFTIntegerList,
+ 2 : OFTReal,
+ 3 : OFTRealList,
+ 4 : OFTString,
+ 5 : OFTStringList,
+ 6 : OFTWideString,
+ 7 : OFTWideStringList,
+ 8 : OFTBinary,
+ 9 : OFTDate,
+ 10 : OFTTime,
+ 11 : OFTDateTime,
+ }
+ROGRFieldTypes = dict([(cls, num) for num, cls in OGRFieldTypes.items()])
diff --git a/parts/django/django/contrib/gis/gdal/geometries.py b/parts/django/django/contrib/gis/gdal/geometries.py
new file mode 100644
index 0000000..30125d5
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/geometries.py
@@ -0,0 +1,737 @@
+"""
+ The OGRGeometry is a wrapper for using the OGR Geometry class
+ (see http://www.gdal.org/ogr/classOGRGeometry.html). OGRGeometry
+ may be instantiated when reading geometries from OGR Data Sources
+ (e.g. SHP files), or when given OGC WKT (a string).
+
+ While the 'full' API is not present yet, the API is "pythonic" unlike
+ the traditional and "next-generation" OGR Python bindings. One major
+ advantage OGR Geometries have over their GEOS counterparts is support
+ for spatial reference systems and their transformation.
+
+ Example:
+ >>> from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, SpatialReference
+ >>> wkt1, wkt2 = 'POINT(-90 30)', 'POLYGON((0 0, 5 0, 5 5, 0 5)'
+ >>> pnt = OGRGeometry(wkt1)
+ >>> print pnt
+ POINT (-90 30)
+ >>> mpnt = OGRGeometry(OGRGeomType('MultiPoint'), SpatialReference('WGS84'))
+ >>> mpnt.add(wkt1)
+ >>> mpnt.add(wkt1)
+ >>> print mpnt
+ MULTIPOINT (-90 30,-90 30)
+ >>> print mpnt.srs.name
+ WGS 84
+ >>> print mpnt.srs.proj
+ +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
+ >>> mpnt.transform_to(SpatialReference('NAD27'))
+ >>> print mpnt.proj
+ +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs
+ >>> print mpnt
+ MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641)
+
+ The OGRGeomType class is to make it easy to specify an OGR geometry type:
+ >>> from django.contrib.gis.gdal import OGRGeomType
+ >>> gt1 = OGRGeomType(3) # Using an integer for the type
+ >>> gt2 = OGRGeomType('Polygon') # Using a string
+ >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive
+ >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects
+ True
+"""
+# Python library requisites.
+import sys
+from binascii import a2b_hex
+from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p
+
+# Getting GDAL prerequisites
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
+from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
+from django.contrib.gis.gdal.geomtype import OGRGeomType
+from django.contrib.gis.gdal.libgdal import GEOJSON, GDAL_VERSION
+from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
+
+# Getting the ctypes prototype functions that interface w/the GDAL C library.
+from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api
+
+# For recognizing geometry input.
+from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_G_* routines are relevant here.
+
+#### OGRGeometry Class ####
+class OGRGeometry(GDALBase):
+ "Generally encapsulates an OGR geometry."
+
+ def __init__(self, geom_input, srs=None):
+ "Initializes Geometry on either WKT or an OGR pointer as input."
+
+ str_instance = isinstance(geom_input, basestring)
+
+ # If HEX, unpack input to to a binary buffer.
+ if str_instance and hex_regex.match(geom_input):
+ geom_input = buffer(a2b_hex(geom_input.upper()))
+ str_instance = False
+
+ # Constructing the geometry,
+ if str_instance:
+ # Checking if unicode
+ if isinstance(geom_input, unicode):
+ # Encoding to ASCII, WKT or HEX doesn't need any more.
+ geom_input = geom_input.encode('ascii')
+
+ wkt_m = wkt_regex.match(geom_input)
+ json_m = json_regex.match(geom_input)
+ if wkt_m:
+ if wkt_m.group('srid'):
+ # If there's EWKT, set the SRS w/value of the SRID.
+ srs = int(wkt_m.group('srid'))
+ if wkt_m.group('type').upper() == 'LINEARRING':
+ # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT.
+ # See http://trac.osgeo.org/gdal/ticket/1992.
+ g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num)
+ capi.import_wkt(g, byref(c_char_p(wkt_m.group('wkt'))))
+ else:
+ g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt'))), None, byref(c_void_p()))
+ elif json_m:
+ if GEOJSON:
+ g = capi.from_json(geom_input)
+ else:
+ raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.')
+ else:
+ # Seeing if the input is a valid short-hand string
+ # (e.g., 'Point', 'POLYGON').
+ ogr_t = OGRGeomType(geom_input)
+ g = capi.create_geom(OGRGeomType(geom_input).num)
+ elif isinstance(geom_input, buffer):
+ # WKB was passed in
+ g = capi.from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input))
+ elif isinstance(geom_input, OGRGeomType):
+ # OGRGeomType was passed in, an empty geometry will be created.
+ g = capi.create_geom(geom_input.num)
+ elif isinstance(geom_input, self.ptr_type):
+ # OGR pointer (c_void_p) was the input.
+ g = geom_input
+ else:
+ raise OGRException('Invalid input type for OGR Geometry construction: %s' % type(geom_input))
+
+ # Now checking the Geometry pointer before finishing initialization
+ # by setting the pointer for the object.
+ if not g:
+ raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input))
+ self.ptr = g
+
+ # Assigning the SpatialReference object to the geometry, if valid.
+ if bool(srs): self.srs = srs
+
+ # Setting the class depending upon the OGR Geometry Type
+ self.__class__ = GEO_CLASSES[self.geom_type.num]
+
+ def __del__(self):
+ "Deletes this Geometry."
+ if self._ptr: capi.destroy_geom(self._ptr)
+
+ # Pickle routines
+ def __getstate__(self):
+ srs = self.srs
+ if srs:
+ srs = srs.wkt
+ else:
+ srs = None
+ return str(self.wkb), srs
+
+ def __setstate__(self, state):
+ wkb, srs = state
+ ptr = capi.from_wkb(wkb, None, byref(c_void_p()), len(wkb))
+ if not ptr: raise OGRException('Invalid OGRGeometry loaded from pickled state.')
+ self.ptr = ptr
+ self.srs = srs
+
+ @classmethod
+ def from_bbox(cls, bbox):
+ "Constructs a Polygon from a bounding box (4-tuple)."
+ x0, y0, x1, y1 = bbox
+ return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (
+ x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
+
+ ### Geometry set-like operations ###
+ # g = g1 | g2
+ def __or__(self, other):
+ "Returns the union of the two geometries."
+ return self.union(other)
+
+ # g = g1 & g2
+ def __and__(self, other):
+ "Returns the intersection of this Geometry and the other."
+ return self.intersection(other)
+
+ # g = g1 - g2
+ def __sub__(self, other):
+ "Return the difference this Geometry and the other."
+ return self.difference(other)
+
+ # g = g1 ^ g2
+ def __xor__(self, other):
+ "Return the symmetric difference of this Geometry and the other."
+ return self.sym_difference(other)
+
+ def __eq__(self, other):
+ "Is this Geometry equal to the other?"
+ if isinstance(other, OGRGeometry):
+ return self.equals(other)
+ else:
+ return False
+
+ def __ne__(self, other):
+ "Tests for inequality."
+ return not (self == other)
+
+ def __str__(self):
+ "WKT is used for the string representation."
+ return self.wkt
+
+ #### Geometry Properties ####
+ @property
+ def dimension(self):
+ "Returns 0 for points, 1 for lines, and 2 for surfaces."
+ return capi.get_dims(self.ptr)
+
+ def _get_coord_dim(self):
+ "Returns the coordinate dimension of the Geometry."
+ if isinstance(self, GeometryCollection) and GDAL_VERSION < (1, 5, 2):
+ # On GDAL versions prior to 1.5.2, there exists a bug in which
+ # the coordinate dimension of geometry collections is always 2:
+ # http://trac.osgeo.org/gdal/ticket/2334
+ # Here we workaround by returning the coordinate dimension of the
+ # first geometry in the collection instead.
+ if len(self):
+ return capi.get_coord_dim(capi.get_geom_ref(self.ptr, 0))
+ return capi.get_coord_dim(self.ptr)
+
+ def _set_coord_dim(self, dim):
+ "Sets the coordinate dimension of this Geometry."
+ if not dim in (2, 3):
+ raise ValueError('Geometry dimension must be either 2 or 3')
+ capi.set_coord_dim(self.ptr, dim)
+
+ coord_dim = property(_get_coord_dim, _set_coord_dim)
+
+ @property
+ def geom_count(self):
+ "The number of elements in this Geometry."
+ return capi.get_geom_count(self.ptr)
+
+ @property
+ def point_count(self):
+ "Returns the number of Points in this Geometry."
+ return capi.get_point_count(self.ptr)
+
+ @property
+ def num_points(self):
+ "Alias for `point_count` (same name method in GEOS API.)"
+ return self.point_count
+
+ @property
+ def num_coords(self):
+ "Alais for `point_count`."
+ return self.point_count
+
+ @property
+ def geom_type(self):
+ "Returns the Type for this Geometry."
+ return OGRGeomType(capi.get_geom_type(self.ptr))
+
+ @property
+ def geom_name(self):
+ "Returns the Name of this Geometry."
+ return capi.get_geom_name(self.ptr)
+
+ @property
+ def area(self):
+ "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
+ return capi.get_area(self.ptr)
+
+ @property
+ def envelope(self):
+ "Returns the envelope for this Geometry."
+ # TODO: Fix Envelope() for Point geometries.
+ return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope())))
+
+ @property
+ def extent(self):
+ "Returns the envelope as a 4-tuple, instead of as an Envelope object."
+ return self.envelope.tuple
+
+ #### SpatialReference-related Properties ####
+
+ # The SRS property
+ def _get_srs(self):
+ "Returns the Spatial Reference for this Geometry."
+ try:
+ srs_ptr = capi.get_geom_srs(self.ptr)
+ return SpatialReference(srs_api.clone_srs(srs_ptr))
+ except SRSException:
+ return None
+
+ def _set_srs(self, srs):
+ "Sets the SpatialReference for this geometry."
+ # Do not have to clone the `SpatialReference` object pointer because
+ # when it is assigned to this `OGRGeometry` it's internal OGR
+ # reference count is incremented, and will likewise be released
+ # (decremented) when this geometry's destructor is called.
+ if isinstance(srs, SpatialReference):
+ srs_ptr = srs.ptr
+ elif isinstance(srs, (int, long, basestring)):
+ sr = SpatialReference(srs)
+ srs_ptr = sr.ptr
+ else:
+ raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
+ capi.assign_srs(self.ptr, srs_ptr)
+
+ srs = property(_get_srs, _set_srs)
+
+ # The SRID property
+ def _get_srid(self):
+ srs = self.srs
+ if srs: return srs.srid
+ return None
+
+ def _set_srid(self, srid):
+ if isinstance(srid, (int, long)):
+ self.srs = srid
+ else:
+ raise TypeError('SRID must be set with an integer.')
+
+ srid = property(_get_srid, _set_srid)
+
+ #### Output Methods ####
+ @property
+ def geos(self):
+ "Returns a GEOSGeometry object from this OGRGeometry."
+ from django.contrib.gis.geos import GEOSGeometry
+ return GEOSGeometry(self.wkb, self.srid)
+
+ @property
+ def gml(self):
+ "Returns the GML representation of the Geometry."
+ return capi.to_gml(self.ptr)
+
+ @property
+ def hex(self):
+ "Returns the hexadecimal representation of the WKB (a string)."
+ return str(self.wkb).encode('hex').upper()
+ #return b2a_hex(self.wkb).upper()
+
+ @property
+ def json(self):
+ """
+ Returns the GeoJSON representation of this Geometry (requires
+ GDAL 1.5+).
+ """
+ if GEOJSON:
+ return capi.to_json(self.ptr)
+ else:
+ raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.')
+ geojson = json
+
+ @property
+ def kml(self):
+ "Returns the KML representation of the Geometry."
+ if GEOJSON:
+ return capi.to_kml(self.ptr, None)
+ else:
+ raise NotImplementedError('KML output only supported on GDAL 1.5+.')
+
+ @property
+ def wkb_size(self):
+ "Returns the size of the WKB buffer."
+ return capi.get_wkbsize(self.ptr)
+
+ @property
+ def wkb(self):
+ "Returns the WKB representation of the Geometry."
+ if sys.byteorder == 'little':
+ byteorder = 1 # wkbNDR (from ogr_core.h)
+ else:
+ byteorder = 0 # wkbXDR
+ sz = self.wkb_size
+ # Creating the unsigned character buffer, and passing it in by reference.
+ buf = (c_ubyte * sz)()
+ wkb = capi.to_wkb(self.ptr, byteorder, byref(buf))
+ # Returning a buffer of the string at the pointer.
+ return buffer(string_at(buf, sz))
+
+ @property
+ def wkt(self):
+ "Returns the WKT representation of the Geometry."
+ return capi.to_wkt(self.ptr, byref(c_char_p()))
+
+ @property
+ def ewkt(self):
+ "Returns the EWKT representation of the Geometry."
+ srs = self.srs
+ if srs and srs.srid:
+ return 'SRID=%s;%s' % (srs.srid, self.wkt)
+ else:
+ return self.wkt
+
+ #### Geometry Methods ####
+ def clone(self):
+ "Clones this OGR Geometry."
+ return OGRGeometry(capi.clone_geom(self.ptr), self.srs)
+
+ def close_rings(self):
+ """
+ If there are any rings within this geometry that have not been
+ closed, this routine will do so by adding the starting point at the
+ end.
+ """
+ # Closing the open rings.
+ capi.geom_close_rings(self.ptr)
+
+ def transform(self, coord_trans, clone=False):
+ """
+ Transforms this geometry to a different spatial reference system.
+ May take a CoordTransform object, a SpatialReference object, string
+ WKT or PROJ.4, and/or an integer SRID. By default nothing is returned
+ and the geometry is transformed in-place. However, if the `clone`
+ keyword is set, then a transformed clone of this geometry will be
+ returned.
+ """
+ if clone:
+ klone = self.clone()
+ klone.transform(coord_trans)
+ return klone
+
+ # Have to get the coordinate dimension of the original geometry
+ # so it can be used to reset the transformed geometry's dimension
+ # afterwards. This is done because of GDAL bug (in versions prior
+ # to 1.7) that turns geometries 3D after transformation, see:
+ # http://trac.osgeo.org/gdal/changeset/17792
+ if GDAL_VERSION < (1, 7):
+ orig_dim = self.coord_dim
+
+ # Depending on the input type, use the appropriate OGR routine
+ # to perform the transformation.
+ if isinstance(coord_trans, CoordTransform):
+ capi.geom_transform(self.ptr, coord_trans.ptr)
+ elif isinstance(coord_trans, SpatialReference):
+ capi.geom_transform_to(self.ptr, coord_trans.ptr)
+ elif isinstance(coord_trans, (int, long, basestring)):
+ sr = SpatialReference(coord_trans)
+ capi.geom_transform_to(self.ptr, sr.ptr)
+ else:
+ raise TypeError('Transform only accepts CoordTransform, '
+ 'SpatialReference, string, and integer objects.')
+
+ # Setting with original dimension, see comment above.
+ if GDAL_VERSION < (1, 7):
+ if isinstance(self, GeometryCollection):
+ # With geometry collections have to set dimension on
+ # each internal geometry reference, as the collection
+ # dimension isn't affected.
+ for i in xrange(len(self)):
+ internal_ptr = capi.get_geom_ref(self.ptr, i)
+ if orig_dim != capi.get_coord_dim(internal_ptr):
+ capi.set_coord_dim(internal_ptr, orig_dim)
+ else:
+ if self.coord_dim != orig_dim:
+ self.coord_dim = orig_dim
+
+ def transform_to(self, srs):
+ "For backwards-compatibility."
+ self.transform(srs)
+
+ #### Topology Methods ####
+ def _topology(self, func, other):
+ """A generalized function for topology operations, takes a GDAL function and
+ the other geometry to perform the operation on."""
+ if not isinstance(other, OGRGeometry):
+ raise TypeError('Must use another OGRGeometry object for topology operations!')
+
+ # Returning the output of the given function with the other geometry's
+ # pointer.
+ return func(self.ptr, other.ptr)
+
+ def intersects(self, other):
+ "Returns True if this geometry intersects with the other."
+ return self._topology(capi.ogr_intersects, other)
+
+ def equals(self, other):
+ "Returns True if this geometry is equivalent to the other."
+ return self._topology(capi.ogr_equals, other)
+
+ def disjoint(self, other):
+ "Returns True if this geometry and the other are spatially disjoint."
+ return self._topology(capi.ogr_disjoint, other)
+
+ def touches(self, other):
+ "Returns True if this geometry touches the other."
+ return self._topology(capi.ogr_touches, other)
+
+ def crosses(self, other):
+ "Returns True if this geometry crosses the other."
+ return self._topology(capi.ogr_crosses, other)
+
+ def within(self, other):
+ "Returns True if this geometry is within the other."
+ return self._topology(capi.ogr_within, other)
+
+ def contains(self, other):
+ "Returns True if this geometry contains the other."
+ return self._topology(capi.ogr_contains, other)
+
+ def overlaps(self, other):
+ "Returns True if this geometry overlaps the other."
+ return self._topology(capi.ogr_overlaps, other)
+
+ #### Geometry-generation Methods ####
+ def _geomgen(self, gen_func, other=None):
+ "A helper routine for the OGR routines that generate geometries."
+ if isinstance(other, OGRGeometry):
+ return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs)
+ else:
+ return OGRGeometry(gen_func(self.ptr), self.srs)
+
+ @property
+ def boundary(self):
+ "Returns the boundary of this geometry."
+ return self._geomgen(capi.get_boundary)
+
+ @property
+ def convex_hull(self):
+ """
+ Returns the smallest convex Polygon that contains all the points in
+ this Geometry.
+ """
+ return self._geomgen(capi.geom_convex_hull)
+
+ def difference(self, other):
+ """
+ Returns a new geometry consisting of the region which is the difference
+ of this geometry and the other.
+ """
+ return self._geomgen(capi.geom_diff, other)
+
+ def intersection(self, other):
+ """
+ Returns a new geometry consisting of the region of intersection of this
+ geometry and the other.
+ """
+ return self._geomgen(capi.geom_intersection, other)
+
+ def sym_difference(self, other):
+ """
+ Returns a new geometry which is the symmetric difference of this
+ geometry and the other.
+ """
+ return self._geomgen(capi.geom_sym_diff, other)
+
+ def union(self, other):
+ """
+ Returns a new geometry consisting of the region which is the union of
+ this geometry and the other.
+ """
+ return self._geomgen(capi.geom_union, other)
+
+# The subclasses for OGR Geometry.
+class Point(OGRGeometry):
+
+ @property
+ def x(self):
+ "Returns the X coordinate for this Point."
+ return capi.getx(self.ptr, 0)
+
+ @property
+ def y(self):
+ "Returns the Y coordinate for this Point."
+ return capi.gety(self.ptr, 0)
+
+ @property
+ def z(self):
+ "Returns the Z coordinate for this Point."
+ if self.coord_dim == 3:
+ return capi.getz(self.ptr, 0)
+
+ @property
+ def tuple(self):
+ "Returns the tuple of this point."
+ if self.coord_dim == 2:
+ return (self.x, self.y)
+ elif self.coord_dim == 3:
+ return (self.x, self.y, self.z)
+ coords = tuple
+
+class LineString(OGRGeometry):
+
+ def __getitem__(self, index):
+ "Returns the Point at the given index."
+ if index >= 0 and index < self.point_count:
+ x, y, z = c_double(), c_double(), c_double()
+ capi.get_point(self.ptr, index, byref(x), byref(y), byref(z))
+ dim = self.coord_dim
+ if dim == 1:
+ return (x.value,)
+ elif dim == 2:
+ return (x.value, y.value)
+ elif dim == 3:
+ return (x.value, y.value, z.value)
+ else:
+ raise OGRIndexError('index out of range: %s' % str(index))
+
+ def __iter__(self):
+ "Iterates over each point in the LineString."
+ for i in xrange(self.point_count):
+ yield self[i]
+
+ def __len__(self):
+ "The length returns the number of points in the LineString."
+ return self.point_count
+
+ @property
+ def tuple(self):
+ "Returns the tuple representation of this LineString."
+ return tuple([self[i] for i in xrange(len(self))])
+ coords = tuple
+
+ def _listarr(self, func):
+ """
+ Internal routine that returns a sequence (list) corresponding with
+ the given function.
+ """
+ return [func(self.ptr, i) for i in xrange(len(self))]
+
+ @property
+ def x(self):
+ "Returns the X coordinates in a list."
+ return self._listarr(capi.getx)
+
+ @property
+ def y(self):
+ "Returns the Y coordinates in a list."
+ return self._listarr(capi.gety)
+
+ @property
+ def z(self):
+ "Returns the Z coordinates in a list."
+ if self.coord_dim == 3:
+ return self._listarr(capi.getz)
+
+# LinearRings are used in Polygons.
+class LinearRing(LineString): pass
+
+class Polygon(OGRGeometry):
+
+ def __len__(self):
+ "The number of interior rings in this Polygon."
+ return self.geom_count
+
+ def __iter__(self):
+ "Iterates through each ring in the Polygon."
+ for i in xrange(self.geom_count):
+ yield self[i]
+
+ def __getitem__(self, index):
+ "Gets the ring at the specified index."
+ if index < 0 or index >= self.geom_count:
+ raise OGRIndexError('index out of range: %s' % index)
+ else:
+ return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
+
+ # Polygon Properties
+ @property
+ def shell(self):
+ "Returns the shell of this Polygon."
+ return self[0] # First ring is the shell
+ exterior_ring = shell
+
+ @property
+ def tuple(self):
+ "Returns a tuple of LinearRing coordinate tuples."
+ return tuple([self[i].tuple for i in xrange(self.geom_count)])
+ coords = tuple
+
+ @property
+ def point_count(self):
+ "The number of Points in this Polygon."
+ # Summing up the number of points in each ring of the Polygon.
+ return sum([self[i].point_count for i in xrange(self.geom_count)])
+
+ @property
+ def centroid(self):
+ "Returns the centroid (a Point) of this Polygon."
+ # The centroid is a Point, create a geometry for this.
+ p = OGRGeometry(OGRGeomType('Point'))
+ capi.get_centroid(self.ptr, p.ptr)
+ return p
+
+# Geometry Collection base class.
+class GeometryCollection(OGRGeometry):
+ "The Geometry Collection class."
+
+ def __getitem__(self, index):
+ "Gets the Geometry at the specified index."
+ if index < 0 or index >= self.geom_count:
+ raise OGRIndexError('index out of range: %s' % index)
+ else:
+ return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
+
+ def __iter__(self):
+ "Iterates over each Geometry."
+ for i in xrange(self.geom_count):
+ yield self[i]
+
+ def __len__(self):
+ "The number of geometries in this Geometry Collection."
+ return self.geom_count
+
+ def add(self, geom):
+ "Add the geometry to this Geometry Collection."
+ if isinstance(geom, OGRGeometry):
+ if isinstance(geom, self.__class__):
+ for g in geom: capi.add_geom(self.ptr, g.ptr)
+ else:
+ capi.add_geom(self.ptr, geom.ptr)
+ elif isinstance(geom, basestring):
+ tmp = OGRGeometry(geom)
+ capi.add_geom(self.ptr, tmp.ptr)
+ else:
+ raise OGRException('Must add an OGRGeometry.')
+
+ @property
+ def point_count(self):
+ "The number of Points in this Geometry Collection."
+ # Summing up the number of points in each geometry in this collection
+ return sum([self[i].point_count for i in xrange(self.geom_count)])
+
+ @property
+ def tuple(self):
+ "Returns a tuple representation of this Geometry Collection."
+ return tuple([self[i].tuple for i in xrange(self.geom_count)])
+ coords = tuple
+
+# Multiple Geometry types.
+class MultiPoint(GeometryCollection): pass
+class MultiLineString(GeometryCollection): pass
+class MultiPolygon(GeometryCollection): pass
+
+# Class mapping dictionary (using the OGRwkbGeometryType as the key)
+GEO_CLASSES = {1 : Point,
+ 2 : LineString,
+ 3 : Polygon,
+ 4 : MultiPoint,
+ 5 : MultiLineString,
+ 6 : MultiPolygon,
+ 7 : GeometryCollection,
+ 101: LinearRing,
+ 1 + OGRGeomType.wkb25bit : Point,
+ 2 + OGRGeomType.wkb25bit : LineString,
+ 3 + OGRGeomType.wkb25bit : Polygon,
+ 4 + OGRGeomType.wkb25bit : MultiPoint,
+ 5 + OGRGeomType.wkb25bit : MultiLineString,
+ 6 + OGRGeomType.wkb25bit : MultiPolygon,
+ 7 + OGRGeomType.wkb25bit : GeometryCollection,
+ }
diff --git a/parts/django/django/contrib/gis/gdal/geomtype.py b/parts/django/django/contrib/gis/gdal/geomtype.py
new file mode 100644
index 0000000..3bf94d4
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/geomtype.py
@@ -0,0 +1,85 @@
+from django.contrib.gis.gdal.error import OGRException
+
+#### OGRGeomType ####
+class OGRGeomType(object):
+ "Encapulates OGR Geometry Types."
+
+ wkb25bit = -2147483648
+
+ # Dictionary of acceptable OGRwkbGeometryType s and their string names.
+ _types = {0 : 'Unknown',
+ 1 : 'Point',
+ 2 : 'LineString',
+ 3 : 'Polygon',
+ 4 : 'MultiPoint',
+ 5 : 'MultiLineString',
+ 6 : 'MultiPolygon',
+ 7 : 'GeometryCollection',
+ 100 : 'None',
+ 101 : 'LinearRing',
+ 1 + wkb25bit: 'Point25D',
+ 2 + wkb25bit: 'LineString25D',
+ 3 + wkb25bit: 'Polygon25D',
+ 4 + wkb25bit: 'MultiPoint25D',
+ 5 + wkb25bit : 'MultiLineString25D',
+ 6 + wkb25bit : 'MultiPolygon25D',
+ 7 + wkb25bit : 'GeometryCollection25D',
+ }
+ # Reverse type dictionary, keyed by lower-case of the name.
+ _str_types = dict([(v.lower(), k) for k, v in _types.items()])
+
+ def __init__(self, type_input):
+ "Figures out the correct OGR Type based upon the input."
+ if isinstance(type_input, OGRGeomType):
+ num = type_input.num
+ elif isinstance(type_input, basestring):
+ type_input = type_input.lower()
+ if type_input == 'geometry': type_input='unknown'
+ num = self._str_types.get(type_input, None)
+ if num is None:
+ raise OGRException('Invalid OGR String Type "%s"' % type_input)
+ elif isinstance(type_input, int):
+ if not type_input in self._types:
+ raise OGRException('Invalid OGR Integer Type: %d' % type_input)
+ num = type_input
+ else:
+ raise TypeError('Invalid OGR input type given.')
+
+ # Setting the OGR geometry type number.
+ self.num = num
+
+ def __str__(self):
+ "Returns the value of the name property."
+ return self.name
+
+ def __eq__(self, other):
+ """
+ Does an equivalence test on the OGR type with the given
+ other OGRGeomType, the short-hand string, or the integer.
+ """
+ if isinstance(other, OGRGeomType):
+ return self.num == other.num
+ elif isinstance(other, basestring):
+ return self.name.lower() == other.lower()
+ elif isinstance(other, int):
+ return self.num == other
+ else:
+ return False
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ @property
+ def name(self):
+ "Returns a short-hand string form of the OGR Geometry type."
+ return self._types[self.num]
+
+ @property
+ def django(self):
+ "Returns the Django GeometryField for this OGR Type."
+ s = self.name.replace('25D', '')
+ if s in ('LinearRing', 'None'):
+ return None
+ elif s == 'Unknown':
+ s = 'Geometry'
+ return s + 'Field'
diff --git a/parts/django/django/contrib/gis/gdal/layer.py b/parts/django/django/contrib/gis/gdal/layer.py
new file mode 100644
index 0000000..a2163bc
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/layer.py
@@ -0,0 +1,212 @@
+# Needed ctypes routines
+from ctypes import c_double, byref
+
+# Other GDAL imports.
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
+from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
+from django.contrib.gis.gdal.feature import Feature
+from django.contrib.gis.gdal.field import OGRFieldTypes
+from django.contrib.gis.gdal.geomtype import OGRGeomType
+from django.contrib.gis.gdal.geometries import OGRGeometry
+from django.contrib.gis.gdal.srs import SpatialReference
+
+# GDAL ctypes function prototypes.
+from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api
+
+# For more information, see the OGR C API source code:
+# http://www.gdal.org/ogr/ogr__api_8h.html
+#
+# The OGR_L_* routines are relevant here.
+class Layer(GDALBase):
+ "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object."
+
+ #### Python 'magic' routines ####
+ def __init__(self, layer_ptr, ds):
+ """
+ Initializes on an OGR C pointer to the Layer and the `DataSource` object
+ that owns this layer. The `DataSource` object is required so that a
+ reference to it is kept with this Layer. This prevents garbage
+ collection of the `DataSource` while this Layer is still active.
+ """
+ if not layer_ptr:
+ raise OGRException('Cannot create Layer, invalid pointer given')
+ self.ptr = layer_ptr
+ self._ds = ds
+ self._ldefn = capi.get_layer_defn(self._ptr)
+ # Does the Layer support random reading?
+ self._random_read = self.test_capability('RandomRead')
+
+ def __getitem__(self, index):
+ "Gets the Feature at the specified index."
+ if isinstance(index, (int, long)):
+ # An integer index was given -- we cannot do a check based on the
+ # number of features because the beginning and ending feature IDs
+ # are not guaranteed to be 0 and len(layer)-1, respectively.
+ if index < 0: raise OGRIndexError('Negative indices are not allowed on OGR Layers.')
+ return self._make_feature(index)
+ elif isinstance(index, slice):
+ # A slice was given
+ start, stop, stride = index.indices(self.num_feat)
+ return [self._make_feature(fid) for fid in xrange(start, stop, stride)]
+ else:
+ raise TypeError('Integers and slices may only be used when indexing OGR Layers.')
+
+ def __iter__(self):
+ "Iterates over each Feature in the Layer."
+ # ResetReading() must be called before iteration is to begin.
+ capi.reset_reading(self._ptr)
+ for i in xrange(self.num_feat):
+ yield Feature(capi.get_next_feature(self._ptr), self._ldefn)
+
+ def __len__(self):
+ "The length is the number of features."
+ return self.num_feat
+
+ def __str__(self):
+ "The string name of the layer."
+ return self.name
+
+ def _make_feature(self, feat_id):
+ """
+ Helper routine for __getitem__ that constructs a Feature from the given
+ Feature ID. If the OGR Layer does not support random-access reading,
+ then each feature of the layer will be incremented through until the
+ a Feature is found matching the given feature ID.
+ """
+ if self._random_read:
+ # If the Layer supports random reading, return.
+ try:
+ return Feature(capi.get_feature(self.ptr, feat_id), self._ldefn)
+ except OGRException:
+ pass
+ else:
+ # Random access isn't supported, have to increment through
+ # each feature until the given feature ID is encountered.
+ for feat in self:
+ if feat.fid == feat_id: return feat
+ # Should have returned a Feature, raise an OGRIndexError.
+ raise OGRIndexError('Invalid feature id: %s.' % feat_id)
+
+ #### Layer properties ####
+ @property
+ def extent(self):
+ "Returns the extent (an Envelope) of this layer."
+ env = OGREnvelope()
+ capi.get_extent(self.ptr, byref(env), 1)
+ return Envelope(env)
+
+ @property
+ def name(self):
+ "Returns the name of this layer in the Data Source."
+ return capi.get_fd_name(self._ldefn)
+
+ @property
+ def num_feat(self, force=1):
+ "Returns the number of features in the Layer."
+ return capi.get_feature_count(self.ptr, force)
+
+ @property
+ def num_fields(self):
+ "Returns the number of fields in the Layer."
+ return capi.get_field_count(self._ldefn)
+
+ @property
+ def geom_type(self):
+ "Returns the geometry type (OGRGeomType) of the Layer."
+ return OGRGeomType(capi.get_fd_geom_type(self._ldefn))
+
+ @property
+ def srs(self):
+ "Returns the Spatial Reference used in this Layer."
+ try:
+ ptr = capi.get_layer_srs(self.ptr)
+ return SpatialReference(srs_api.clone_srs(ptr))
+ except SRSException:
+ return None
+
+ @property
+ def fields(self):
+ """
+ Returns a list of string names corresponding to each of the Fields
+ available in this Layer.
+ """
+ return [capi.get_field_name(capi.get_field_defn(self._ldefn, i))
+ for i in xrange(self.num_fields) ]
+
+ @property
+ def field_types(self):
+ """
+ Returns a list of the types of fields in this Layer. For example,
+ the list [OFTInteger, OFTReal, OFTString] would be returned for
+ an OGR layer that had an integer, a floating-point, and string
+ fields.
+ """
+ return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))]
+ for i in xrange(self.num_fields)]
+
+ @property
+ def field_widths(self):
+ "Returns a list of the maximum field widths for the features."
+ return [capi.get_field_width(capi.get_field_defn(self._ldefn, i))
+ for i in xrange(self.num_fields)]
+
+ @property
+ def field_precisions(self):
+ "Returns the field precisions for the features."
+ return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i))
+ for i in xrange(self.num_fields)]
+
+ def _get_spatial_filter(self):
+ try:
+ return OGRGeometry(geom_api.clone_geom(capi.get_spatial_filter(self.ptr)))
+ except OGRException:
+ return None
+
+ def _set_spatial_filter(self, filter):
+ if isinstance(filter, OGRGeometry):
+ capi.set_spatial_filter(self.ptr, filter.ptr)
+ elif isinstance(filter, (tuple, list)):
+ if not len(filter) == 4:
+ raise ValueError('Spatial filter list/tuple must have 4 elements.')
+ # Map c_double onto params -- if a bad type is passed in it
+ # will be caught here.
+ xmin, ymin, xmax, ymax = map(c_double, filter)
+ capi.set_spatial_filter_rect(self.ptr, xmin, ymin, xmax, ymax)
+ elif filter is None:
+ capi.set_spatial_filter(self.ptr, None)
+ else:
+ raise TypeError('Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None.')
+
+ spatial_filter = property(_get_spatial_filter, _set_spatial_filter)
+
+ #### Layer Methods ####
+ def get_fields(self, field_name):
+ """
+ Returns a list containing the given field name for every Feature
+ in the Layer.
+ """
+ if not field_name in self.fields:
+ raise OGRException('invalid field name: %s' % field_name)
+ return [feat.get(field_name) for feat in self]
+
+ def get_geoms(self, geos=False):
+ """
+ Returns a list containing the OGRGeometry for every Feature in
+ the Layer.
+ """
+ if geos:
+ from django.contrib.gis.geos import GEOSGeometry
+ return [GEOSGeometry(feat.geom.wkb) for feat in self]
+ else:
+ return [feat.geom for feat in self]
+
+ def test_capability(self, capability):
+ """
+ Returns a bool indicating whether the this Layer supports the given
+ capability (a string). Valid capability strings include:
+ 'RandomRead', 'SequentialWrite', 'RandomWrite', 'FastSpatialFilter',
+ 'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions',
+ 'DeleteFeature', and 'FastSetNextByIndex'.
+ """
+ return bool(capi.test_capability(self.ptr, capability))
diff --git a/parts/django/django/contrib/gis/gdal/libgdal.py b/parts/django/django/contrib/gis/gdal/libgdal.py
new file mode 100644
index 0000000..a7a5658
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/libgdal.py
@@ -0,0 +1,104 @@
+import os, re, sys
+from ctypes import c_char_p, CDLL
+from ctypes.util import find_library
+from django.contrib.gis.gdal.error import OGRException
+
+# Custom library path set?
+try:
+ from django.conf import settings
+ lib_path = settings.GDAL_LIBRARY_PATH
+except (AttributeError, EnvironmentError, ImportError):
+ lib_path = None
+
+if lib_path:
+ lib_names = None
+elif os.name == 'nt':
+ # Windows NT shared library
+ lib_names = ['gdal17', 'gdal16', 'gdal15']
+elif os.name == 'posix':
+ # *NIX library names.
+ lib_names = ['gdal', 'GDAL', 'gdal1.7.0', 'gdal1.6.0', 'gdal1.5.0', 'gdal1.4.0']
+else:
+ raise OGRException('Unsupported OS "%s"' % os.name)
+
+# Using the ctypes `find_library` utility to find the
+# path to the GDAL library from the list of library names.
+if lib_names:
+ for lib_name in lib_names:
+ lib_path = find_library(lib_name)
+ if not lib_path is None: break
+
+if lib_path is None:
+ raise OGRException('Could not find the GDAL library (tried "%s"). '
+ 'Try setting GDAL_LIBRARY_PATH in your settings.' %
+ '", "'.join(lib_names))
+
+# This loads the GDAL/OGR C library
+lgdal = CDLL(lib_path)
+
+# On Windows, the GDAL binaries have some OSR routines exported with
+# STDCALL, while others are not. Thus, the library will also need to
+# be loaded up as WinDLL for said OSR functions that require the
+# different calling convention.
+if os.name == 'nt':
+ from ctypes import WinDLL
+ lwingdal = WinDLL(lib_path)
+
+def std_call(func):
+ """
+ Returns the correct STDCALL function for certain OSR routines on Win32
+ platforms.
+ """
+ if os.name == 'nt':
+ return lwingdal[func]
+ else:
+ return lgdal[func]
+
+#### Version-information functions. ####
+
+# Returns GDAL library version information with the given key.
+_version_info = std_call('GDALVersionInfo')
+_version_info.argtypes = [c_char_p]
+_version_info.restype = c_char_p
+
+def gdal_version():
+ "Returns only the GDAL version number information."
+ return _version_info('RELEASE_NAME')
+
+def gdal_full_version():
+ "Returns the full GDAL version information."
+ return _version_info('')
+
+def gdal_release_date(date=False):
+ """
+ Returns the release date in a string format, e.g, "2007/06/27".
+ If the date keyword argument is set to True, a Python datetime object
+ will be returned instead.
+ """
+ from datetime import date as date_type
+ rel = _version_info('RELEASE_DATE')
+ yy, mm, dd = map(int, (rel[0:4], rel[4:6], rel[6:8]))
+ d = date_type(yy, mm, dd)
+ if date: return d
+ else: return d.strftime('%Y/%m/%d')
+
+version_regex = re.compile(r'^(?P<major>\d+)\.(?P<minor>\d+)(\.(?P<subminor>\d+))?')
+def gdal_version_info():
+ ver = gdal_version()
+ m = version_regex.match(ver)
+ if not m: raise OGRException('Could not parse GDAL version string "%s"' % ver)
+ return dict([(key, m.group(key)) for key in ('major', 'minor', 'subminor')])
+
+_verinfo = gdal_version_info()
+GDAL_MAJOR_VERSION = int(_verinfo['major'])
+GDAL_MINOR_VERSION = int(_verinfo['minor'])
+GDAL_SUBMINOR_VERSION = _verinfo['subminor'] and int(_verinfo['subminor'])
+GDAL_VERSION = (GDAL_MAJOR_VERSION, GDAL_MINOR_VERSION, GDAL_SUBMINOR_VERSION)
+del _verinfo
+
+# GeoJSON support is available only in GDAL 1.5+.
+if GDAL_VERSION >= (1, 5):
+ GEOJSON = True
+else:
+ GEOJSON = False
+
diff --git a/parts/django/django/contrib/gis/gdal/prototypes/__init__.py b/parts/django/django/contrib/gis/gdal/prototypes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/prototypes/__init__.py
diff --git a/parts/django/django/contrib/gis/gdal/prototypes/ds.py b/parts/django/django/contrib/gis/gdal/prototypes/ds.py
new file mode 100644
index 0000000..44828ee
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/prototypes/ds.py
@@ -0,0 +1,71 @@
+"""
+ This module houses the ctypes function prototypes for OGR DataSource
+ related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*,
+ OGR_Fld_* routines are relevant here.
+"""
+from ctypes import c_char_p, c_double, c_int, c_long, c_void_p, POINTER
+from django.contrib.gis.gdal.envelope import OGREnvelope
+from django.contrib.gis.gdal.libgdal import lgdal
+from django.contrib.gis.gdal.prototypes.generation import \
+ const_string_output, double_output, geom_output, int_output, \
+ srs_output, void_output, voidptr_output
+
+c_int_p = POINTER(c_int) # shortcut type
+
+### Driver Routines ###
+register_all = void_output(lgdal.OGRRegisterAll, [], errcheck=False)
+cleanup_all = void_output(lgdal.OGRCleanupAll, [], errcheck=False)
+get_driver = voidptr_output(lgdal.OGRGetDriver, [c_int])
+get_driver_by_name = voidptr_output(lgdal.OGRGetDriverByName, [c_char_p])
+get_driver_count = int_output(lgdal.OGRGetDriverCount, [])
+get_driver_name = const_string_output(lgdal.OGR_Dr_GetName, [c_void_p])
+
+### DataSource ###
+open_ds = voidptr_output(lgdal.OGROpen, [c_char_p, c_int, POINTER(c_void_p)])
+destroy_ds = void_output(lgdal.OGR_DS_Destroy, [c_void_p], errcheck=False)
+release_ds = void_output(lgdal.OGRReleaseDataSource, [c_void_p])
+get_ds_name = const_string_output(lgdal.OGR_DS_GetName, [c_void_p])
+get_layer = voidptr_output(lgdal.OGR_DS_GetLayer, [c_void_p, c_int])
+get_layer_by_name = voidptr_output(lgdal.OGR_DS_GetLayerByName, [c_void_p, c_char_p])
+get_layer_count = int_output(lgdal.OGR_DS_GetLayerCount, [c_void_p])
+
+### Layer Routines ###
+get_extent = void_output(lgdal.OGR_L_GetExtent, [c_void_p, POINTER(OGREnvelope), c_int])
+get_feature = voidptr_output(lgdal.OGR_L_GetFeature, [c_void_p, c_long])
+get_feature_count = int_output(lgdal.OGR_L_GetFeatureCount, [c_void_p, c_int])
+get_layer_defn = voidptr_output(lgdal.OGR_L_GetLayerDefn, [c_void_p])
+get_layer_srs = srs_output(lgdal.OGR_L_GetSpatialRef, [c_void_p])
+get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p])
+reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False)
+test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p])
+get_spatial_filter = geom_output(lgdal.OGR_L_GetSpatialFilter, [c_void_p])
+set_spatial_filter = void_output(lgdal.OGR_L_SetSpatialFilter, [c_void_p, c_void_p], errcheck=False)
+set_spatial_filter_rect = void_output(lgdal.OGR_L_SetSpatialFilterRect, [c_void_p, c_double, c_double, c_double, c_double], errcheck=False)
+
+### Feature Definition Routines ###
+get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p])
+get_fd_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p])
+get_feat_name = const_string_output(lgdal.OGR_FD_GetName, [c_void_p])
+get_field_count = int_output(lgdal.OGR_FD_GetFieldCount, [c_void_p])
+get_field_defn = voidptr_output(lgdal.OGR_FD_GetFieldDefn, [c_void_p, c_int])
+
+### Feature Routines ###
+clone_feature = voidptr_output(lgdal.OGR_F_Clone, [c_void_p])
+destroy_feature = void_output(lgdal.OGR_F_Destroy, [c_void_p], errcheck=False)
+feature_equal = int_output(lgdal.OGR_F_Equal, [c_void_p, c_void_p])
+get_feat_geom_ref = geom_output(lgdal.OGR_F_GetGeometryRef, [c_void_p])
+get_feat_field_count = int_output(lgdal.OGR_F_GetFieldCount, [c_void_p])
+get_feat_field_defn = voidptr_output(lgdal.OGR_F_GetFieldDefnRef, [c_void_p, c_int])
+get_fid = int_output(lgdal.OGR_F_GetFID, [c_void_p])
+get_field_as_datetime = int_output(lgdal.OGR_F_GetFieldAsDateTime, [c_void_p, c_int, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p, c_int_p])
+get_field_as_double = double_output(lgdal.OGR_F_GetFieldAsDouble, [c_void_p, c_int])
+get_field_as_integer = int_output(lgdal.OGR_F_GetFieldAsInteger, [c_void_p, c_int])
+get_field_as_string = const_string_output(lgdal.OGR_F_GetFieldAsString, [c_void_p, c_int])
+get_field_index = int_output(lgdal.OGR_F_GetFieldIndex, [c_void_p, c_char_p])
+
+### Field Routines ###
+get_field_name = const_string_output(lgdal.OGR_Fld_GetNameRef, [c_void_p])
+get_field_precision = int_output(lgdal.OGR_Fld_GetPrecision, [c_void_p])
+get_field_type = int_output(lgdal.OGR_Fld_GetType, [c_void_p])
+get_field_type_name = const_string_output(lgdal.OGR_GetFieldTypeName, [c_int])
+get_field_width = int_output(lgdal.OGR_Fld_GetWidth, [c_void_p])
diff --git a/parts/django/django/contrib/gis/gdal/prototypes/errcheck.py b/parts/django/django/contrib/gis/gdal/prototypes/errcheck.py
new file mode 100644
index 0000000..91858ea
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/prototypes/errcheck.py
@@ -0,0 +1,127 @@
+"""
+ This module houses the error-checking routines used by the GDAL
+ ctypes prototypes.
+"""
+from ctypes import c_void_p, string_at
+from django.contrib.gis.gdal.error import check_err, OGRException, SRSException
+from django.contrib.gis.gdal.libgdal import lgdal
+
+# Helper routines for retrieving pointers and/or values from
+# arguments passed in by reference.
+def arg_byref(args, offset=-1):
+ "Returns the pointer argument's by-refernece value."
+ return args[offset]._obj.value
+
+def ptr_byref(args, offset=-1):
+ "Returns the pointer argument passed in by-reference."
+ return args[offset]._obj
+
+def check_bool(result, func, cargs):
+ "Returns the boolean evaluation of the value."
+ if bool(result): return True
+ else: return False
+
+### String checking Routines ###
+def check_const_string(result, func, cargs, offset=None):
+ """
+ Similar functionality to `check_string`, but does not free the pointer.
+ """
+ if offset:
+ check_err(result)
+ ptr = ptr_byref(cargs, offset)
+ return ptr.value
+ else:
+ return result
+
+def check_string(result, func, cargs, offset=-1, str_result=False):
+ """
+ Checks the string output returned from the given function, and frees
+ the string pointer allocated by OGR. The `str_result` keyword
+ may be used when the result is the string pointer, otherwise
+ the OGR error code is assumed. The `offset` keyword may be used
+ to extract the string pointer passed in by-reference at the given
+ slice offset in the function arguments.
+ """
+ if str_result:
+ # For routines that return a string.
+ ptr = result
+ if not ptr: s = None
+ else: s = string_at(result)
+ else:
+ # Error-code return specified.
+ check_err(result)
+ ptr = ptr_byref(cargs, offset)
+ # Getting the string value
+ s = ptr.value
+ # Correctly freeing the allocated memory beind GDAL pointer
+ # w/the VSIFree routine.
+ if ptr: lgdal.VSIFree(ptr)
+ return s
+
+### DataSource, Layer error-checking ###
+
+### Envelope checking ###
+def check_envelope(result, func, cargs, offset=-1):
+ "Checks a function that returns an OGR Envelope by reference."
+ env = ptr_byref(cargs, offset)
+ return env
+
+### Geometry error-checking routines ###
+def check_geom(result, func, cargs):
+ "Checks a function that returns a geometry."
+ # OGR_G_Clone may return an integer, even though the
+ # restype is set to c_void_p
+ if isinstance(result, (int, long)):
+ result = c_void_p(result)
+ if not result:
+ raise OGRException('Invalid geometry pointer returned from "%s".' % func.__name__)
+ return result
+
+def check_geom_offset(result, func, cargs, offset=-1):
+ "Chcks the geometry at the given offset in the C parameter list."
+ check_err(result)
+ geom = ptr_byref(cargs, offset=offset)
+ return check_geom(geom, func, cargs)
+
+### Spatial Reference error-checking routines ###
+def check_srs(result, func, cargs):
+ if isinstance(result, (int, long)):
+ result = c_void_p(result)
+ if not result:
+ raise SRSException('Invalid spatial reference pointer returned from "%s".' % func.__name__)
+ return result
+
+### Other error-checking routines ###
+def check_arg_errcode(result, func, cargs):
+ """
+ The error code is returned in the last argument, by reference.
+ Check its value with `check_err` before returning the result.
+ """
+ check_err(arg_byref(cargs))
+ return result
+
+def check_errcode(result, func, cargs):
+ """
+ Check the error code returned (c_int).
+ """
+ check_err(result)
+ return
+
+def check_pointer(result, func, cargs):
+ "Makes sure the result pointer is valid."
+ if isinstance(result, (int, long)):
+ result = c_void_p(result)
+ if bool(result):
+ return result
+ else:
+ raise OGRException('Invalid pointer returned from "%s"' % func.__name__)
+
+def check_str_arg(result, func, cargs):
+ """
+ This is for the OSRGet[Angular|Linear]Units functions, which
+ require that the returned string pointer not be freed. This
+ returns both the double and tring values.
+ """
+ dbl = result
+ ptr = cargs[-1]._obj
+ return dbl, ptr.value
diff --git a/parts/django/django/contrib/gis/gdal/prototypes/generation.py b/parts/django/django/contrib/gis/gdal/prototypes/generation.py
new file mode 100644
index 0000000..1303532
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/prototypes/generation.py
@@ -0,0 +1,119 @@
+"""
+ This module contains functions that generate ctypes prototypes for the
+ GDAL routines.
+"""
+
+from ctypes import c_char_p, c_double, c_int, c_void_p
+from django.contrib.gis.gdal.prototypes.errcheck import \
+ check_arg_errcode, check_errcode, check_geom, check_geom_offset, \
+ check_pointer, check_srs, check_str_arg, check_string, check_const_string
+
+class gdal_char_p(c_char_p):
+ pass
+
+def double_output(func, argtypes, errcheck=False, strarg=False):
+ "Generates a ctypes function that returns a double value."
+ func.argtypes = argtypes
+ func.restype = c_double
+ if errcheck: func.errcheck = check_arg_errcode
+ if strarg: func.errcheck = check_str_arg
+ return func
+
+def geom_output(func, argtypes, offset=None):
+ """
+ Generates a function that returns a Geometry either by reference
+ or directly (if the return_geom keyword is set to True).
+ """
+ # Setting the argument types
+ func.argtypes = argtypes
+
+ if not offset:
+ # When a geometry pointer is directly returned.
+ func.restype = c_void_p
+ func.errcheck = check_geom
+ else:
+ # Error code returned, geometry is returned by-reference.
+ func.restype = c_int
+ def geomerrcheck(result, func, cargs):
+ return check_geom_offset(result, func, cargs, offset)
+ func.errcheck = geomerrcheck
+
+ return func
+
+def int_output(func, argtypes):
+ "Generates a ctypes function that returns an integer value."
+ func.argtypes = argtypes
+ func.restype = c_int
+ return func
+
+def srs_output(func, argtypes):
+ """
+ Generates a ctypes prototype for the given function with
+ the given C arguments that returns a pointer to an OGR
+ Spatial Reference System.
+ """
+ func.argtypes = argtypes
+ func.restype = c_void_p
+ func.errcheck = check_srs
+ return func
+
+def const_string_output(func, argtypes, offset=None):
+ func.argtypes = argtypes
+ if offset:
+ func.restype = c_int
+ else:
+ func.restype = c_char_p
+
+ def _check_const(result, func, cargs):
+ return check_const_string(result, func, cargs, offset=offset)
+ func.errcheck = _check_const
+
+ return func
+
+def string_output(func, argtypes, offset=-1, str_result=False):
+ """
+ Generates a ctypes prototype for the given function with the
+ given argument types that returns a string from a GDAL pointer.
+ The `const` flag indicates whether the allocated pointer should
+ be freed via the GDAL library routine VSIFree -- but only applies
+ only when `str_result` is True.
+ """
+ func.argtypes = argtypes
+ if str_result:
+ # Use subclass of c_char_p so the error checking routine
+ # can free the memory at the pointer's address.
+ func.restype = gdal_char_p
+ else:
+ # Error code is returned
+ func.restype = c_int
+
+ # Dynamically defining our error-checking function with the
+ # given offset.
+ def _check_str(result, func, cargs):
+ return check_string(result, func, cargs,
+ offset=offset, str_result=str_result)
+ func.errcheck = _check_str
+ return func
+
+def void_output(func, argtypes, errcheck=True):
+ """
+ For functions that don't only return an error code that needs to
+ be examined.
+ """
+ if argtypes: func.argtypes = argtypes
+ if errcheck:
+ # `errcheck` keyword may be set to False for routines that
+ # return void, rather than a status code.
+ func.restype = c_int
+ func.errcheck = check_errcode
+ else:
+ func.restype = None
+
+ return func
+
+def voidptr_output(func, argtypes):
+ "For functions that return c_void_p."
+ func.argtypes = argtypes
+ func.restype = c_void_p
+ func.errcheck = check_pointer
+ return func
diff --git a/parts/django/django/contrib/gis/gdal/prototypes/geom.py b/parts/django/django/contrib/gis/gdal/prototypes/geom.py
new file mode 100644
index 0000000..e002590
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/prototypes/geom.py
@@ -0,0 +1,106 @@
+import re
+from datetime import date
+from ctypes import c_char, c_char_p, c_double, c_int, c_ubyte, c_void_p, POINTER
+from django.contrib.gis.gdal.envelope import OGREnvelope
+from django.contrib.gis.gdal.libgdal import lgdal, GEOJSON
+from django.contrib.gis.gdal.prototypes.errcheck import check_bool, check_envelope
+from django.contrib.gis.gdal.prototypes.generation import \
+ const_string_output, double_output, geom_output, int_output, \
+ srs_output, string_output, void_output
+
+### Generation routines specific to this module ###
+def env_func(f, argtypes):
+ "For getting OGREnvelopes."
+ f.argtypes = argtypes
+ f.restype = None
+ f.errcheck = check_envelope
+ return f
+
+def pnt_func(f):
+ "For accessing point information."
+ return double_output(f, [c_void_p, c_int])
+
+def topology_func(f):
+ f.argtypes = [c_void_p, c_void_p]
+ f.restype = c_int
+ f.errchck = check_bool
+ return f
+
+### OGR_G ctypes function prototypes ###
+
+# GeoJSON routines, if supported.
+if GEOJSON:
+ from_json = geom_output(lgdal.OGR_G_CreateGeometryFromJson, [c_char_p])
+ to_json = string_output(lgdal.OGR_G_ExportToJson, [c_void_p], str_result=True)
+ to_kml = string_output(lgdal.OGR_G_ExportToKML, [c_void_p, c_char_p], str_result=True)
+else:
+ from_json = False
+ to_json = False
+ to_kml = False
+
+# GetX, GetY, GetZ all return doubles.
+getx = pnt_func(lgdal.OGR_G_GetX)
+gety = pnt_func(lgdal.OGR_G_GetY)
+getz = pnt_func(lgdal.OGR_G_GetZ)
+
+# Geometry creation routines.
+from_wkb = geom_output(lgdal.OGR_G_CreateFromWkb, [c_char_p, c_void_p, POINTER(c_void_p), c_int], offset=-2)
+from_wkt = geom_output(lgdal.OGR_G_CreateFromWkt, [POINTER(c_char_p), c_void_p, POINTER(c_void_p)], offset=-1)
+create_geom = geom_output(lgdal.OGR_G_CreateGeometry, [c_int])
+clone_geom = geom_output(lgdal.OGR_G_Clone, [c_void_p])
+get_geom_ref = geom_output(lgdal.OGR_G_GetGeometryRef, [c_void_p, c_int])
+get_boundary = geom_output(lgdal.OGR_G_GetBoundary, [c_void_p])
+geom_convex_hull = geom_output(lgdal.OGR_G_ConvexHull, [c_void_p])
+geom_diff = geom_output(lgdal.OGR_G_Difference, [c_void_p, c_void_p])
+geom_intersection = geom_output(lgdal.OGR_G_Intersection, [c_void_p, c_void_p])
+geom_sym_diff = geom_output(lgdal.OGR_G_SymmetricDifference, [c_void_p, c_void_p])
+geom_union = geom_output(lgdal.OGR_G_Union, [c_void_p, c_void_p])
+
+# Geometry modification routines.
+add_geom = void_output(lgdal.OGR_G_AddGeometry, [c_void_p, c_void_p])
+import_wkt = void_output(lgdal.OGR_G_ImportFromWkt, [c_void_p, POINTER(c_char_p)])
+
+# Destroys a geometry
+destroy_geom = void_output(lgdal.OGR_G_DestroyGeometry, [c_void_p], errcheck=False)
+
+# Geometry export routines.
+to_wkb = void_output(lgdal.OGR_G_ExportToWkb, None, errcheck=True) # special handling for WKB.
+to_wkt = string_output(lgdal.OGR_G_ExportToWkt, [c_void_p, POINTER(c_char_p)])
+to_gml = string_output(lgdal.OGR_G_ExportToGML, [c_void_p], str_result=True)
+get_wkbsize = int_output(lgdal.OGR_G_WkbSize, [c_void_p])
+
+# Geometry spatial-reference related routines.
+assign_srs = void_output(lgdal.OGR_G_AssignSpatialReference, [c_void_p, c_void_p], errcheck=False)
+get_geom_srs = srs_output(lgdal.OGR_G_GetSpatialReference, [c_void_p])
+
+# Geometry properties
+get_area = double_output(lgdal.OGR_G_GetArea, [c_void_p])
+get_centroid = void_output(lgdal.OGR_G_Centroid, [c_void_p, c_void_p])
+get_dims = int_output(lgdal.OGR_G_GetDimension, [c_void_p])
+get_coord_dim = int_output(lgdal.OGR_G_GetCoordinateDimension, [c_void_p])
+set_coord_dim = void_output(lgdal.OGR_G_SetCoordinateDimension, [c_void_p, c_int], errcheck=False)
+
+get_geom_count = int_output(lgdal.OGR_G_GetGeometryCount, [c_void_p])
+get_geom_name = const_string_output(lgdal.OGR_G_GetGeometryName, [c_void_p])
+get_geom_type = int_output(lgdal.OGR_G_GetGeometryType, [c_void_p])
+get_point_count = int_output(lgdal.OGR_G_GetPointCount, [c_void_p])
+get_point = void_output(lgdal.OGR_G_GetPoint, [c_void_p, c_int, POINTER(c_double), POINTER(c_double), POINTER(c_double)], errcheck=False)
+geom_close_rings = void_output(lgdal.OGR_G_CloseRings, [c_void_p], errcheck=False)
+
+# Topology routines.
+ogr_contains = topology_func(lgdal.OGR_G_Contains)
+ogr_crosses = topology_func(lgdal.OGR_G_Crosses)
+ogr_disjoint = topology_func(lgdal.OGR_G_Disjoint)
+ogr_equals = topology_func(lgdal.OGR_G_Equals)
+ogr_intersects = topology_func(lgdal.OGR_G_Intersects)
+ogr_overlaps = topology_func(lgdal.OGR_G_Overlaps)
+ogr_touches = topology_func(lgdal.OGR_G_Touches)
+ogr_within = topology_func(lgdal.OGR_G_Within)
+
+# Transformation routines.
+geom_transform = void_output(lgdal.OGR_G_Transform, [c_void_p, c_void_p])
+geom_transform_to = void_output(lgdal.OGR_G_TransformTo, [c_void_p, c_void_p])
+
+# For retrieving the envelope of the geometry.
+get_envelope = env_func(lgdal.OGR_G_GetEnvelope, [c_void_p, POINTER(OGREnvelope)])
+
diff --git a/parts/django/django/contrib/gis/gdal/prototypes/srs.py b/parts/django/django/contrib/gis/gdal/prototypes/srs.py
new file mode 100644
index 0000000..411cec9
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/prototypes/srs.py
@@ -0,0 +1,72 @@
+from ctypes import c_char_p, c_int, c_void_p, POINTER
+from django.contrib.gis.gdal.libgdal import lgdal, std_call
+from django.contrib.gis.gdal.prototypes.generation import \
+ const_string_output, double_output, int_output, \
+ srs_output, string_output, void_output
+
+## Shortcut generation for routines with known parameters.
+def srs_double(f):
+ """
+ Creates a function prototype for the OSR routines that take
+ the OSRSpatialReference object and
+ """
+ return double_output(f, [c_void_p, POINTER(c_int)], errcheck=True)
+
+def units_func(f):
+ """
+ Creates a ctypes function prototype for OSR units functions, e.g.,
+ OSRGetAngularUnits, OSRGetLinearUnits.
+ """
+ return double_output(f, [c_void_p, POINTER(c_char_p)], strarg=True)
+
+# Creation & destruction.
+clone_srs = srs_output(std_call('OSRClone'), [c_void_p])
+new_srs = srs_output(std_call('OSRNewSpatialReference'), [c_char_p])
+release_srs = void_output(lgdal.OSRRelease, [c_void_p], errcheck=False)
+destroy_srs = void_output(std_call('OSRDestroySpatialReference'), [c_void_p], errcheck=False)
+srs_validate = void_output(lgdal.OSRValidate, [c_void_p])
+
+# Getting the semi_major, semi_minor, and flattening functions.
+semi_major = srs_double(lgdal.OSRGetSemiMajor)
+semi_minor = srs_double(lgdal.OSRGetSemiMinor)
+invflattening = srs_double(lgdal.OSRGetInvFlattening)
+
+# WKT, PROJ, EPSG, XML importation routines.
+from_wkt = void_output(lgdal.OSRImportFromWkt, [c_void_p, POINTER(c_char_p)])
+from_proj = void_output(lgdal.OSRImportFromProj4, [c_void_p, c_char_p])
+from_epsg = void_output(std_call('OSRImportFromEPSG'), [c_void_p, c_int])
+from_xml = void_output(lgdal.OSRImportFromXML, [c_void_p, c_char_p])
+from_user_input = void_output(std_call('OSRSetFromUserInput'), [c_void_p, c_char_p])
+
+# Morphing to/from ESRI WKT.
+morph_to_esri = void_output(lgdal.OSRMorphToESRI, [c_void_p])
+morph_from_esri = void_output(lgdal.OSRMorphFromESRI, [c_void_p])
+
+# Identifying the EPSG
+identify_epsg = void_output(lgdal.OSRAutoIdentifyEPSG, [c_void_p])
+
+# Getting the angular_units, linear_units functions
+linear_units = units_func(lgdal.OSRGetLinearUnits)
+angular_units = units_func(lgdal.OSRGetAngularUnits)
+
+# For exporting to WKT, PROJ.4, "Pretty" WKT, and XML.
+to_wkt = string_output(std_call('OSRExportToWkt'), [c_void_p, POINTER(c_char_p)])
+to_proj = string_output(std_call('OSRExportToProj4'), [c_void_p, POINTER(c_char_p)])
+to_pretty_wkt = string_output(std_call('OSRExportToPrettyWkt'), [c_void_p, POINTER(c_char_p), c_int], offset=-2)
+
+# Memory leak fixed in GDAL 1.5; still exists in 1.4.
+to_xml = string_output(lgdal.OSRExportToXML, [c_void_p, POINTER(c_char_p), c_char_p], offset=-2)
+
+# String attribute retrival routines.
+get_attr_value = const_string_output(std_call('OSRGetAttrValue'), [c_void_p, c_char_p, c_int])
+get_auth_name = const_string_output(lgdal.OSRGetAuthorityName, [c_void_p, c_char_p])
+get_auth_code = const_string_output(lgdal.OSRGetAuthorityCode, [c_void_p, c_char_p])
+
+# SRS Properties
+isgeographic = int_output(lgdal.OSRIsGeographic, [c_void_p])
+islocal = int_output(lgdal.OSRIsLocal, [c_void_p])
+isprojected = int_output(lgdal.OSRIsProjected, [c_void_p])
+
+# Coordinate transformation
+new_ct= srs_output(std_call('OCTNewCoordinateTransformation'), [c_void_p, c_void_p])
+destroy_ct = void_output(std_call('OCTDestroyCoordinateTransformation'), [c_void_p], errcheck=False)
diff --git a/parts/django/django/contrib/gis/gdal/srs.py b/parts/django/django/contrib/gis/gdal/srs.py
new file mode 100644
index 0000000..95e71f1
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/srs.py
@@ -0,0 +1,337 @@
+"""
+ The Spatial Reference class, represensents OGR Spatial Reference objects.
+
+ Example:
+ >>> from django.contrib.gis.gdal import SpatialReference
+ >>> srs = SpatialReference('WGS84')
+ >>> print srs
+ GEOGCS["WGS 84",
+ DATUM["WGS_1984",
+ SPHEROID["WGS 84",6378137,298.257223563,
+ AUTHORITY["EPSG","7030"]],
+ TOWGS84[0,0,0,0,0,0,0],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich",0,
+ AUTHORITY["EPSG","8901"]],
+ UNIT["degree",0.01745329251994328,
+ AUTHORITY["EPSG","9122"]],
+ AUTHORITY["EPSG","4326"]]
+ >>> print srs.proj
+ +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs
+ >>> print srs.ellipsoid
+ (6378137.0, 6356752.3142451793, 298.25722356300003)
+ >>> print srs.projected, srs.geographic
+ False True
+ >>> srs.import_epsg(32140)
+ >>> print srs.name
+ NAD83 / Texas South Central
+"""
+import re
+from ctypes import byref, c_char_p, c_int, c_void_p
+
+# Getting the error checking routine and exceptions
+from django.contrib.gis.gdal.base import GDALBase
+from django.contrib.gis.gdal.error import OGRException, SRSException
+from django.contrib.gis.gdal.prototypes import srs as capi
+
+#### Spatial Reference class. ####
+class SpatialReference(GDALBase):
+ """
+ A wrapper for the OGRSpatialReference object. According to the GDAL Web site,
+ the SpatialReference object "provide[s] services to represent coordinate
+ systems (projections and datums) and to transform between them."
+ """
+
+ #### Python 'magic' routines ####
+ def __init__(self, srs_input=''):
+ """
+ Creates a GDAL OSR Spatial Reference object from the given input.
+ The input may be string of OGC Well Known Text (WKT), an integer
+ EPSG code, a PROJ.4 string, and/or a projection "well known" shorthand
+ string (one of 'WGS84', 'WGS72', 'NAD27', 'NAD83').
+ """
+ buf = c_char_p('')
+ srs_type = 'user'
+
+ if isinstance(srs_input, basestring):
+ # Encoding to ASCII if unicode passed in.
+ if isinstance(srs_input, unicode):
+ srs_input = srs_input.encode('ascii')
+ try:
+ # If SRID is a string, e.g., '4326', then make acceptable
+ # as user input.
+ srid = int(srs_input)
+ srs_input = 'EPSG:%d' % srid
+ except ValueError:
+ pass
+ elif isinstance(srs_input, (int, long)):
+ # EPSG integer code was input.
+ srs_type = 'epsg'
+ elif isinstance(srs_input, self.ptr_type):
+ srs = srs_input
+ srs_type = 'ogr'
+ else:
+ raise TypeError('Invalid SRS type "%s"' % srs_type)
+
+ if srs_type == 'ogr':
+ # Input is already an SRS pointer.
+ srs = srs_input
+ else:
+ # Creating a new SRS pointer, using the string buffer.
+ srs = capi.new_srs(buf)
+
+ # If the pointer is NULL, throw an exception.
+ if not srs:
+ raise SRSException('Could not create spatial reference from: %s' % srs_input)
+ else:
+ self.ptr = srs
+
+ # Importing from either the user input string or an integer SRID.
+ if srs_type == 'user':
+ self.import_user_input(srs_input)
+ elif srs_type == 'epsg':
+ self.import_epsg(srs_input)
+
+ def __del__(self):
+ "Destroys this spatial reference."
+ if self._ptr: capi.release_srs(self._ptr)
+
+ def __getitem__(self, target):
+ """
+ Returns the value of the given string attribute node, None if the node
+ doesn't exist. Can also take a tuple as a parameter, (target, child),
+ where child is the index of the attribute in the WKT. For example:
+
+ >>> wkt = 'GEOGCS["WGS 84", DATUM["WGS_1984, ... AUTHORITY["EPSG","4326"]]')
+ >>> srs = SpatialReference(wkt) # could also use 'WGS84', or 4326
+ >>> print srs['GEOGCS']
+ WGS 84
+ >>> print srs['DATUM']
+ WGS_1984
+ >>> print srs['AUTHORITY']
+ EPSG
+ >>> print srs['AUTHORITY', 1] # The authority value
+ 4326
+ >>> print srs['TOWGS84', 4] # the fourth value in this wkt
+ 0
+ >>> print srs['UNIT|AUTHORITY'] # For the units authority, have to use the pipe symbole.
+ EPSG
+ >>> print srs['UNIT|AUTHORITY', 1] # The authority value for the untis
+ 9122
+ """
+ if isinstance(target, tuple):
+ return self.attr_value(*target)
+ else:
+ return self.attr_value(target)
+
+ def __str__(self):
+ "The string representation uses 'pretty' WKT."
+ return self.pretty_wkt
+
+ #### SpatialReference Methods ####
+ def attr_value(self, target, index=0):
+ """
+ The attribute value for the given target node (e.g. 'PROJCS'). The index
+ keyword specifies an index of the child node to return.
+ """
+ if not isinstance(target, basestring) or not isinstance(index, int):
+ raise TypeError
+ return capi.get_attr_value(self.ptr, target, index)
+
+ def auth_name(self, target):
+ "Returns the authority name for the given string target node."
+ return capi.get_auth_name(self.ptr, target)
+
+ def auth_code(self, target):
+ "Returns the authority code for the given string target node."
+ return capi.get_auth_code(self.ptr, target)
+
+ def clone(self):
+ "Returns a clone of this SpatialReference object."
+ return SpatialReference(capi.clone_srs(self.ptr))
+
+ def from_esri(self):
+ "Morphs this SpatialReference from ESRI's format to EPSG."
+ capi.morph_from_esri(self.ptr)
+
+ def identify_epsg(self):
+ """
+ This method inspects the WKT of this SpatialReference, and will
+ add EPSG authority nodes where an EPSG identifier is applicable.
+ """
+ capi.identify_epsg(self.ptr)
+
+ def to_esri(self):
+ "Morphs this SpatialReference to ESRI's format."
+ capi.morph_to_esri(self.ptr)
+
+ def validate(self):
+ "Checks to see if the given spatial reference is valid."
+ capi.srs_validate(self.ptr)
+
+ #### Name & SRID properties ####
+ @property
+ def name(self):
+ "Returns the name of this Spatial Reference."
+ if self.projected: return self.attr_value('PROJCS')
+ elif self.geographic: return self.attr_value('GEOGCS')
+ elif self.local: return self.attr_value('LOCAL_CS')
+ else: return None
+
+ @property
+ def srid(self):
+ "Returns the SRID of top-level authority, or None if undefined."
+ try:
+ return int(self.attr_value('AUTHORITY', 1))
+ except (TypeError, ValueError):
+ return None
+
+ #### Unit Properties ####
+ @property
+ def linear_name(self):
+ "Returns the name of the linear units."
+ units, name = capi.linear_units(self.ptr, byref(c_char_p()))
+ return name
+
+ @property
+ def linear_units(self):
+ "Returns the value of the linear units."
+ units, name = capi.linear_units(self.ptr, byref(c_char_p()))
+ return units
+
+ @property
+ def angular_name(self):
+ "Returns the name of the angular units."
+ units, name = capi.angular_units(self.ptr, byref(c_char_p()))
+ return name
+
+ @property
+ def angular_units(self):
+ "Returns the value of the angular units."
+ units, name = capi.angular_units(self.ptr, byref(c_char_p()))
+ return units
+
+ @property
+ def units(self):
+ """
+ Returns a 2-tuple of the units value and the units name,
+ and will automatically determines whether to return the linear
+ or angular units.
+ """
+ if self.projected or self.local:
+ return capi.linear_units(self.ptr, byref(c_char_p()))
+ elif self.geographic:
+ return capi.angular_units(self.ptr, byref(c_char_p()))
+ else:
+ return (None, None)
+
+ #### Spheroid/Ellipsoid Properties ####
+ @property
+ def ellipsoid(self):
+ """
+ Returns a tuple of the ellipsoid parameters:
+ (semimajor axis, semiminor axis, and inverse flattening)
+ """
+ return (self.semi_major, self.semi_minor, self.inverse_flattening)
+
+ @property
+ def semi_major(self):
+ "Returns the Semi Major Axis for this Spatial Reference."
+ return capi.semi_major(self.ptr, byref(c_int()))
+
+ @property
+ def semi_minor(self):
+ "Returns the Semi Minor Axis for this Spatial Reference."
+ return capi.semi_minor(self.ptr, byref(c_int()))
+
+ @property
+ def inverse_flattening(self):
+ "Returns the Inverse Flattening for this Spatial Reference."
+ return capi.invflattening(self.ptr, byref(c_int()))
+
+ #### Boolean Properties ####
+ @property
+ def geographic(self):
+ """
+ Returns True if this SpatialReference is geographic
+ (root node is GEOGCS).
+ """
+ return bool(capi.isgeographic(self.ptr))
+
+ @property
+ def local(self):
+ "Returns True if this SpatialReference is local (root node is LOCAL_CS)."
+ return bool(capi.islocal(self.ptr))
+
+ @property
+ def projected(self):
+ """
+ Returns True if this SpatialReference is a projected coordinate system
+ (root node is PROJCS).
+ """
+ return bool(capi.isprojected(self.ptr))
+
+ #### Import Routines #####
+ def import_epsg(self, epsg):
+ "Imports the Spatial Reference from the EPSG code (an integer)."
+ capi.from_epsg(self.ptr, epsg)
+
+ def import_proj(self, proj):
+ "Imports the Spatial Reference from a PROJ.4 string."
+ capi.from_proj(self.ptr, proj)
+
+ def import_user_input(self, user_input):
+ "Imports the Spatial Reference from the given user input string."
+ capi.from_user_input(self.ptr, user_input)
+
+ def import_wkt(self, wkt):
+ "Imports the Spatial Reference from OGC WKT (string)"
+ capi.from_wkt(self.ptr, byref(c_char_p(wkt)))
+
+ def import_xml(self, xml):
+ "Imports the Spatial Reference from an XML string."
+ capi.from_xml(self.ptr, xml)
+
+ #### Export Properties ####
+ @property
+ def wkt(self):
+ "Returns the WKT representation of this Spatial Reference."
+ return capi.to_wkt(self.ptr, byref(c_char_p()))
+
+ @property
+ def pretty_wkt(self, simplify=0):
+ "Returns the 'pretty' representation of the WKT."
+ return capi.to_pretty_wkt(self.ptr, byref(c_char_p()), simplify)
+
+ @property
+ def proj(self):
+ "Returns the PROJ.4 representation for this Spatial Reference."
+ return capi.to_proj(self.ptr, byref(c_char_p()))
+
+ @property
+ def proj4(self):
+ "Alias for proj()."
+ return self.proj
+
+ @property
+ def xml(self, dialect=''):
+ "Returns the XML representation of this Spatial Reference."
+ return capi.to_xml(self.ptr, byref(c_char_p()), dialect)
+
+class CoordTransform(GDALBase):
+ "The coordinate system transformation object."
+
+ def __init__(self, source, target):
+ "Initializes on a source and target SpatialReference objects."
+ if not isinstance(source, SpatialReference) or not isinstance(target, SpatialReference):
+ raise TypeError('source and target must be of type SpatialReference')
+ self.ptr = capi.new_ct(source._ptr, target._ptr)
+ self._srs1_name = source.name
+ self._srs2_name = target.name
+
+ def __del__(self):
+ "Deletes this Coordinate Transformation object."
+ if self._ptr: capi.destroy_ct(self._ptr)
+
+ def __str__(self):
+ return 'Transform from "%s" to "%s"' % (self._srs1_name, self._srs2_name)
diff --git a/parts/django/django/contrib/gis/gdal/tests/__init__.py b/parts/django/django/contrib/gis/gdal/tests/__init__.py
new file mode 100644
index 0000000..aada5f4
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/tests/__init__.py
@@ -0,0 +1,25 @@
+"""
+Module for executing all of the GDAL tests. None
+of these tests require the use of the database.
+"""
+from unittest import TestSuite, TextTestRunner
+
+# Importing the GDAL test modules.
+import test_driver, test_ds, test_envelope, test_geom, test_srs
+
+test_suites = [test_driver.suite(),
+ test_ds.suite(),
+ test_envelope.suite(),
+ test_geom.suite(),
+ test_srs.suite(),
+ ]
+
+def suite():
+ "Builds a test suite for the GDAL tests."
+ s = TestSuite()
+ map(s.addTest, test_suites)
+ return s
+
+def run(verbosity=1):
+ "Runs the GDAL tests."
+ TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/gdal/tests/test_driver.py b/parts/django/django/contrib/gis/gdal/tests/test_driver.py
new file mode 100644
index 0000000..1ff65ac
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/tests/test_driver.py
@@ -0,0 +1,40 @@
+import os, os.path, unittest
+from django.contrib.gis.gdal import Driver, OGRException
+
+valid_drivers = ('ESRI Shapefile', 'MapInfo File', 'TIGER', 'S57', 'DGN',
+ 'Memory', 'CSV', 'GML', 'KML')
+
+invalid_drivers = ('Foo baz', 'clucka', 'ESRI Shp')
+
+aliases = {'eSrI' : 'ESRI Shapefile',
+ 'TigER/linE' : 'TIGER',
+ 'SHAPE' : 'ESRI Shapefile',
+ 'sHp' : 'ESRI Shapefile',
+ }
+
+class DriverTest(unittest.TestCase):
+
+ def test01_valid_driver(self):
+ "Testing valid OGR Data Source Drivers."
+ for d in valid_drivers:
+ dr = Driver(d)
+ self.assertEqual(d, str(dr))
+
+ def test02_invalid_driver(self):
+ "Testing invalid OGR Data Source Drivers."
+ for i in invalid_drivers:
+ self.assertRaises(OGRException, Driver, i)
+
+ def test03_aliases(self):
+ "Testing driver aliases."
+ for alias, full_name in aliases.items():
+ dr = Driver(alias)
+ self.assertEqual(full_name, str(dr))
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(DriverTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/gdal/tests/test_ds.py b/parts/django/django/contrib/gis/gdal/tests/test_ds.py
new file mode 100644
index 0000000..e1083b2
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/tests/test_ds.py
@@ -0,0 +1,226 @@
+import os, os.path, unittest
+from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION
+from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
+from django.contrib.gis.geometry.test_data import get_ds_file, TestDS
+
+# List of acceptable data sources.
+ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile',
+ fields={'dbl' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,},
+ extent=(-1.35011,0.166623,-0.524093,0.824508), # Got extent from QGIS
+ srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]',
+ field_values={'dbl' : [float(i) for i in range(1, 6)], 'int' : range(1, 6), 'str' : [str(i) for i in range(1, 6)]},
+ fids=range(5)),
+ TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype='Point25D', driver='VRT',
+ fields={'POINT_X' : OFTString, 'POINT_Y' : OFTString, 'NUM' : OFTString}, # VRT uses CSV, which all types are OFTString.
+ extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV
+ field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']},
+ fids=range(1,4)),
+ TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3,
+ driver='ESRI Shapefile',
+ fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,},
+ extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS
+ srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),
+ )
+
+bad_ds = (TestDS('foo'),
+ )
+
+class DataSourceTest(unittest.TestCase):
+
+ def test01_valid_shp(self):
+ "Testing valid SHP Data Source files."
+
+ for source in ds_list:
+ # Loading up the data source
+ ds = DataSource(source.ds)
+
+ # Making sure the layer count is what's expected (only 1 layer in a SHP file)
+ self.assertEqual(1, len(ds))
+
+ # Making sure GetName works
+ self.assertEqual(source.ds, ds.name)
+
+ # Making sure the driver name matches up
+ self.assertEqual(source.driver, str(ds.driver))
+
+ # Making sure indexing works
+ try:
+ ds[len(ds)]
+ except OGRIndexError:
+ pass
+ else:
+ self.fail('Expected an IndexError!')
+
+ def test02_invalid_shp(self):
+ "Testing invalid SHP files for the Data Source."
+ for source in bad_ds:
+ self.assertRaises(OGRException, DataSource, source.ds)
+
+ def test03a_layers(self):
+ "Testing Data Source Layers."
+ print "\nBEGIN - expecting out of range feature id error; safe to ignore.\n"
+ for source in ds_list:
+ ds = DataSource(source.ds)
+
+ # Incrementing through each layer, this tests DataSource.__iter__
+ for layer in ds:
+ # Making sure we get the number of features we expect
+ self.assertEqual(len(layer), source.nfeat)
+
+ # Making sure we get the number of fields we expect
+ self.assertEqual(source.nfld, layer.num_fields)
+ self.assertEqual(source.nfld, len(layer.fields))
+
+ # Testing the layer's extent (an Envelope), and it's properties
+ if source.driver == 'VRT' and (GDAL_VERSION > (1, 7, 0) and GDAL_VERSION < (1, 7, 3)):
+ # There's a known GDAL regression with retrieving the extent
+ # of a VRT layer in versions 1.7.0-1.7.2:
+ # http://trac.osgeo.org/gdal/ticket/3783
+ pass
+ else:
+ self.assertEqual(True, isinstance(layer.extent, Envelope))
+ self.assertAlmostEqual(source.extent[0], layer.extent.min_x, 5)
+ self.assertAlmostEqual(source.extent[1], layer.extent.min_y, 5)
+ self.assertAlmostEqual(source.extent[2], layer.extent.max_x, 5)
+ self.assertAlmostEqual(source.extent[3], layer.extent.max_y, 5)
+
+ # Now checking the field names.
+ flds = layer.fields
+ for f in flds: self.assertEqual(True, f in source.fields)
+
+ # Negative FIDs are not allowed.
+ self.assertRaises(OGRIndexError, layer.__getitem__, -1)
+ self.assertRaises(OGRIndexError, layer.__getitem__, 50000)
+
+ if hasattr(source, 'field_values'):
+ fld_names = source.field_values.keys()
+
+ # Testing `Layer.get_fields` (which uses Layer.__iter__)
+ for fld_name in fld_names:
+ self.assertEqual(source.field_values[fld_name], layer.get_fields(fld_name))
+
+ # Testing `Layer.__getitem__`.
+ for i, fid in enumerate(source.fids):
+ feat = layer[fid]
+ self.assertEqual(fid, feat.fid)
+ # Maybe this should be in the test below, but we might as well test
+ # the feature values here while in this loop.
+ for fld_name in fld_names:
+ self.assertEqual(source.field_values[fld_name][i], feat.get(fld_name))
+ print "\nEND - expecting out of range feature id error; safe to ignore."
+
+ def test03b_layer_slice(self):
+ "Test indexing and slicing on Layers."
+ # Using the first data-source because the same slice
+ # can be used for both the layer and the control values.
+ source = ds_list[0]
+ ds = DataSource(source.ds)
+
+ sl = slice(1, 3)
+ feats = ds[0][sl]
+
+ for fld_name in ds[0].fields:
+ test_vals = [feat.get(fld_name) for feat in feats]
+ control_vals = source.field_values[fld_name][sl]
+ self.assertEqual(control_vals, test_vals)
+
+ def test03c_layer_references(self):
+ "Test to make sure Layer access is still available without the DataSource."
+ source = ds_list[0]
+
+ # See ticket #9448.
+ def get_layer():
+ # This DataSource object is not accessible outside this
+ # scope. However, a reference should still be kept alive
+ # on the `Layer` returned.
+ ds = DataSource(source.ds)
+ return ds[0]
+
+ # Making sure we can call OGR routines on the Layer returned.
+ lyr = get_layer()
+ self.assertEqual(source.nfeat, len(lyr))
+ self.assertEqual(source.gtype, lyr.geom_type.num)
+
+ def test04_features(self):
+ "Testing Data Source Features."
+ for source in ds_list:
+ ds = DataSource(source.ds)
+
+ # Incrementing through each layer
+ for layer in ds:
+ # Incrementing through each feature in the layer
+ for feat in layer:
+ # Making sure the number of fields, and the geometry type
+ # are what's expected.
+ self.assertEqual(source.nfld, len(list(feat)))
+ self.assertEqual(source.gtype, feat.geom_type)
+
+ # Making sure the fields match to an appropriate OFT type.
+ for k, v in source.fields.items():
+ # Making sure we get the proper OGR Field instance, using
+ # a string value index for the feature.
+ self.assertEqual(True, isinstance(feat[k], v))
+
+ # Testing Feature.__iter__
+ for fld in feat: self.assertEqual(True, fld.name in source.fields.keys())
+
+ def test05_geometries(self):
+ "Testing Geometries from Data Source Features."
+ for source in ds_list:
+ ds = DataSource(source.ds)
+
+ # Incrementing through each layer and feature.
+ for layer in ds:
+ for feat in layer:
+ g = feat.geom
+
+ # Making sure we get the right Geometry name & type
+ self.assertEqual(source.geom, g.geom_name)
+ self.assertEqual(source.gtype, g.geom_type)
+
+ # Making sure the SpatialReference is as expected.
+ if hasattr(source, 'srs_wkt'):
+ self.assertEqual(source.srs_wkt, g.srs.wkt)
+
+ def test06_spatial_filter(self):
+ "Testing the Layer.spatial_filter property."
+ ds = DataSource(get_ds_file('cities', 'shp'))
+ lyr = ds[0]
+
+ # When not set, it should be None.
+ self.assertEqual(None, lyr.spatial_filter)
+
+ # Must be set a/an OGRGeometry or 4-tuple.
+ self.assertRaises(TypeError, lyr._set_spatial_filter, 'foo')
+
+ # Setting the spatial filter with a tuple/list with the extent of
+ # a buffer centering around Pueblo.
+ self.assertRaises(ValueError, lyr._set_spatial_filter, range(5))
+ filter_extent = (-105.609252, 37.255001, -103.609252, 39.255001)
+ lyr.spatial_filter = (-105.609252, 37.255001, -103.609252, 39.255001)
+ self.assertEqual(OGRGeometry.from_bbox(filter_extent), lyr.spatial_filter)
+ feats = [feat for feat in lyr]
+ self.assertEqual(1, len(feats))
+ self.assertEqual('Pueblo', feats[0].get('Name'))
+
+ # Setting the spatial filter with an OGRGeometry for buffer centering
+ # around Houston.
+ filter_geom = OGRGeometry('POLYGON((-96.363151 28.763374,-94.363151 28.763374,-94.363151 30.763374,-96.363151 30.763374,-96.363151 28.763374))')
+ lyr.spatial_filter = filter_geom
+ self.assertEqual(filter_geom, lyr.spatial_filter)
+ feats = [feat for feat in lyr]
+ self.assertEqual(1, len(feats))
+ self.assertEqual('Houston', feats[0].get('Name'))
+
+ # Clearing the spatial filter by setting it to None. Now
+ # should indicate that there are 3 features in the Layer.
+ lyr.spatial_filter = None
+ self.assertEqual(3, len(lyr))
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(DataSourceTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/gdal/tests/test_envelope.py b/parts/django/django/contrib/gis/gdal/tests/test_envelope.py
new file mode 100644
index 0000000..f181fa2
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/tests/test_envelope.py
@@ -0,0 +1,94 @@
+import unittest
+from django.contrib.gis.gdal import Envelope, OGRException
+
+class TestPoint(object):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+class EnvelopeTest(unittest.TestCase):
+
+ def setUp(self):
+ self.e = Envelope(0, 0, 5, 5)
+
+ def test01_init(self):
+ "Testing Envelope initilization."
+ e1 = Envelope((0, 0, 5, 5))
+ e2 = Envelope(0, 0, 5, 5)
+ e3 = Envelope(0, '0', '5', 5) # Thanks to ww for this
+ e4 = Envelope(e1._envelope)
+ self.assertRaises(OGRException, Envelope, (5, 5, 0, 0))
+ self.assertRaises(OGRException, Envelope, 5, 5, 0, 0)
+ self.assertRaises(OGRException, Envelope, (0, 0, 5, 5, 3))
+ self.assertRaises(OGRException, Envelope, ())
+ self.assertRaises(ValueError, Envelope, 0, 'a', 5, 5)
+ self.assertRaises(TypeError, Envelope, u'foo')
+ self.assertRaises(OGRException, Envelope, (1, 1, 0, 0))
+ try:
+ Envelope(0, 0, 0, 0)
+ except OGRException:
+ self.fail("shouldn't raise an exception for min_x == max_x or min_y == max_y")
+
+ def test02_properties(self):
+ "Testing Envelope properties."
+ e = Envelope(0, 0, 2, 3)
+ self.assertEqual(0, e.min_x)
+ self.assertEqual(0, e.min_y)
+ self.assertEqual(2, e.max_x)
+ self.assertEqual(3, e.max_y)
+ self.assertEqual((0, 0), e.ll)
+ self.assertEqual((2, 3), e.ur)
+ self.assertEqual((0, 0, 2, 3), e.tuple)
+ self.assertEqual('POLYGON((0.0 0.0,0.0 3.0,2.0 3.0,2.0 0.0,0.0 0.0))', e.wkt)
+ self.assertEqual('(0.0, 0.0, 2.0, 3.0)', str(e))
+
+ def test03_equivalence(self):
+ "Testing Envelope equivalence."
+ e1 = Envelope(0.523, 0.217, 253.23, 523.69)
+ e2 = Envelope((0.523, 0.217, 253.23, 523.69))
+ self.assertEqual(e1, e2)
+ self.assertEqual((0.523, 0.217, 253.23, 523.69), e1)
+
+ def test04_expand_to_include_pt_2_params(self):
+ "Testing Envelope expand_to_include -- point as two parameters."
+ self.e.expand_to_include(2, 6)
+ self.assertEqual((0, 0, 5, 6), self.e)
+ self.e.expand_to_include(-1, -1)
+ self.assertEqual((-1, -1, 5, 6), self.e)
+
+ def test05_expand_to_include_pt_2_tuple(self):
+ "Testing Envelope expand_to_include -- point as a single 2-tuple parameter."
+ self.e.expand_to_include((10, 10))
+ self.assertEqual((0, 0, 10, 10), self.e)
+ self.e.expand_to_include((-10, -10))
+ self.assertEqual((-10, -10, 10, 10), self.e)
+
+ def test06_expand_to_include_extent_4_params(self):
+ "Testing Envelope expand_to_include -- extent as 4 parameters."
+ self.e.expand_to_include(-1, 1, 3, 7)
+ self.assertEqual((-1, 0, 5, 7), self.e)
+
+ def test06_expand_to_include_extent_4_tuple(self):
+ "Testing Envelope expand_to_include -- extent as a single 4-tuple parameter."
+ self.e.expand_to_include((-1, 1, 3, 7))
+ self.assertEqual((-1, 0, 5, 7), self.e)
+
+ def test07_expand_to_include_envelope(self):
+ "Testing Envelope expand_to_include with Envelope as parameter."
+ self.e.expand_to_include(Envelope(-1, 1, 3, 7))
+ self.assertEqual((-1, 0, 5, 7), self.e)
+
+ def test08_expand_to_include_point(self):
+ "Testing Envelope expand_to_include with Point as parameter."
+ self.e.expand_to_include(TestPoint(-1, 1))
+ self.assertEqual((-1, 0, 5, 5), self.e)
+ self.e.expand_to_include(TestPoint(10, 10))
+ self.assertEqual((-1, 0, 10, 10), self.e)
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(EnvelopeTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/gdal/tests/test_geom.py b/parts/django/django/contrib/gis/gdal/tests/test_geom.py
new file mode 100644
index 0000000..f3d1ffb
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/tests/test_geom.py
@@ -0,0 +1,490 @@
+import unittest
+from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, \
+ OGRException, OGRIndexError, SpatialReference, CoordTransform, \
+ gdal_version
+from django.contrib.gis.geometry.test_data import TestDataMixin
+
+class OGRGeomTest(unittest.TestCase, TestDataMixin):
+ "This tests the OGR Geometry."
+
+ def test00a_geomtype(self):
+ "Testing OGRGeomType object."
+
+ # OGRGeomType should initialize on all these inputs.
+ try:
+ g = OGRGeomType(1)
+ g = OGRGeomType(7)
+ g = OGRGeomType('point')
+ g = OGRGeomType('GeometrycollectioN')
+ g = OGRGeomType('LINearrING')
+ g = OGRGeomType('Unknown')
+ except:
+ self.fail('Could not create an OGRGeomType object!')
+
+ # Should throw TypeError on this input
+ self.assertRaises(OGRException, OGRGeomType, 23)
+ self.assertRaises(OGRException, OGRGeomType, 'fooD')
+ self.assertRaises(OGRException, OGRGeomType, 9)
+
+ # Equivalence can take strings, ints, and other OGRGeomTypes
+ self.assertEqual(True, OGRGeomType(1) == OGRGeomType(1))
+ self.assertEqual(True, OGRGeomType(7) == 'GeometryCollection')
+ self.assertEqual(True, OGRGeomType('point') == 'POINT')
+ self.assertEqual(False, OGRGeomType('point') == 2)
+ self.assertEqual(True, OGRGeomType('unknown') == 0)
+ self.assertEqual(True, OGRGeomType(6) == 'MULtiPolyGON')
+ self.assertEqual(False, OGRGeomType(1) != OGRGeomType('point'))
+ self.assertEqual(True, OGRGeomType('POINT') != OGRGeomType(6))
+
+ # Testing the Django field name equivalent property.
+ self.assertEqual('PointField', OGRGeomType('Point').django)
+ self.assertEqual('GeometryField', OGRGeomType('Unknown').django)
+ self.assertEqual(None, OGRGeomType('none').django)
+
+ # 'Geometry' initialization implies an unknown geometry type.
+ gt = OGRGeomType('Geometry')
+ self.assertEqual(0, gt.num)
+ self.assertEqual('Unknown', gt.name)
+
+ def test00b_geomtype_25d(self):
+ "Testing OGRGeomType object with 25D types."
+ wkb25bit = OGRGeomType.wkb25bit
+ self.failUnless(OGRGeomType(wkb25bit + 1) == 'Point25D')
+ self.failUnless(OGRGeomType('MultiLineString25D') == (5 + wkb25bit))
+ self.assertEqual('GeometryCollectionField', OGRGeomType('GeometryCollection25D').django)
+
+ def test01a_wkt(self):
+ "Testing WKT output."
+ for g in self.geometries.wkt_out:
+ geom = OGRGeometry(g.wkt)
+ self.assertEqual(g.wkt, geom.wkt)
+
+ def test01a_ewkt(self):
+ "Testing EWKT input/output."
+ for ewkt_val in ('POINT (1 2 3)', 'LINEARRING (0 0,1 1,2 1,0 0)'):
+ # First with ewkt output when no SRID in EWKT
+ self.assertEqual(ewkt_val, OGRGeometry(ewkt_val).ewkt)
+ # No test consumption with an SRID specified.
+ ewkt_val = 'SRID=4326;%s' % ewkt_val
+ geom = OGRGeometry(ewkt_val)
+ self.assertEqual(ewkt_val, geom.ewkt)
+ self.assertEqual(4326, geom.srs.srid)
+
+ def test01b_gml(self):
+ "Testing GML output."
+ for g in self.geometries.wkt_out:
+ geom = OGRGeometry(g.wkt)
+ self.assertEqual(g.gml, geom.gml)
+
+ def test01c_hex(self):
+ "Testing HEX input/output."
+ for g in self.geometries.hex_wkt:
+ geom1 = OGRGeometry(g.wkt)
+ self.assertEqual(g.hex, geom1.hex)
+ # Constructing w/HEX
+ geom2 = OGRGeometry(g.hex)
+ self.assertEqual(geom1, geom2)
+
+ def test01d_wkb(self):
+ "Testing WKB input/output."
+ from binascii import b2a_hex
+ for g in self.geometries.hex_wkt:
+ geom1 = OGRGeometry(g.wkt)
+ wkb = geom1.wkb
+ self.assertEqual(b2a_hex(wkb).upper(), g.hex)
+ # Constructing w/WKB.
+ geom2 = OGRGeometry(wkb)
+ self.assertEqual(geom1, geom2)
+
+ def test01e_json(self):
+ "Testing GeoJSON input/output."
+ from django.contrib.gis.gdal.prototypes.geom import GEOJSON
+ if not GEOJSON: return
+ for g in self.geometries.json_geoms:
+ geom = OGRGeometry(g.wkt)
+ if not hasattr(g, 'not_equal'):
+ self.assertEqual(g.json, geom.json)
+ self.assertEqual(g.json, geom.geojson)
+ self.assertEqual(OGRGeometry(g.wkt), OGRGeometry(geom.json))
+
+ def test02_points(self):
+ "Testing Point objects."
+
+ prev = OGRGeometry('POINT(0 0)')
+ for p in self.geometries.points:
+ if not hasattr(p, 'z'): # No 3D
+ pnt = OGRGeometry(p.wkt)
+ self.assertEqual(1, pnt.geom_type)
+ self.assertEqual('POINT', pnt.geom_name)
+ self.assertEqual(p.x, pnt.x)
+ self.assertEqual(p.y, pnt.y)
+ self.assertEqual((p.x, p.y), pnt.tuple)
+
+ def test03_multipoints(self):
+ "Testing MultiPoint objects."
+ for mp in self.geometries.multipoints:
+ mgeom1 = OGRGeometry(mp.wkt) # First one from WKT
+ self.assertEqual(4, mgeom1.geom_type)
+ self.assertEqual('MULTIPOINT', mgeom1.geom_name)
+ mgeom2 = OGRGeometry('MULTIPOINT') # Creating empty multipoint
+ mgeom3 = OGRGeometry('MULTIPOINT')
+ for g in mgeom1:
+ mgeom2.add(g) # adding each point from the multipoints
+ mgeom3.add(g.wkt) # should take WKT as well
+ self.assertEqual(mgeom1, mgeom2) # they should equal
+ self.assertEqual(mgeom1, mgeom3)
+ self.assertEqual(mp.coords, mgeom2.coords)
+ self.assertEqual(mp.n_p, mgeom2.point_count)
+
+ def test04_linestring(self):
+ "Testing LineString objects."
+ prev = OGRGeometry('POINT(0 0)')
+ for ls in self.geometries.linestrings:
+ linestr = OGRGeometry(ls.wkt)
+ self.assertEqual(2, linestr.geom_type)
+ self.assertEqual('LINESTRING', linestr.geom_name)
+ self.assertEqual(ls.n_p, linestr.point_count)
+ self.assertEqual(ls.coords, linestr.tuple)
+ self.assertEqual(True, linestr == OGRGeometry(ls.wkt))
+ self.assertEqual(True, linestr != prev)
+ self.assertRaises(OGRIndexError, linestr.__getitem__, len(linestr))
+ prev = linestr
+
+ # Testing the x, y properties.
+ x = [tmpx for tmpx, tmpy in ls.coords]
+ y = [tmpy for tmpx, tmpy in ls.coords]
+ self.assertEqual(x, linestr.x)
+ self.assertEqual(y, linestr.y)
+
+ def test05_multilinestring(self):
+ "Testing MultiLineString objects."
+ prev = OGRGeometry('POINT(0 0)')
+ for mls in self.geometries.multilinestrings:
+ mlinestr = OGRGeometry(mls.wkt)
+ self.assertEqual(5, mlinestr.geom_type)
+ self.assertEqual('MULTILINESTRING', mlinestr.geom_name)
+ self.assertEqual(mls.n_p, mlinestr.point_count)
+ self.assertEqual(mls.coords, mlinestr.tuple)
+ self.assertEqual(True, mlinestr == OGRGeometry(mls.wkt))
+ self.assertEqual(True, mlinestr != prev)
+ prev = mlinestr
+ for ls in mlinestr:
+ self.assertEqual(2, ls.geom_type)
+ self.assertEqual('LINESTRING', ls.geom_name)
+ self.assertRaises(OGRIndexError, mlinestr.__getitem__, len(mlinestr))
+
+ def test06_linearring(self):
+ "Testing LinearRing objects."
+ prev = OGRGeometry('POINT(0 0)')
+ for rr in self.geometries.linearrings:
+ lr = OGRGeometry(rr.wkt)
+ #self.assertEqual(101, lr.geom_type.num)
+ self.assertEqual('LINEARRING', lr.geom_name)
+ self.assertEqual(rr.n_p, len(lr))
+ self.assertEqual(True, lr == OGRGeometry(rr.wkt))
+ self.assertEqual(True, lr != prev)
+ prev = lr
+
+ def test07a_polygons(self):
+ "Testing Polygon objects."
+
+ # Testing `from_bbox` class method
+ bbox = (-180,-90,180,90)
+ p = OGRGeometry.from_bbox( bbox )
+ self.assertEqual(bbox, p.extent)
+
+ prev = OGRGeometry('POINT(0 0)')
+ for p in self.geometries.polygons:
+ poly = OGRGeometry(p.wkt)
+ self.assertEqual(3, poly.geom_type)
+ self.assertEqual('POLYGON', poly.geom_name)
+ self.assertEqual(p.n_p, poly.point_count)
+ self.assertEqual(p.n_i + 1, len(poly))
+
+ # Testing area & centroid.
+ self.assertAlmostEqual(p.area, poly.area, 9)
+ x, y = poly.centroid.tuple
+ self.assertAlmostEqual(p.centroid[0], x, 9)
+ self.assertAlmostEqual(p.centroid[1], y, 9)
+
+ # Testing equivalence
+ self.assertEqual(True, poly == OGRGeometry(p.wkt))
+ self.assertEqual(True, poly != prev)
+
+ if p.ext_ring_cs:
+ ring = poly[0]
+ self.assertEqual(p.ext_ring_cs, ring.tuple)
+ self.assertEqual(p.ext_ring_cs, poly[0].tuple)
+ self.assertEqual(len(p.ext_ring_cs), ring.point_count)
+
+ for r in poly:
+ self.assertEqual('LINEARRING', r.geom_name)
+
+ def test07b_closepolygons(self):
+ "Testing closing Polygon objects."
+ # Both rings in this geometry are not closed.
+ poly = OGRGeometry('POLYGON((0 0, 5 0, 5 5, 0 5), (1 1, 2 1, 2 2, 2 1))')
+ self.assertEqual(8, poly.point_count)
+ print "\nBEGIN - expecting IllegalArgumentException; safe to ignore.\n"
+ try:
+ c = poly.centroid
+ except OGRException:
+ # Should raise an OGR exception, rings are not closed
+ pass
+ else:
+ self.fail('Should have raised an OGRException!')
+ print "\nEND - expecting IllegalArgumentException; safe to ignore.\n"
+
+ # Closing the rings -- doesn't work on GDAL versions 1.4.1 and below:
+ # http://trac.osgeo.org/gdal/ticket/1673
+ major, minor1, minor2 = gdal_version().split('.')
+ if major == '1':
+ iminor1 = int(minor1)
+ if iminor1 < 4 or (iminor1 == 4 and minor2.startswith('1')): return
+ poly.close_rings()
+ self.assertEqual(10, poly.point_count) # Two closing points should've been added
+ self.assertEqual(OGRGeometry('POINT(2.5 2.5)'), poly.centroid)
+
+ def test08_multipolygons(self):
+ "Testing MultiPolygon objects."
+ prev = OGRGeometry('POINT(0 0)')
+ for mp in self.geometries.multipolygons:
+ mpoly = OGRGeometry(mp.wkt)
+ self.assertEqual(6, mpoly.geom_type)
+ self.assertEqual('MULTIPOLYGON', mpoly.geom_name)
+ if mp.valid:
+ self.assertEqual(mp.n_p, mpoly.point_count)
+ self.assertEqual(mp.num_geom, len(mpoly))
+ self.assertRaises(OGRIndexError, mpoly.__getitem__, len(mpoly))
+ for p in mpoly:
+ self.assertEqual('POLYGON', p.geom_name)
+ self.assertEqual(3, p.geom_type)
+ self.assertEqual(mpoly.wkt, OGRGeometry(mp.wkt).wkt)
+
+ def test09a_srs(self):
+ "Testing OGR Geometries with Spatial Reference objects."
+ for mp in self.geometries.multipolygons:
+ # Creating a geometry w/spatial reference
+ sr = SpatialReference('WGS84')
+ mpoly = OGRGeometry(mp.wkt, sr)
+ self.assertEqual(sr.wkt, mpoly.srs.wkt)
+
+ # Ensuring that SRS is propagated to clones.
+ klone = mpoly.clone()
+ self.assertEqual(sr.wkt, klone.srs.wkt)
+
+ # Ensuring all children geometries (polygons and their rings) all
+ # return the assigned spatial reference as well.
+ for poly in mpoly:
+ self.assertEqual(sr.wkt, poly.srs.wkt)
+ for ring in poly:
+ self.assertEqual(sr.wkt, ring.srs.wkt)
+
+ # Ensuring SRS propagate in topological ops.
+ a = OGRGeometry(self.geometries.topology_geoms[0].wkt_a, sr)
+ b = OGRGeometry(self.geometries.topology_geoms[0].wkt_b, sr)
+ diff = a.difference(b)
+ union = a.union(b)
+ self.assertEqual(sr.wkt, diff.srs.wkt)
+ self.assertEqual(sr.srid, union.srs.srid)
+
+ # Instantiating w/an integer SRID
+ mpoly = OGRGeometry(mp.wkt, 4326)
+ self.assertEqual(4326, mpoly.srid)
+ mpoly.srs = SpatialReference(4269)
+ self.assertEqual(4269, mpoly.srid)
+ self.assertEqual('NAD83', mpoly.srs.name)
+
+ # Incrementing through the multipolyogn after the spatial reference
+ # has been re-assigned.
+ for poly in mpoly:
+ self.assertEqual(mpoly.srs.wkt, poly.srs.wkt)
+ poly.srs = 32140
+ for ring in poly:
+ # Changing each ring in the polygon
+ self.assertEqual(32140, ring.srs.srid)
+ self.assertEqual('NAD83 / Texas South Central', ring.srs.name)
+ ring.srs = str(SpatialReference(4326)) # back to WGS84
+ self.assertEqual(4326, ring.srs.srid)
+
+ # Using the `srid` property.
+ ring.srid = 4322
+ self.assertEqual('WGS 72', ring.srs.name)
+ self.assertEqual(4322, ring.srid)
+
+ def test09b_srs_transform(self):
+ "Testing transform()."
+ orig = OGRGeometry('POINT (-104.609 38.255)', 4326)
+ trans = OGRGeometry('POINT (992385.4472045 481455.4944650)', 2774)
+
+ # Using an srid, a SpatialReference object, and a CoordTransform object
+ # or transformations.
+ t1, t2, t3 = orig.clone(), orig.clone(), orig.clone()
+ t1.transform(trans.srid)
+ t2.transform(SpatialReference('EPSG:2774'))
+ ct = CoordTransform(SpatialReference('WGS84'), SpatialReference(2774))
+ t3.transform(ct)
+
+ # Testing use of the `clone` keyword.
+ k1 = orig.clone()
+ k2 = k1.transform(trans.srid, clone=True)
+ self.assertEqual(k1, orig)
+ self.assertNotEqual(k1, k2)
+
+ prec = 3
+ for p in (t1, t2, t3, k2):
+ self.assertAlmostEqual(trans.x, p.x, prec)
+ self.assertAlmostEqual(trans.y, p.y, prec)
+
+ def test09c_transform_dim(self):
+ "Testing coordinate dimension is the same on transformed geometries."
+ ls_orig = OGRGeometry('LINESTRING(-104.609 38.255)', 4326)
+ ls_trans = OGRGeometry('LINESTRING(992385.4472045 481455.4944650)', 2774)
+
+ prec = 3
+ ls_orig.transform(ls_trans.srs)
+ # Making sure the coordinate dimension is still 2D.
+ self.assertEqual(2, ls_orig.coord_dim)
+ self.assertAlmostEqual(ls_trans.x[0], ls_orig.x[0], prec)
+ self.assertAlmostEqual(ls_trans.y[0], ls_orig.y[0], prec)
+
+ def test10_difference(self):
+ "Testing difference()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a)
+ b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b)
+ d1 = OGRGeometry(self.geometries.diff_geoms[i].wkt)
+ d2 = a.difference(b)
+ self.assertEqual(d1, d2)
+ self.assertEqual(d1, a - b) # __sub__ is difference operator
+ a -= b # testing __isub__
+ self.assertEqual(d1, a)
+
+ def test11_intersection(self):
+ "Testing intersects() and intersection()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a)
+ b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b)
+ i1 = OGRGeometry(self.geometries.intersect_geoms[i].wkt)
+ self.assertEqual(True, a.intersects(b))
+ i2 = a.intersection(b)
+ self.assertEqual(i1, i2)
+ self.assertEqual(i1, a & b) # __and__ is intersection operator
+ a &= b # testing __iand__
+ self.assertEqual(i1, a)
+
+ def test12_symdifference(self):
+ "Testing sym_difference()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a)
+ b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b)
+ d1 = OGRGeometry(self.geometries.sdiff_geoms[i].wkt)
+ d2 = a.sym_difference(b)
+ self.assertEqual(d1, d2)
+ self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator
+ a ^= b # testing __ixor__
+ self.assertEqual(d1, a)
+
+ def test13_union(self):
+ "Testing union()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = OGRGeometry(self.geometries.topology_geoms[i].wkt_a)
+ b = OGRGeometry(self.geometries.topology_geoms[i].wkt_b)
+ u1 = OGRGeometry(self.geometries.union_geoms[i].wkt)
+ u2 = a.union(b)
+ self.assertEqual(u1, u2)
+ self.assertEqual(u1, a | b) # __or__ is union operator
+ a |= b # testing __ior__
+ self.assertEqual(u1, a)
+
+ def test14_add(self):
+ "Testing GeometryCollection.add()."
+ # Can't insert a Point into a MultiPolygon.
+ mp = OGRGeometry('MultiPolygon')
+ pnt = OGRGeometry('POINT(5 23)')
+ self.assertRaises(OGRException, mp.add, pnt)
+
+ # GeometryCollection.add may take an OGRGeometry (if another collection
+ # of the same type all child geoms will be added individually) or WKT.
+ for mp in self.geometries.multipolygons:
+ mpoly = OGRGeometry(mp.wkt)
+ mp1 = OGRGeometry('MultiPolygon')
+ mp2 = OGRGeometry('MultiPolygon')
+ mp3 = OGRGeometry('MultiPolygon')
+
+ for poly in mpoly:
+ mp1.add(poly) # Adding a geometry at a time
+ mp2.add(poly.wkt) # Adding WKT
+ mp3.add(mpoly) # Adding a MultiPolygon's entire contents at once.
+ for tmp in (mp1, mp2, mp3): self.assertEqual(mpoly, tmp)
+
+ def test15_extent(self):
+ "Testing `extent` property."
+ # The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
+ mp = OGRGeometry('MULTIPOINT(5 23, 0 0, 10 50)')
+ self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent)
+ # Testing on the 'real world' Polygon.
+ poly = OGRGeometry(self.geometries.polygons[3].wkt)
+ ring = poly.shell
+ x, y = ring.x, ring.y
+ xmin, ymin = min(x), min(y)
+ xmax, ymax = max(x), max(y)
+ self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
+
+ def test16_25D(self):
+ "Testing 2.5D geometries."
+ pnt_25d = OGRGeometry('POINT(1 2 3)')
+ self.assertEqual('Point25D', pnt_25d.geom_type.name)
+ self.assertEqual(3.0, pnt_25d.z)
+ self.assertEqual(3, pnt_25d.coord_dim)
+ ls_25d = OGRGeometry('LINESTRING(1 1 1,2 2 2,3 3 3)')
+ self.assertEqual('LineString25D', ls_25d.geom_type.name)
+ self.assertEqual([1.0, 2.0, 3.0], ls_25d.z)
+ self.assertEqual(3, ls_25d.coord_dim)
+
+ def test17_pickle(self):
+ "Testing pickle support."
+ import cPickle
+ g1 = OGRGeometry('LINESTRING(1 1 1,2 2 2,3 3 3)', 'WGS84')
+ g2 = cPickle.loads(cPickle.dumps(g1))
+ self.assertEqual(g1, g2)
+ self.assertEqual(4326, g2.srs.srid)
+ self.assertEqual(g1.srs.wkt, g2.srs.wkt)
+
+ def test18_ogrgeometry_transform_workaround(self):
+ "Testing coordinate dimensions on geometries after transformation."
+ # A bug in GDAL versions prior to 1.7 changes the coordinate
+ # dimension of a geometry after it has been transformed.
+ # This test ensures that the bug workarounds employed within
+ # `OGRGeometry.transform` indeed work.
+ wkt_2d = "MULTILINESTRING ((0 0,1 1,2 2))"
+ wkt_3d = "MULTILINESTRING ((0 0 0,1 1 1,2 2 2))"
+ srid = 4326
+
+ # For both the 2D and 3D MultiLineString, ensure _both_ the dimension
+ # of the collection and the component LineString have the expected
+ # coordinate dimension after transform.
+ geom = OGRGeometry(wkt_2d, srid)
+ geom.transform(srid)
+ self.assertEqual(2, geom.coord_dim)
+ self.assertEqual(2, geom[0].coord_dim)
+ self.assertEqual(wkt_2d, geom.wkt)
+
+ geom = OGRGeometry(wkt_3d, srid)
+ geom.transform(srid)
+ self.assertEqual(3, geom.coord_dim)
+ self.assertEqual(3, geom[0].coord_dim)
+ self.assertEqual(wkt_3d, geom.wkt)
+
+ def test19_equivalence_regression(self):
+ "Testing equivalence methods with non-OGRGeometry instances."
+ self.assertNotEqual(None, OGRGeometry('POINT(0 0)'))
+ self.assertEqual(False, OGRGeometry('LINESTRING(0 0, 1 1)') == 3)
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(OGRGeomTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/gdal/tests/test_srs.py b/parts/django/django/contrib/gis/gdal/tests/test_srs.py
new file mode 100644
index 0000000..2742c7a
--- /dev/null
+++ b/parts/django/django/contrib/gis/gdal/tests/test_srs.py
@@ -0,0 +1,169 @@
+import unittest
+from django.contrib.gis.gdal import SpatialReference, CoordTransform, OGRException, SRSException
+
+class TestSRS:
+ def __init__(self, wkt, **kwargs):
+ self.wkt = wkt
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+# Some Spatial Reference examples
+srlist = (TestSRS('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]',
+ proj='+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ',
+ epsg=4326, projected=False, geographic=True, local=False,
+ lin_name='unknown', ang_name='degree', lin_units=1.0, ang_units=0.0174532925199,
+ auth={'GEOGCS' : ('EPSG', '4326'), 'spheroid' : ('EPSG', '7030')},
+ attr=(('DATUM', 'WGS_1984'), (('SPHEROID', 1), '6378137'),('primem|authority', 'EPSG'),),
+ ),
+ TestSRS('PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]',
+ proj='+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ',
+ epsg=32140, projected=True, geographic=False, local=False,
+ lin_name='metre', ang_name='degree', lin_units=1.0, ang_units=0.0174532925199,
+ auth={'PROJCS' : ('EPSG', '32140'), 'spheroid' : ('EPSG', '7019'), 'unit' : ('EPSG', '9001'),},
+ attr=(('DATUM', 'North_American_Datum_1983'),(('SPHEROID', 2), '298.257222101'),('PROJECTION','Lambert_Conformal_Conic_2SP'),),
+ ),
+ TestSRS('PROJCS["NAD_1983_StatePlane_Texas_South_Central_FIPS_4204_Feet",GEOGCS["GCS_North_American_1983",DATUM["North_American_Datum_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["False_Easting",1968500.0],PARAMETER["False_Northing",13123333.33333333],PARAMETER["Central_Meridian",-99.0],PARAMETER["Standard_Parallel_1",28.38333333333333],PARAMETER["Standard_Parallel_2",30.28333333333334],PARAMETER["Latitude_Of_Origin",27.83333333333333],UNIT["Foot_US",0.3048006096012192]]',
+ proj='+proj=lcc +lat_1=28.38333333333333 +lat_2=30.28333333333334 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=3999999.999999999 +ellps=GRS80 +datum=NAD83 +to_meter=0.3048006096012192 +no_defs ',
+ epsg=None, projected=True, geographic=False, local=False,
+ lin_name='Foot_US', ang_name='Degree', lin_units=0.3048006096012192, ang_units=0.0174532925199,
+ auth={'PROJCS' : (None, None),},
+ attr=(('PROJCS|GeOgCs|spheroid', 'GRS_1980'),(('projcs', 9), 'UNIT'), (('projcs', 11), None),),
+ ),
+ # This is really ESRI format, not WKT -- but the import should work the same
+ TestSRS('LOCAL_CS["Non-Earth (Meter)",LOCAL_DATUM["Local Datum",0],UNIT["Meter",1.0],AXIS["X",EAST],AXIS["Y",NORTH]]',
+ esri=True, proj=None, epsg=None, projected=False, geographic=False, local=True,
+ lin_name='Meter', ang_name='degree', lin_units=1.0, ang_units=0.0174532925199,
+ attr=(('LOCAL_DATUM', 'Local Datum'), ('unit', 'Meter')),
+ ),
+ )
+
+# Well-Known Names
+well_known = (TestSRS('GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]', wk='WGS84', name='WGS 84', attrs=(('GEOGCS|AUTHORITY', 1, '4326'), ('SPHEROID', 'WGS 84'))),
+ TestSRS('GEOGCS["WGS 72",DATUM["WGS_1972",SPHEROID["WGS 72",6378135,298.26,AUTHORITY["EPSG","7043"]],AUTHORITY["EPSG","6322"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4322"]]', wk='WGS72', name='WGS 72', attrs=(('GEOGCS|AUTHORITY', 1, '4322'), ('SPHEROID', 'WGS 72'))),
+ TestSRS('GEOGCS["NAD27",DATUM["North_American_Datum_1927",SPHEROID["Clarke 1866",6378206.4,294.9786982138982,AUTHORITY["EPSG","7008"]],AUTHORITY["EPSG","6267"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4267"]]', wk='NAD27', name='NAD27', attrs=(('GEOGCS|AUTHORITY', 1, '4267'), ('SPHEROID', 'Clarke 1866'))),
+ TestSRS('GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]]', wk='NAD83', name='NAD83', attrs=(('GEOGCS|AUTHORITY', 1, '4269'), ('SPHEROID', 'GRS 1980'))),
+ TestSRS('PROJCS["NZGD49 / Karamea Circuit",GEOGCS["NZGD49",DATUM["New_Zealand_Geodetic_Datum_1949",SPHEROID["International 1924",6378388,297,AUTHORITY["EPSG","7022"]],TOWGS84[59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993],AUTHORITY["EPSG","6272"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4272"]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",-41.28991152777778],PARAMETER["central_meridian",172.1090281944444],PARAMETER["scale_factor",1],PARAMETER["false_easting",300000],PARAMETER["false_northing",700000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","27216"]]', wk='EPSG:27216', name='NZGD49 / Karamea Circuit', attrs=(('PROJECTION','Transverse_Mercator'), ('SPHEROID', 'International 1924'))),
+ )
+
+bad_srlist = ('Foobar', 'OOJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]',)
+
+class SpatialRefTest(unittest.TestCase):
+
+ def test01_wkt(self):
+ "Testing initialization on valid OGC WKT."
+ for s in srlist:
+ srs = SpatialReference(s.wkt)
+
+ def test02_bad_wkt(self):
+ "Testing initialization on invalid WKT."
+ for bad in bad_srlist:
+ try:
+ srs = SpatialReference(bad)
+ srs.validate()
+ except (SRSException, OGRException):
+ pass
+ else:
+ self.fail('Should not have initialized on bad WKT "%s"!')
+
+ def test03_get_wkt(self):
+ "Testing getting the WKT."
+ for s in srlist:
+ srs = SpatialReference(s.wkt)
+ self.assertEqual(s.wkt, srs.wkt)
+
+ def test04_proj(self):
+ "Test PROJ.4 import and export."
+
+ for s in srlist:
+ if s.proj:
+ srs1 = SpatialReference(s.wkt)
+ srs2 = SpatialReference(s.proj)
+ self.assertEqual(srs1.proj, srs2.proj)
+
+ def test05_epsg(self):
+ "Test EPSG import."
+ for s in srlist:
+ if s.epsg:
+ srs1 = SpatialReference(s.wkt)
+ srs2 = SpatialReference(s.epsg)
+ srs3 = SpatialReference(str(s.epsg))
+ srs4 = SpatialReference('EPSG:%d' % s.epsg)
+ #self.assertEqual(srs1.wkt, srs2.wkt)
+ for srs in (srs1, srs2, srs3, srs4):
+ for attr, expected in s.attr:
+ self.assertEqual(expected, srs[attr])
+
+ def test07_boolean_props(self):
+ "Testing the boolean properties."
+ for s in srlist:
+ srs = SpatialReference(s.wkt)
+ self.assertEqual(s.projected, srs.projected)
+ self.assertEqual(s.geographic, srs.geographic)
+
+ def test08_angular_linear(self):
+ "Testing the linear and angular units routines."
+ for s in srlist:
+ srs = SpatialReference(s.wkt)
+ self.assertEqual(s.ang_name, srs.angular_name)
+ self.assertEqual(s.lin_name, srs.linear_name)
+ self.assertAlmostEqual(s.ang_units, srs.angular_units, 9)
+ self.assertAlmostEqual(s.lin_units, srs.linear_units, 9)
+
+ def test09_authority(self):
+ "Testing the authority name & code routines."
+ for s in srlist:
+ if hasattr(s, 'auth'):
+ srs = SpatialReference(s.wkt)
+ for target, tup in s.auth.items():
+ self.assertEqual(tup[0], srs.auth_name(target))
+ self.assertEqual(tup[1], srs.auth_code(target))
+
+ def test10_attributes(self):
+ "Testing the attribute retrieval routines."
+ for s in srlist:
+ srs = SpatialReference(s.wkt)
+ for tup in s.attr:
+ att = tup[0] # Attribute to test
+ exp = tup[1] # Expected result
+ self.assertEqual(exp, srs[att])
+
+ def test11_wellknown(self):
+ "Testing Well Known Names of Spatial References."
+ for s in well_known:
+ srs = SpatialReference(s.wk)
+ self.assertEqual(s.name, srs.name)
+ for tup in s.attrs:
+ if len(tup) == 2:
+ key = tup[0]
+ exp = tup[1]
+ elif len(tup) == 3:
+ key = tup[:2]
+ exp = tup[2]
+ self.assertEqual(srs[key], exp)
+
+ def test12_coordtransform(self):
+ "Testing initialization of a CoordTransform."
+ target = SpatialReference('WGS84')
+ for s in srlist:
+ if s.proj:
+ ct = CoordTransform(SpatialReference(s.wkt), target)
+
+ def test13_attr_value(self):
+ "Testing the attr_value() method."
+ s1 = SpatialReference('WGS84')
+ self.assertRaises(TypeError, s1.__getitem__, 0)
+ self.assertRaises(TypeError, s1.__getitem__, ('GEOGCS', 'foo'))
+ self.assertEqual('WGS 84', s1['GEOGCS'])
+ self.assertEqual('WGS_1984', s1['DATUM'])
+ self.assertEqual('EPSG', s1['AUTHORITY'])
+ self.assertEqual(4326, int(s1['AUTHORITY', 1]))
+ #for i in range(7): self.assertEqual(0, int(s1['TOWGS84', i]))
+ self.assertEqual(None, s1['FOOBAR'])
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(SpatialRefTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/geometry/__init__.py b/parts/django/django/contrib/gis/geometry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/geometry/__init__.py
diff --git a/parts/django/django/contrib/gis/geometry/backend/__init__.py b/parts/django/django/contrib/gis/geometry/backend/__init__.py
new file mode 100644
index 0000000..d79a556
--- /dev/null
+++ b/parts/django/django/contrib/gis/geometry/backend/__init__.py
@@ -0,0 +1,21 @@
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.utils.importlib import import_module
+
+geom_backend = getattr(settings, 'GEOMETRY_BACKEND', 'geos')
+
+try:
+ module = import_module('.%s' % geom_backend, 'django.contrib.gis.geometry.backend')
+except ImportError, e:
+ try:
+ module = import_module(geom_backend)
+ except ImportError, e_user:
+ raise ImproperlyConfigured('Could not import user-defined GEOMETRY_BACKEND '
+ '"%s".' % geom_backend)
+
+try:
+ Geometry = module.Geometry
+ GeometryException = module.GeometryException
+except AttributeError:
+ raise ImproperlyConfigured('Cannot import Geometry from the "%s" '
+ 'geometry backend.' % geom_backend)
diff --git a/parts/django/django/contrib/gis/geometry/backend/geos.py b/parts/django/django/contrib/gis/geometry/backend/geos.py
new file mode 100644
index 0000000..a1ac096
--- /dev/null
+++ b/parts/django/django/contrib/gis/geometry/backend/geos.py
@@ -0,0 +1,3 @@
+from django.contrib.gis.geos import \
+ GEOSGeometry as Geometry, \
+ GEOSException as GeometryException
diff --git a/parts/django/django/contrib/gis/geometry/regex.py b/parts/django/django/contrib/gis/geometry/regex.py
new file mode 100644
index 0000000..1b9e2f4
--- /dev/null
+++ b/parts/django/django/contrib/gis/geometry/regex.py
@@ -0,0 +1,12 @@
+import re
+
+# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
+# to prevent potentially malicious input from reaching the underlying C
+# library. Not a substitute for good Web security programming practices.
+hex_regex = re.compile(r'^[0-9A-F]+$', re.I)
+wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?'
+ r'(?P<wkt>'
+ r'(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)'
+ r'[ACEGIMLONPSRUTYZ\d,\.\-\(\) ]+)$',
+ re.I)
+json_regex = re.compile(r'^(\s+)?\{[\s\w,\[\]\{\}\-\."\':]+\}(\s+)?$')
diff --git a/parts/django/django/contrib/gis/geometry/test_data.py b/parts/django/django/contrib/gis/geometry/test_data.py
new file mode 100644
index 0000000..4e07348
--- /dev/null
+++ b/parts/django/django/contrib/gis/geometry/test_data.py
@@ -0,0 +1,105 @@
+"""
+This module has the mock object definitions used to hold reference geometry
+for the GEOS and GDAL tests.
+"""
+import gzip
+import os
+
+from django.contrib import gis
+from django.utils import simplejson
+
+
+# This global used to store reference geometry data.
+GEOMETRIES = None
+
+# Path where reference test data is located.
+TEST_DATA = os.path.join(os.path.dirname(gis.__file__), 'tests', 'data')
+
+
+def tuplize(seq):
+ "Turn all nested sequences to tuples in given sequence."
+ if isinstance(seq, (list, tuple)):
+ return tuple([tuplize(i) for i in seq])
+ return seq
+
+
+def strconvert(d):
+ "Converts all keys in dictionary to str type."
+ return dict([(str(k), v) for k, v in d.iteritems()])
+
+
+def get_ds_file(name, ext):
+ return os.path.join(TEST_DATA,
+ name,
+ name + '.%s' % ext
+ )
+
+
+class TestObj(object):
+ """
+ Base testing object, turns keyword args into attributes.
+ """
+ def __init__(self, **kwargs):
+ for key, value in kwargs.items():
+ setattr(self, key, value)
+
+
+class TestDS(TestObj):
+ """
+ Object for testing GDAL data sources.
+ """
+ def __init__(self, name, **kwargs):
+ # Shapefile is default extension, unless specified otherwise.
+ ext = kwargs.pop('ext', 'shp')
+ self.ds = get_ds_file(name, ext)
+ super(TestDS, self).__init__(**kwargs)
+
+
+class TestGeom(TestObj):
+ """
+ Testing object used for wrapping reference geometry data
+ in GEOS/GDAL tests.
+ """
+ def __init__(self, **kwargs):
+ # Converting lists to tuples of certain keyword args
+ # so coordinate test cases will match (JSON has no
+ # concept of tuple).
+ coords = kwargs.pop('coords', None)
+ if coords:
+ self.coords = tuplize(coords)
+
+ centroid = kwargs.pop('centroid', None)
+ if centroid:
+ self.centroid = tuple(centroid)
+
+ ext_ring_cs = kwargs.pop('ext_ring_cs', None)
+ if ext_ring_cs:
+ ext_ring_cs = tuplize(ext_ring_cs)
+ self.ext_ring_cs = ext_ring_cs
+
+ super(TestGeom, self).__init__(**kwargs)
+
+
+class TestGeomSet(object):
+ """
+ Each attribute of this object is a list of `TestGeom` instances.
+ """
+ def __init__(self, **kwargs):
+ for key, value in kwargs.items():
+ setattr(self, key, [TestGeom(**strconvert(kw)) for kw in value])
+
+
+class TestDataMixin(object):
+ """
+ Mixin used for GEOS/GDAL test cases that defines a `geometries`
+ property, which returns and/or loads the reference geometry data.
+ """
+ @property
+ def geometries(self):
+ global GEOMETRIES
+ if GEOMETRIES is None:
+ # Load up the test geometry data from fixture into global.
+ gzf = gzip.GzipFile(os.path.join(TEST_DATA, 'geometries.json.gz'))
+ geometries = simplejson.loads(gzf.read())
+ GEOMETRIES = TestGeomSet(**strconvert(geometries))
+ return GEOMETRIES
diff --git a/parts/django/django/contrib/gis/geos/LICENSE b/parts/django/django/contrib/gis/geos/LICENSE
new file mode 100644
index 0000000..0479b07
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2007-2009 Justin Bronn
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of GEOSGeometry nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/parts/django/django/contrib/gis/geos/__init__.py b/parts/django/django/contrib/gis/geos/__init__.py
new file mode 100644
index 0000000..5885a30
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/__init__.py
@@ -0,0 +1,14 @@
+"""
+The GeoDjango GEOS module. Please consult the GeoDjango documentation
+for more details:
+ http://geodjango.org/docs/geos.html
+"""
+from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex
+from django.contrib.gis.geos.point import Point
+from django.contrib.gis.geos.linestring import LineString, LinearRing
+from django.contrib.gis.geos.polygon import Polygon
+from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
+from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
+from django.contrib.gis.geos.io import WKTReader, WKTWriter, WKBReader, WKBWriter
+from django.contrib.gis.geos.factory import fromfile, fromstr
+from django.contrib.gis.geos.libgeos import geos_version, geos_version_info, GEOS_PREPARE
diff --git a/parts/django/django/contrib/gis/geos/base.py b/parts/django/django/contrib/gis/geos/base.py
new file mode 100644
index 0000000..34c03c8
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/base.py
@@ -0,0 +1,52 @@
+from ctypes import c_void_p
+from types import NoneType
+from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
+
+# Trying to import GDAL libraries, if available. Have to place in
+# try/except since this package may be used outside GeoDjango.
+try:
+ from django.contrib.gis import gdal
+except ImportError:
+ # A 'dummy' gdal module.
+ class GDALInfo(object):
+ HAS_GDAL = False
+ GEOJSON = False
+ gdal = GDALInfo()
+
+# NumPy supported?
+try:
+ import numpy
+except ImportError:
+ numpy = False
+
+class GEOSBase(object):
+ """
+ Base object for GEOS objects that has a pointer access property
+ that controls access to the underlying C pointer.
+ """
+ # Initially the pointer is NULL.
+ _ptr = None
+
+ # Default allowed pointer type.
+ ptr_type = c_void_p
+
+ # Pointer access property.
+ def _get_ptr(self):
+ # Raise an exception if the pointer isn't valid don't
+ # want to be passing NULL pointers to routines --
+ # that's very bad.
+ if self._ptr: return self._ptr
+ else: raise GEOSException('NULL GEOS %s pointer encountered.' % self.__class__.__name__)
+
+ def _set_ptr(self, ptr):
+ # Only allow the pointer to be set with pointers of the
+ # compatible type or None (NULL).
+ if isinstance(ptr, (self.ptr_type, NoneType)):
+ self._ptr = ptr
+ else:
+ raise TypeError('Incompatible pointer type')
+
+ # Property for controlling access to the GEOS object pointers. Using
+ # this raises an exception when the pointer is NULL, thus preventing
+ # the C library from attempting to access an invalid memory location.
+ ptr = property(_get_ptr, _set_ptr)
diff --git a/parts/django/django/contrib/gis/geos/collections.py b/parts/django/django/contrib/gis/geos/collections.py
new file mode 100644
index 0000000..515f80e
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/collections.py
@@ -0,0 +1,123 @@
+"""
+ This module houses the Geometry Collection objects:
+ GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon
+"""
+from ctypes import c_int, c_uint, byref
+from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
+from django.contrib.gis.geos.geometry import GEOSGeometry
+from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.linestring import LineString, LinearRing
+from django.contrib.gis.geos.point import Point
+from django.contrib.gis.geos.polygon import Polygon
+from django.contrib.gis.geos import prototypes as capi
+
+class GeometryCollection(GEOSGeometry):
+ _typeid = 7
+
+ def __init__(self, *args, **kwargs):
+ "Initializes a Geometry Collection from a sequence of Geometry objects."
+
+ # Checking the arguments
+ if not args:
+ raise TypeError('Must provide at least one Geometry to initialize %s.' % self.__class__.__name__)
+
+ if len(args) == 1:
+ # If only one geometry provided or a list of geometries is provided
+ # in the first argument.
+ if isinstance(args[0], (tuple, list)):
+ init_geoms = args[0]
+ else:
+ init_geoms = args
+ else:
+ init_geoms = args
+
+ # Ensuring that only the permitted geometries are allowed in this collection
+ # this is moved to list mixin super class
+ self._check_allowed(init_geoms)
+
+ # Creating the geometry pointer array.
+ collection = self._create_collection(len(init_geoms), iter(init_geoms))
+ super(GeometryCollection, self).__init__(collection, **kwargs)
+
+ def __iter__(self):
+ "Iterates over each Geometry in the Collection."
+ for i in xrange(len(self)):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the number of geometries in this Collection."
+ return self.num_geom
+
+ ### Methods for compatibility with ListMixin ###
+ def _create_collection(self, length, items):
+ # Creating the geometry pointer array.
+ geoms = get_pointer_arr(length)
+ for i, g in enumerate(items):
+ # this is a little sloppy, but makes life easier
+ # allow GEOSGeometry types (python wrappers) or pointer types
+ geoms[i] = capi.geom_clone(getattr(g, 'ptr', g))
+
+ return capi.create_collection(c_int(self._typeid), byref(geoms), c_uint(length))
+
+ def _get_single_internal(self, index):
+ return capi.get_geomn(self.ptr, index)
+
+ def _get_single_external(self, index):
+ "Returns the Geometry from this Collection at the given index (0-based)."
+ # Checking the index and returning the corresponding GEOS geometry.
+ return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid)
+
+ def _set_list(self, length, items):
+ "Create a new collection, and destroy the contents of the previous pointer."
+ prev_ptr = self.ptr
+ srid = self.srid
+ self.ptr = self._create_collection(length, items)
+ if srid: self.srid = srid
+ capi.destroy_geom(prev_ptr)
+
+ _set_single = GEOSGeometry._set_single_rebuild
+ _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
+
+ @property
+ def kml(self):
+ "Returns the KML for this Geometry Collection."
+ return '<MultiGeometry>%s</MultiGeometry>' % ''.join([g.kml for g in self])
+
+ @property
+ def tuple(self):
+ "Returns a tuple of all the coordinates in this Geometry Collection"
+ return tuple([g.tuple for g in self])
+ coords = tuple
+
+# MultiPoint, MultiLineString, and MultiPolygon class definitions.
+class MultiPoint(GeometryCollection):
+ _allowed = Point
+ _typeid = 4
+
+class MultiLineString(GeometryCollection):
+ _allowed = (LineString, LinearRing)
+ _typeid = 5
+
+ @property
+ def merged(self):
+ """
+ Returns a LineString representing the line merge of this
+ MultiLineString.
+ """
+ return self._topology(capi.geos_linemerge(self.ptr))
+
+class MultiPolygon(GeometryCollection):
+ _allowed = Polygon
+ _typeid = 6
+
+ @property
+ def cascaded_union(self):
+ "Returns a cascaded union of this MultiPolygon."
+ if GEOS_PREPARE:
+ return GEOSGeometry(capi.geos_cascaded_union(self.ptr), self.srid)
+ else:
+ raise GEOSException('The cascaded union operation requires GEOS 3.1+.')
+
+# Setting the allowed types here since GeometryCollection is defined before
+# its subclasses.
+GeometryCollection._allowed = (Point, LineString, LinearRing, Polygon, MultiPoint, MultiLineString, MultiPolygon)
diff --git a/parts/django/django/contrib/gis/geos/coordseq.py b/parts/django/django/contrib/gis/geos/coordseq.py
new file mode 100644
index 0000000..027d34e
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/coordseq.py
@@ -0,0 +1,156 @@
+"""
+ This module houses the GEOSCoordSeq object, which is used internally
+ by GEOSGeometry to house the actual coordinates of the Point,
+ LineString, and LinearRing geometries.
+"""
+from ctypes import c_double, c_uint, byref
+from django.contrib.gis.geos.base import GEOSBase, numpy
+from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
+from django.contrib.gis.geos.libgeos import CS_PTR
+from django.contrib.gis.geos import prototypes as capi
+
+class GEOSCoordSeq(GEOSBase):
+ "The internal representation of a list of coordinates inside a Geometry."
+
+ ptr_type = CS_PTR
+
+ #### Python 'magic' routines ####
+ def __init__(self, ptr, z=False):
+ "Initializes from a GEOS pointer."
+ if not isinstance(ptr, CS_PTR):
+ raise TypeError('Coordinate sequence should initialize with a CS_PTR.')
+ self._ptr = ptr
+ self._z = z
+
+ def __iter__(self):
+ "Iterates over each point in the coordinate sequence."
+ for i in xrange(self.size):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the number of points in the coordinate sequence."
+ return int(self.size)
+
+ def __str__(self):
+ "Returns the string representation of the coordinate sequence."
+ return str(self.tuple)
+
+ def __getitem__(self, index):
+ "Returns the coordinate sequence value at the given index."
+ coords = [self.getX(index), self.getY(index)]
+ if self.dims == 3 and self._z:
+ coords.append(self.getZ(index))
+ return tuple(coords)
+
+ def __setitem__(self, index, value):
+ "Sets the coordinate sequence value at the given index."
+ # Checking the input value
+ if isinstance(value, (list, tuple)):
+ pass
+ elif numpy and isinstance(value, numpy.ndarray):
+ pass
+ else:
+ raise TypeError('Must set coordinate with a sequence (list, tuple, or numpy array).')
+ # Checking the dims of the input
+ if self.dims == 3 and self._z:
+ n_args = 3
+ set_3d = True
+ else:
+ n_args = 2
+ set_3d = False
+ if len(value) != n_args:
+ raise TypeError('Dimension of value does not match.')
+ # Setting the X, Y, Z
+ self.setX(index, value[0])
+ self.setY(index, value[1])
+ if set_3d: self.setZ(index, value[2])
+
+ #### Internal Routines ####
+ def _checkindex(self, index):
+ "Checks the given index."
+ sz = self.size
+ if (sz < 1) or (index < 0) or (index >= sz):
+ raise GEOSIndexError('invalid GEOS Geometry index: %s' % str(index))
+
+ def _checkdim(self, dim):
+ "Checks the given dimension."
+ if dim < 0 or dim > 2:
+ raise GEOSException('invalid ordinate dimension "%d"' % dim)
+
+ #### Ordinate getting and setting routines ####
+ def getOrdinate(self, dimension, index):
+ "Returns the value for the given dimension and index."
+ self._checkindex(index)
+ self._checkdim(dimension)
+ return capi.cs_getordinate(self.ptr, index, dimension, byref(c_double()))
+
+ def setOrdinate(self, dimension, index, value):
+ "Sets the value for the given dimension and index."
+ self._checkindex(index)
+ self._checkdim(dimension)
+ capi.cs_setordinate(self.ptr, index, dimension, value)
+
+ def getX(self, index):
+ "Get the X value at the index."
+ return self.getOrdinate(0, index)
+
+ def setX(self, index, value):
+ "Set X with the value at the given index."
+ self.setOrdinate(0, index, value)
+
+ def getY(self, index):
+ "Get the Y value at the given index."
+ return self.getOrdinate(1, index)
+
+ def setY(self, index, value):
+ "Set Y with the value at the given index."
+ self.setOrdinate(1, index, value)
+
+ def getZ(self, index):
+ "Get Z with the value at the given index."
+ return self.getOrdinate(2, index)
+
+ def setZ(self, index, value):
+ "Set Z with the value at the given index."
+ self.setOrdinate(2, index, value)
+
+ ### Dimensions ###
+ @property
+ def size(self):
+ "Returns the size of this coordinate sequence."
+ return capi.cs_getsize(self.ptr, byref(c_uint()))
+
+ @property
+ def dims(self):
+ "Returns the dimensions of this coordinate sequence."
+ return capi.cs_getdims(self.ptr, byref(c_uint()))
+
+ @property
+ def hasz(self):
+ """
+ Returns whether this coordinate sequence is 3D. This property value is
+ inherited from the parent Geometry.
+ """
+ return self._z
+
+ ### Other Methods ###
+ def clone(self):
+ "Clones this coordinate sequence."
+ return GEOSCoordSeq(capi.cs_clone(self.ptr), self.hasz)
+
+ @property
+ def kml(self):
+ "Returns the KML representation for the coordinates."
+ # Getting the substitution string depending on whether the coordinates have
+ # a Z dimension.
+ if self.hasz: substr = '%s,%s,%s '
+ else: substr = '%s,%s,0 '
+ return '<coordinates>%s</coordinates>' % \
+ ''.join([substr % self[i] for i in xrange(len(self))]).strip()
+
+ @property
+ def tuple(self):
+ "Returns a tuple version of this coordinate sequence."
+ n = self.size
+ if n == 1: return self[0]
+ else: return tuple([self[i] for i in xrange(n)])
diff --git a/parts/django/django/contrib/gis/geos/error.py b/parts/django/django/contrib/gis/geos/error.py
new file mode 100644
index 0000000..46bdfe6
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/error.py
@@ -0,0 +1,20 @@
+"""
+ This module houses the GEOS exceptions, specifically, GEOSException and
+ GEOSGeometryIndexError.
+"""
+
+class GEOSException(Exception):
+ "The base GEOS exception, indicates a GEOS-related error."
+ pass
+
+class GEOSIndexError(GEOSException, KeyError):
+ """
+ This exception is raised when an invalid index is encountered, and has
+ the 'silent_variable_feature' attribute set to true. This ensures that
+ django's templates proceed to use the next lookup type gracefully when
+ an Exception is raised. Fixes ticket #4740.
+ """
+ # "If, during the method lookup, a method raises an exception, the exception
+ # will be propagated, unless the exception has an attribute
+ # `silent_variable_failure` whose value is True." -- Django template docs.
+ silent_variable_failure = True
diff --git a/parts/django/django/contrib/gis/geos/factory.py b/parts/django/django/contrib/gis/geos/factory.py
new file mode 100644
index 0000000..df29976
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/factory.py
@@ -0,0 +1,23 @@
+from django.contrib.gis.geos.geometry import GEOSGeometry, wkt_regex, hex_regex
+
+def fromfile(file_h):
+ """
+ Given a string file name, returns a GEOSGeometry. The file may contain WKB,
+ WKT, or HEX.
+ """
+ # If given a file name, get a real handle.
+ if isinstance(file_h, basestring):
+ file_h = open(file_h, 'rb')
+
+ # Reading in the file's contents,
+ buf = file_h.read()
+
+ # If we get WKB need to wrap in buffer(), so run through regexes.
+ if wkt_regex.match(buf) or hex_regex.match(buf):
+ return GEOSGeometry(buf)
+ else:
+ return GEOSGeometry(buffer(buf))
+
+def fromstr(string, **kwargs):
+ "Given a string value, returns a GEOSGeometry object."
+ return GEOSGeometry(string, **kwargs)
diff --git a/parts/django/django/contrib/gis/geos/geometry.py b/parts/django/django/contrib/gis/geos/geometry.py
new file mode 100644
index 0000000..51666bc
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/geometry.py
@@ -0,0 +1,661 @@
+"""
+ This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
+ inherit from this object.
+"""
+# Python, ctypes and types dependencies.
+import re
+from ctypes import addressof, byref, c_double, c_size_t
+
+# super-class for mutable list behavior
+from django.contrib.gis.geos.mutable_list import ListMixin
+
+# GEOS-related dependencies.
+from django.contrib.gis.geos.base import GEOSBase, gdal
+from django.contrib.gis.geos.coordseq import GEOSCoordSeq
+from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
+from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.mutable_list import ListMixin
+
+# All other functions in this module come from the ctypes
+# prototypes module -- which handles all interaction with
+# the underlying GEOS library.
+from django.contrib.gis.geos import prototypes as capi
+
+# These functions provide access to a thread-local instance
+# of their corresponding GEOS I/O class.
+from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
+
+# For recognizing geometry input.
+from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex
+
+class GEOSGeometry(GEOSBase, ListMixin):
+ "A class that, generally, encapsulates a GEOS geometry."
+
+ # Raise GEOSIndexError instead of plain IndexError
+ # (see ticket #4740 and GEOSIndexError docstring)
+ _IndexError = GEOSIndexError
+
+ ptr_type = GEOM_PTR
+
+ #### Python 'magic' routines ####
+ def __init__(self, geo_input, srid=None):
+ """
+ The base constructor for GEOS geometry objects, and may take the
+ following inputs:
+
+ * strings:
+ - WKT
+ - HEXEWKB (a PostGIS-specific canonical form)
+ - GeoJSON (requires GDAL)
+ * buffer:
+ - WKB
+
+ The `srid` keyword is used to specify the Source Reference Identifier
+ (SRID) number for this Geometry. If not set, the SRID will be None.
+ """
+ if isinstance(geo_input, basestring):
+ if isinstance(geo_input, unicode):
+ # Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
+ geo_input = geo_input.encode('ascii')
+
+ wkt_m = wkt_regex.match(geo_input)
+ if wkt_m:
+ # Handling WKT input.
+ if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
+ g = wkt_r().read(wkt_m.group('wkt'))
+ elif hex_regex.match(geo_input):
+ # Handling HEXEWKB input.
+ g = wkb_r().read(geo_input)
+ elif gdal.GEOJSON and json_regex.match(geo_input):
+ # Handling GeoJSON input.
+ g = wkb_r().read(gdal.OGRGeometry(geo_input).wkb)
+ else:
+ raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
+ elif isinstance(geo_input, GEOM_PTR):
+ # When the input is a pointer to a geomtry (GEOM_PTR).
+ g = geo_input
+ elif isinstance(geo_input, buffer):
+ # When the input is a buffer (WKB).
+ g = wkb_r().read(geo_input)
+ elif isinstance(geo_input, GEOSGeometry):
+ g = capi.geom_clone(geo_input.ptr)
+ else:
+ # Invalid geometry type.
+ raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))
+
+ if bool(g):
+ # Setting the pointer object with a valid pointer.
+ self.ptr = g
+ else:
+ raise GEOSException('Could not initialize GEOS Geometry with given input.')
+
+ # Post-initialization setup.
+ self._post_init(srid)
+
+ def _post_init(self, srid):
+ "Helper routine for performing post-initialization setup."
+ # Setting the SRID, if given.
+ if srid and isinstance(srid, int): self.srid = srid
+
+ # Setting the class type (e.g., Point, Polygon, etc.)
+ self.__class__ = GEOS_CLASSES[self.geom_typeid]
+
+ # Setting the coordinate sequence for the geometry (will be None on
+ # geometries that do not have coordinate sequences)
+ self._set_cs()
+
+ def __del__(self):
+ """
+ Destroys this Geometry; in other words, frees the memory used by the
+ GEOS C++ object.
+ """
+ if self._ptr: capi.destroy_geom(self._ptr)
+
+ def __copy__(self):
+ """
+ Returns a clone because the copy of a GEOSGeometry may contain an
+ invalid pointer location if the original is garbage collected.
+ """
+ return self.clone()
+
+ def __deepcopy__(self, memodict):
+ """
+ The `deepcopy` routine is used by the `Node` class of django.utils.tree;
+ thus, the protocol routine needs to be implemented to return correct
+ copies (clones) of these GEOS objects, which use C pointers.
+ """
+ return self.clone()
+
+ def __str__(self):
+ "WKT is used for the string representation."
+ return self.wkt
+
+ def __repr__(self):
+ "Short-hand representation because WKT may be very large."
+ return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
+
+ # Pickling support
+ def __getstate__(self):
+ # The pickled state is simply a tuple of the WKB (in string form)
+ # and the SRID.
+ return str(self.wkb), self.srid
+
+ def __setstate__(self, state):
+ # Instantiating from the tuple state that was pickled.
+ wkb, srid = state
+ ptr = wkb_r().read(buffer(wkb))
+ if not ptr: raise GEOSException('Invalid Geometry loaded from pickled state.')
+ self.ptr = ptr
+ self._post_init(srid)
+
+ # Comparison operators
+ def __eq__(self, other):
+ """
+ Equivalence testing, a Geometry may be compared with another Geometry
+ or a WKT representation.
+ """
+ if isinstance(other, basestring):
+ return self.wkt == other
+ elif isinstance(other, GEOSGeometry):
+ return self.equals_exact(other)
+ else:
+ return False
+
+ def __ne__(self, other):
+ "The not equals operator."
+ return not (self == other)
+
+ ### Geometry set-like operations ###
+ # Thanks to Sean Gillies for inspiration:
+ # http://lists.gispython.org/pipermail/community/2007-July/001034.html
+ # g = g1 | g2
+ def __or__(self, other):
+ "Returns the union of this Geometry and the other."
+ return self.union(other)
+
+ # g = g1 & g2
+ def __and__(self, other):
+ "Returns the intersection of this Geometry and the other."
+ return self.intersection(other)
+
+ # g = g1 - g2
+ def __sub__(self, other):
+ "Return the difference this Geometry and the other."
+ return self.difference(other)
+
+ # g = g1 ^ g2
+ def __xor__(self, other):
+ "Return the symmetric difference of this Geometry and the other."
+ return self.sym_difference(other)
+
+ #### Coordinate Sequence Routines ####
+ @property
+ def has_cs(self):
+ "Returns True if this Geometry has a coordinate sequence, False if not."
+ # Only these geometries are allowed to have coordinate sequences.
+ if isinstance(self, (Point, LineString, LinearRing)):
+ return True
+ else:
+ return False
+
+ def _set_cs(self):
+ "Sets the coordinate sequence for this Geometry."
+ if self.has_cs:
+ self._cs = GEOSCoordSeq(capi.get_cs(self.ptr), self.hasz)
+ else:
+ self._cs = None
+
+ @property
+ def coord_seq(self):
+ "Returns a clone of the coordinate sequence for this Geometry."
+ if self.has_cs:
+ return self._cs.clone()
+
+ #### Geometry Info ####
+ @property
+ def geom_type(self):
+ "Returns a string representing the Geometry type, e.g. 'Polygon'"
+ return capi.geos_type(self.ptr)
+
+ @property
+ def geom_typeid(self):
+ "Returns an integer representing the Geometry type."
+ return capi.geos_typeid(self.ptr)
+
+ @property
+ def num_geom(self):
+ "Returns the number of geometries in the Geometry."
+ return capi.get_num_geoms(self.ptr)
+
+ @property
+ def num_coords(self):
+ "Returns the number of coordinates in the Geometry."
+ return capi.get_num_coords(self.ptr)
+
+ @property
+ def num_points(self):
+ "Returns the number points, or coordinates, in the Geometry."
+ return self.num_coords
+
+ @property
+ def dims(self):
+ "Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
+ return capi.get_dims(self.ptr)
+
+ def normalize(self):
+ "Converts this Geometry to normal form (or canonical form)."
+ return capi.geos_normalize(self.ptr)
+
+ #### Unary predicates ####
+ @property
+ def empty(self):
+ """
+ Returns a boolean indicating whether the set of points in this Geometry
+ are empty.
+ """
+ return capi.geos_isempty(self.ptr)
+
+ @property
+ def hasz(self):
+ "Returns whether the geometry has a 3D dimension."
+ return capi.geos_hasz(self.ptr)
+
+ @property
+ def ring(self):
+ "Returns whether or not the geometry is a ring."
+ return capi.geos_isring(self.ptr)
+
+ @property
+ def simple(self):
+ "Returns false if the Geometry not simple."
+ return capi.geos_issimple(self.ptr)
+
+ @property
+ def valid(self):
+ "This property tests the validity of this Geometry."
+ return capi.geos_isvalid(self.ptr)
+
+ #### Binary predicates. ####
+ def contains(self, other):
+ "Returns true if other.within(this) returns true."
+ return capi.geos_contains(self.ptr, other.ptr)
+
+ def crosses(self, other):
+ """
+ Returns true if the DE-9IM intersection matrix for the two Geometries
+ is T*T****** (for a point and a curve,a point and an area or a line and
+ an area) 0******** (for two curves).
+ """
+ return capi.geos_crosses(self.ptr, other.ptr)
+
+ def disjoint(self, other):
+ """
+ Returns true if the DE-9IM intersection matrix for the two Geometries
+ is FF*FF****.
+ """
+ return capi.geos_disjoint(self.ptr, other.ptr)
+
+ def equals(self, other):
+ """
+ Returns true if the DE-9IM intersection matrix for the two Geometries
+ is T*F**FFF*.
+ """
+ return capi.geos_equals(self.ptr, other.ptr)
+
+ def equals_exact(self, other, tolerance=0):
+ """
+ Returns true if the two Geometries are exactly equal, up to a
+ specified tolerance.
+ """
+ return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))
+
+ def intersects(self, other):
+ "Returns true if disjoint returns false."
+ return capi.geos_intersects(self.ptr, other.ptr)
+
+ def overlaps(self, other):
+ """
+ Returns true if the DE-9IM intersection matrix for the two Geometries
+ is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
+ """
+ return capi.geos_overlaps(self.ptr, other.ptr)
+
+ def relate_pattern(self, other, pattern):
+ """
+ Returns true if the elements in the DE-9IM intersection matrix for the
+ two Geometries match the elements in pattern.
+ """
+ if not isinstance(pattern, basestring) or len(pattern) > 9:
+ raise GEOSException('invalid intersection matrix pattern')
+ return capi.geos_relatepattern(self.ptr, other.ptr, pattern)
+
+ def touches(self, other):
+ """
+ Returns true if the DE-9IM intersection matrix for the two Geometries
+ is FT*******, F**T***** or F***T****.
+ """
+ return capi.geos_touches(self.ptr, other.ptr)
+
+ def within(self, other):
+ """
+ Returns true if the DE-9IM intersection matrix for the two Geometries
+ is T*F**F***.
+ """
+ return capi.geos_within(self.ptr, other.ptr)
+
+ #### SRID Routines ####
+ def get_srid(self):
+ "Gets the SRID for the geometry, returns None if no SRID is set."
+ s = capi.geos_get_srid(self.ptr)
+ if s == 0: return None
+ else: return s
+
+ def set_srid(self, srid):
+ "Sets the SRID for the geometry."
+ capi.geos_set_srid(self.ptr, srid)
+ srid = property(get_srid, set_srid)
+
+ #### Output Routines ####
+ @property
+ def ewkt(self):
+ """
+ Returns the EWKT (WKT + SRID) of the Geometry. Note that Z values
+ are *not* included in this representation because GEOS does not yet
+ support serializing them.
+ """
+ if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
+ else: return self.wkt
+
+ @property
+ def wkt(self):
+ "Returns the WKT (Well-Known Text) representation of this Geometry."
+ return wkt_w().write(self)
+
+ @property
+ def hex(self):
+ """
+ Returns the WKB of this Geometry in hexadecimal form. Please note
+ that the SRID and Z values are not included in this representation
+ because it is not a part of the OGC specification (use the `hexewkb`
+ property instead).
+ """
+ # A possible faster, all-python, implementation:
+ # str(self.wkb).encode('hex')
+ return wkb_w().write_hex(self)
+
+ @property
+ def hexewkb(self):
+ """
+ Returns the EWKB of this Geometry in hexadecimal form. This is an
+ extension of the WKB specification that includes SRID and Z values
+ that are a part of this geometry.
+ """
+ if self.hasz:
+ if not GEOS_PREPARE:
+ # See: http://trac.osgeo.org/geos/ticket/216
+ raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')
+ return ewkb_w3d().write_hex(self)
+ else:
+ return ewkb_w().write_hex(self)
+
+ @property
+ def json(self):
+ """
+ Returns GeoJSON representation of this Geometry if GDAL 1.5+
+ is installed.
+ """
+ if gdal.GEOJSON:
+ return self.ogr.json
+ else:
+ raise GEOSException('GeoJSON output only supported on GDAL 1.5+.')
+ geojson = json
+
+ @property
+ def wkb(self):
+ """
+ Returns the WKB (Well-Known Binary) representation of this Geometry
+ as a Python buffer. SRID and Z values are not included, use the
+ `ewkb` property instead.
+ """
+ return wkb_w().write(self)
+
+ @property
+ def ewkb(self):
+ """
+ Return the EWKB representation of this Geometry as a Python buffer.
+ This is an extension of the WKB specification that includes any SRID
+ and Z values that are a part of this geometry.
+ """
+ if self.hasz:
+ if not GEOS_PREPARE:
+ # See: http://trac.osgeo.org/geos/ticket/216
+ raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.')
+ return ewkb_w3d().write(self)
+ else:
+ return ewkb_w().write(self)
+
+ @property
+ def kml(self):
+ "Returns the KML representation of this Geometry."
+ gtype = self.geom_type
+ return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)
+
+ @property
+ def prepared(self):
+ """
+ Returns a PreparedGeometry corresponding to this geometry -- it is
+ optimized for the contains, intersects, and covers operations.
+ """
+ if GEOS_PREPARE:
+ return PreparedGeometry(self)
+ else:
+ raise GEOSException('GEOS 3.1+ required for prepared geometry support.')
+
+ #### GDAL-specific output routines ####
+ @property
+ def ogr(self):
+ "Returns the OGR Geometry for this Geometry."
+ if gdal.HAS_GDAL:
+ if self.srid:
+ return gdal.OGRGeometry(self.wkb, self.srid)
+ else:
+ return gdal.OGRGeometry(self.wkb)
+ else:
+ raise GEOSException('GDAL required to convert to an OGRGeometry.')
+
+ @property
+ def srs(self):
+ "Returns the OSR SpatialReference for SRID of this Geometry."
+ if gdal.HAS_GDAL:
+ if self.srid:
+ return gdal.SpatialReference(self.srid)
+ else:
+ return None
+ else:
+ raise GEOSException('GDAL required to return a SpatialReference object.')
+
+ @property
+ def crs(self):
+ "Alias for `srs` property."
+ return self.srs
+
+ def transform(self, ct, clone=False):
+ """
+ Requires GDAL. Transforms the geometry according to the given
+ transformation object, which may be an integer SRID, and WKT or
+ PROJ.4 string. By default, the geometry is transformed in-place and
+ nothing is returned. However if the `clone` keyword is set, then this
+ geometry will not be modified and a transformed clone will be returned
+ instead.
+ """
+ srid = self.srid
+ if gdal.HAS_GDAL and srid:
+ # Creating an OGR Geometry, which is then transformed.
+ g = gdal.OGRGeometry(self.wkb, srid)
+ g.transform(ct)
+ # Getting a new GEOS pointer
+ ptr = wkb_r().read(g.wkb)
+ if clone:
+ # User wants a cloned transformed geometry returned.
+ return GEOSGeometry(ptr, srid=g.srid)
+ if ptr:
+ # Reassigning pointer, and performing post-initialization setup
+ # again due to the reassignment.
+ capi.destroy_geom(self.ptr)
+ self.ptr = ptr
+ self._post_init(g.srid)
+ else:
+ raise GEOSException('Transformed WKB was invalid.')
+
+ #### Topology Routines ####
+ def _topology(self, gptr):
+ "Helper routine to return Geometry from the given pointer."
+ return GEOSGeometry(gptr, srid=self.srid)
+
+ @property
+ def boundary(self):
+ "Returns the boundary as a newly allocated Geometry object."
+ return self._topology(capi.geos_boundary(self.ptr))
+
+ def buffer(self, width, quadsegs=8):
+ """
+ Returns a geometry that represents all points whose distance from this
+ Geometry is less than or equal to distance. Calculations are in the
+ Spatial Reference System of this Geometry. The optional third parameter sets
+ the number of segment used to approximate a quarter circle (defaults to 8).
+ (Text from PostGIS documentation at ch. 6.1.3)
+ """
+ return self._topology(capi.geos_buffer(self.ptr, width, quadsegs))
+
+ @property
+ def centroid(self):
+ """
+ The centroid is equal to the centroid of the set of component Geometries
+ of highest dimension (since the lower-dimension geometries contribute zero
+ "weight" to the centroid).
+ """
+ return self._topology(capi.geos_centroid(self.ptr))
+
+ @property
+ def convex_hull(self):
+ """
+ Returns the smallest convex Polygon that contains all the points
+ in the Geometry.
+ """
+ return self._topology(capi.geos_convexhull(self.ptr))
+
+ def difference(self, other):
+ """
+ Returns a Geometry representing the points making up this Geometry
+ that do not make up other.
+ """
+ return self._topology(capi.geos_difference(self.ptr, other.ptr))
+
+ @property
+ def envelope(self):
+ "Return the envelope for this geometry (a polygon)."
+ return self._topology(capi.geos_envelope(self.ptr))
+
+ def intersection(self, other):
+ "Returns a Geometry representing the points shared by this Geometry and other."
+ return self._topology(capi.geos_intersection(self.ptr, other.ptr))
+
+ @property
+ def point_on_surface(self):
+ "Computes an interior point of this Geometry."
+ return self._topology(capi.geos_pointonsurface(self.ptr))
+
+ def relate(self, other):
+ "Returns the DE-9IM intersection matrix for this Geometry and the other."
+ return capi.geos_relate(self.ptr, other.ptr)
+
+ def simplify(self, tolerance=0.0, preserve_topology=False):
+ """
+ Returns the Geometry, simplified using the Douglas-Peucker algorithm
+ to the specified tolerance (higher tolerance => less points). If no
+ tolerance provided, defaults to 0.
+
+ By default, this function does not preserve topology - e.g. polygons can
+ be split, collapse to lines or disappear holes can be created or
+ disappear, and lines can cross. By specifying preserve_topology=True,
+ the result will have the same dimension and number of components as the
+ input. This is significantly slower.
+ """
+ if preserve_topology:
+ return self._topology(capi.geos_preservesimplify(self.ptr, tolerance))
+ else:
+ return self._topology(capi.geos_simplify(self.ptr, tolerance))
+
+ def sym_difference(self, other):
+ """
+ Returns a set combining the points in this Geometry not in other,
+ and the points in other not in this Geometry.
+ """
+ return self._topology(capi.geos_symdifference(self.ptr, other.ptr))
+
+ def union(self, other):
+ "Returns a Geometry representing all the points in this Geometry and other."
+ return self._topology(capi.geos_union(self.ptr, other.ptr))
+
+ #### Other Routines ####
+ @property
+ def area(self):
+ "Returns the area of the Geometry."
+ return capi.geos_area(self.ptr, byref(c_double()))
+
+ def distance(self, other):
+ """
+ Returns the distance between the closest points on this Geometry
+ and the other. Units will be in those of the coordinate system of
+ the Geometry.
+ """
+ if not isinstance(other, GEOSGeometry):
+ raise TypeError('distance() works only on other GEOS Geometries.')
+ return capi.geos_distance(self.ptr, other.ptr, byref(c_double()))
+
+ @property
+ def extent(self):
+ """
+ Returns the extent of this geometry as a 4-tuple, consisting of
+ (xmin, ymin, xmax, ymax).
+ """
+ env = self.envelope
+ if isinstance(env, Point):
+ xmin, ymin = env.tuple
+ xmax, ymax = xmin, ymin
+ else:
+ xmin, ymin = env[0][0]
+ xmax, ymax = env[0][2]
+ return (xmin, ymin, xmax, ymax)
+
+ @property
+ def length(self):
+ """
+ Returns the length of this Geometry (e.g., 0 for point, or the
+ circumfrence of a Polygon).
+ """
+ return capi.geos_length(self.ptr, byref(c_double()))
+
+ def clone(self):
+ "Clones this Geometry."
+ return GEOSGeometry(capi.geom_clone(self.ptr), srid=self.srid)
+
+# Class mapping dictionary. Has to be at the end to avoid import
+# conflicts with GEOSGeometry.
+from django.contrib.gis.geos.linestring import LineString, LinearRing
+from django.contrib.gis.geos.point import Point
+from django.contrib.gis.geos.polygon import Polygon
+from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
+GEOS_CLASSES = {0 : Point,
+ 1 : LineString,
+ 2 : LinearRing,
+ 3 : Polygon,
+ 4 : MultiPoint,
+ 5 : MultiLineString,
+ 6 : MultiPolygon,
+ 7 : GeometryCollection,
+ }
+
+# If supported, import the PreparedGeometry class.
+if GEOS_PREPARE:
+ from django.contrib.gis.geos.prepared import PreparedGeometry
diff --git a/parts/django/django/contrib/gis/geos/io.py b/parts/django/django/contrib/gis/geos/io.py
new file mode 100644
index 0000000..54ba6b4
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/io.py
@@ -0,0 +1,20 @@
+"""
+Module that holds classes for performing I/O operations on GEOS geometry
+objects. Specifically, this has Python implementations of WKB/WKT
+reader and writer classes.
+"""
+from django.contrib.gis.geos.geometry import GEOSGeometry
+from django.contrib.gis.geos.prototypes.io import _WKTReader, _WKBReader, WKBWriter, WKTWriter
+
+# Public classes for (WKB|WKT)Reader, which return GEOSGeometry
+class WKBReader(_WKBReader):
+ def read(self, wkb):
+ "Returns a GEOSGeometry for the given WKB buffer."
+ return GEOSGeometry(super(WKBReader, self).read(wkb))
+
+class WKTReader(_WKTReader):
+ def read(self, wkt):
+ "Returns a GEOSGeometry for the given WKT string."
+ return GEOSGeometry(super(WKTReader, self).read(wkt))
+
+
diff --git a/parts/django/django/contrib/gis/geos/libgeos.py b/parts/django/django/contrib/gis/geos/libgeos.py
new file mode 100644
index 0000000..84299a0
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/libgeos.py
@@ -0,0 +1,141 @@
+"""
+ This module houses the ctypes initialization procedures, as well
+ as the notice and error handler function callbacks (get called
+ when an error occurs in GEOS).
+
+ This module also houses GEOS Pointer utilities, including
+ get_pointer_arr(), and GEOM_PTR.
+"""
+import os, re, sys
+from ctypes import c_char_p, Structure, CDLL, CFUNCTYPE, POINTER
+from ctypes.util import find_library
+from django.contrib.gis.geos.error import GEOSException
+
+# Custom library path set?
+try:
+ from django.conf import settings
+ lib_path = settings.GEOS_LIBRARY_PATH
+except (AttributeError, EnvironmentError, ImportError):
+ lib_path = None
+
+# Setting the appropriate names for the GEOS-C library.
+if lib_path:
+ lib_names = None
+elif os.name == 'nt':
+ # Windows NT libraries
+ lib_names = ['libgeos_c-1']
+elif os.name == 'posix':
+ # *NIX libraries
+ lib_names = ['geos_c', 'GEOS']
+else:
+ raise ImportError('Unsupported OS "%s"' % os.name)
+
+# Using the ctypes `find_library` utility to find the path to the GEOS
+# shared library. This is better than manually specifiying each library name
+# and extension (e.g., libgeos_c.[so|so.1|dylib].).
+if lib_names:
+ for lib_name in lib_names:
+ lib_path = find_library(lib_name)
+ if not lib_path is None: break
+
+# No GEOS library could be found.
+if lib_path is None:
+ raise ImportError('Could not find the GEOS library (tried "%s"). '
+ 'Try setting GEOS_LIBRARY_PATH in your settings.' %
+ '", "'.join(lib_names))
+
+# Getting the GEOS C library. The C interface (CDLL) is used for
+# both *NIX and Windows.
+# See the GEOS C API source code for more details on the library function calls:
+# http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
+lgeos = CDLL(lib_path)
+
+# The notice and error handler C function callback definitions.
+# Supposed to mimic the GEOS message handler (C below):
+# typedef void (*GEOSMessageHandler)(const char *fmt, ...);
+NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
+def notice_h(fmt, lst, output_h=sys.stdout):
+ try:
+ warn_msg = fmt % lst
+ except:
+ warn_msg = fmt
+ output_h.write('GEOS_NOTICE: %s\n' % warn_msg)
+notice_h = NOTICEFUNC(notice_h)
+
+ERRORFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
+def error_h(fmt, lst, output_h=sys.stderr):
+ try:
+ err_msg = fmt % lst
+ except:
+ err_msg = fmt
+ output_h.write('GEOS_ERROR: %s\n' % err_msg)
+error_h = ERRORFUNC(error_h)
+
+#### GEOS Geometry C data structures, and utility functions. ####
+
+# Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
+class GEOSGeom_t(Structure): pass
+class GEOSPrepGeom_t(Structure): pass
+class GEOSCoordSeq_t(Structure): pass
+class GEOSContextHandle_t(Structure): pass
+
+# Pointers to opaque GEOS geometry structures.
+GEOM_PTR = POINTER(GEOSGeom_t)
+PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
+CS_PTR = POINTER(GEOSCoordSeq_t)
+CONTEXT_PTR = POINTER(GEOSContextHandle_t)
+
+# Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
+# GEOS routines
+def get_pointer_arr(n):
+ "Gets a ctypes pointer array (of length `n`) for GEOSGeom_t opaque pointer."
+ GeomArr = GEOM_PTR * n
+ return GeomArr()
+
+# Returns the string version of the GEOS library. Have to set the restype
+# explicitly to c_char_p to ensure compatibility accross 32 and 64-bit platforms.
+geos_version = lgeos.GEOSversion
+geos_version.argtypes = None
+geos_version.restype = c_char_p
+
+# Regular expression should be able to parse version strings such as
+# '3.0.0rc4-CAPI-1.3.3', or '3.0.0-CAPI-1.4.1'
+version_regex = re.compile(r'^(?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<subminor>\d+))(rc(?P<release_candidate>\d+))?-CAPI-(?P<capi_version>\d+\.\d+\.\d+)$')
+def geos_version_info():
+ """
+ Returns a dictionary containing the various version metadata parsed from
+ the GEOS version string, including the version number, whether the version
+ is a release candidate (and what number release candidate), and the C API
+ version.
+ """
+ ver = geos_version()
+ m = version_regex.match(ver)
+ if not m: raise GEOSException('Could not parse version info string "%s"' % ver)
+ return dict((key, m.group(key)) for key in ('version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor'))
+
+# Version numbers and whether or not prepared geometry support is available.
+_verinfo = geos_version_info()
+GEOS_MAJOR_VERSION = int(_verinfo['major'])
+GEOS_MINOR_VERSION = int(_verinfo['minor'])
+GEOS_SUBMINOR_VERSION = int(_verinfo['subminor'])
+del _verinfo
+GEOS_VERSION = (GEOS_MAJOR_VERSION, GEOS_MINOR_VERSION, GEOS_SUBMINOR_VERSION)
+GEOS_PREPARE = GEOS_VERSION >= (3, 1, 0)
+
+if GEOS_PREPARE:
+ # Here we set up the prototypes for the initGEOS_r and finishGEOS_r
+ # routines. These functions aren't actually called until they are
+ # attached to a GEOS context handle -- this actually occurs in
+ # geos/prototypes/threadsafe.py.
+ lgeos.initGEOS_r.restype = CONTEXT_PTR
+ lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
+else:
+ # When thread-safety isn't available, the initGEOS routine must be called
+ # first. This function takes the notice and error functions, defined
+ # as Python callbacks above, as parameters. Here is the C code that is
+ # wrapped:
+ # extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);
+ lgeos.initGEOS(notice_h, error_h)
+ # Calling finishGEOS() upon exit of the interpreter.
+ import atexit
+ atexit.register(lgeos.finishGEOS)
diff --git a/parts/django/django/contrib/gis/geos/linestring.py b/parts/django/django/contrib/gis/geos/linestring.py
new file mode 100644
index 0000000..ecf7741
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/linestring.py
@@ -0,0 +1,152 @@
+from django.contrib.gis.geos.base import numpy
+from django.contrib.gis.geos.coordseq import GEOSCoordSeq
+from django.contrib.gis.geos.error import GEOSException
+from django.contrib.gis.geos.geometry import GEOSGeometry
+from django.contrib.gis.geos.point import Point
+from django.contrib.gis.geos import prototypes as capi
+
+class LineString(GEOSGeometry):
+ _init_func = capi.create_linestring
+ _minlength = 2
+
+ #### Python 'magic' routines ####
+ def __init__(self, *args, **kwargs):
+ """
+ Initializes on the given sequence -- may take lists, tuples, NumPy arrays
+ of X,Y pairs, or Point objects. If Point objects are used, ownership is
+ _not_ transferred to the LineString object.
+
+ Examples:
+ ls = LineString((1, 1), (2, 2))
+ ls = LineString([(1, 1), (2, 2)])
+ ls = LineString(array([(1, 1), (2, 2)]))
+ ls = LineString(Point(1, 1), Point(2, 2))
+ """
+ # If only one argument provided, set the coords array appropriately
+ if len(args) == 1: coords = args[0]
+ else: coords = args
+
+ if isinstance(coords, (tuple, list)):
+ # Getting the number of coords and the number of dimensions -- which
+ # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
+ ncoords = len(coords)
+ if coords: ndim = len(coords[0])
+ else: raise TypeError('Cannot initialize on empty sequence.')
+ self._checkdim(ndim)
+ # Incrementing through each of the coordinates and verifying
+ for i in xrange(1, ncoords):
+ if not isinstance(coords[i], (tuple, list, Point)):
+ raise TypeError('each coordinate should be a sequence (list or tuple)')
+ if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.')
+ numpy_coords = False
+ elif numpy and isinstance(coords, numpy.ndarray):
+ shape = coords.shape # Using numpy's shape.
+ if len(shape) != 2: raise TypeError('Too many dimensions.')
+ self._checkdim(shape[1])
+ ncoords = shape[0]
+ ndim = shape[1]
+ numpy_coords = True
+ else:
+ raise TypeError('Invalid initialization input for LineStrings.')
+
+ # Creating a coordinate sequence object because it is easier to
+ # set the points using GEOSCoordSeq.__setitem__().
+ cs = GEOSCoordSeq(capi.create_cs(ncoords, ndim), z=bool(ndim==3))
+
+ for i in xrange(ncoords):
+ if numpy_coords: cs[i] = coords[i,:]
+ elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
+ else: cs[i] = coords[i]
+
+ # If SRID was passed in with the keyword arguments
+ srid = kwargs.get('srid', None)
+
+ # Calling the base geometry initialization with the returned pointer
+ # from the function.
+ super(LineString, self).__init__(self._init_func(cs.ptr), srid=srid)
+
+ def __iter__(self):
+ "Allows iteration over this LineString."
+ for i in xrange(len(self)):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the number of points in this LineString."
+ return len(self._cs)
+
+ def _get_single_external(self, index):
+ return self._cs[index]
+
+ _get_single_internal = _get_single_external
+
+ def _set_list(self, length, items):
+ ndim = self._cs.dims #
+ hasz = self._cs.hasz # I don't understand why these are different
+
+ # create a new coordinate sequence and populate accordingly
+ cs = GEOSCoordSeq(capi.create_cs(length, ndim), z=hasz)
+ for i, c in enumerate(items):
+ cs[i] = c
+
+ ptr = self._init_func(cs.ptr)
+ if ptr:
+ capi.destroy_geom(self.ptr)
+ self.ptr = ptr
+ self._post_init(self.srid)
+ else:
+ # can this happen?
+ raise GEOSException('Geometry resulting from slice deletion was invalid.')
+
+ def _set_single(self, index, value):
+ self._checkindex(index)
+ self._cs[index] = value
+
+ def _checkdim(self, dim):
+ if dim not in (2, 3): raise TypeError('Dimension mismatch.')
+
+ #### Sequence Properties ####
+ @property
+ def tuple(self):
+ "Returns a tuple version of the geometry from the coordinate sequence."
+ return self._cs.tuple
+ coords = tuple
+
+ def _listarr(self, func):
+ """
+ Internal routine that returns a sequence (list) corresponding with
+ the given function. Will return a numpy array if possible.
+ """
+ lst = [func(i) for i in xrange(len(self))]
+ if numpy: return numpy.array(lst) # ARRRR!
+ else: return lst
+
+ @property
+ def array(self):
+ "Returns a numpy array for the LineString."
+ return self._listarr(self._cs.__getitem__)
+
+ @property
+ def merged(self):
+ "Returns the line merge of this LineString."
+ return self._topology(capi.geos_linemerge(self.ptr))
+
+ @property
+ def x(self):
+ "Returns a list or numpy array of the X variable."
+ return self._listarr(self._cs.getX)
+
+ @property
+ def y(self):
+ "Returns a list or numpy array of the Y variable."
+ return self._listarr(self._cs.getY)
+
+ @property
+ def z(self):
+ "Returns a list or numpy array of the Z variable."
+ if not self.hasz: return None
+ else: return self._listarr(self._cs.getZ)
+
+# LinearRings are LineStrings used within Polygons.
+class LinearRing(LineString):
+ _minLength = 4
+ _init_func = capi.create_linearring
diff --git a/parts/django/django/contrib/gis/geos/mutable_list.py b/parts/django/django/contrib/gis/geos/mutable_list.py
new file mode 100644
index 0000000..cc28147
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/mutable_list.py
@@ -0,0 +1,309 @@
+# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
+# Released under the New BSD license.
+"""
+This module contains a base type which provides list-style mutations
+without specific data storage methods.
+
+See also http://www.aryehleib.com/MutableLists.html
+
+Author: Aryeh Leib Taurog.
+"""
+class ListMixin(object):
+ """
+ A base class which provides complete list interface.
+ Derived classes must call ListMixin's __init__() function
+ and implement the following:
+
+ function _get_single_external(self, i):
+ Return single item with index i for general use.
+ The index i will always satisfy 0 <= i < len(self).
+
+ function _get_single_internal(self, i):
+ Same as above, but for use within the class [Optional]
+ Note that if _get_single_internal and _get_single_internal return
+ different types of objects, _set_list must distinguish
+ between the two and handle each appropriately.
+
+ function _set_list(self, length, items):
+ Recreate the entire object.
+
+ NOTE: items may be a generator which calls _get_single_internal.
+ Therefore, it is necessary to cache the values in a temporary:
+ temp = list(items)
+ before clobbering the original storage.
+
+ function _set_single(self, i, value):
+ Set the single item at index i to value [Optional]
+ If left undefined, all mutations will result in rebuilding
+ the object using _set_list.
+
+ function __len__(self):
+ Return the length
+
+ int _minlength:
+ The minimum legal length [Optional]
+
+ int _maxlength:
+ The maximum legal length [Optional]
+
+ type or tuple _allowed:
+ A type or tuple of allowed item types [Optional]
+
+ class _IndexError:
+ The type of exception to be raise on invalid index [Optional]
+ """
+
+ _minlength = 0
+ _maxlength = None
+ _IndexError = IndexError
+
+ ### Python initialization and special list interface methods ###
+
+ def __init__(self, *args, **kwargs):
+ if not hasattr(self, '_get_single_internal'):
+ self._get_single_internal = self._get_single_external
+
+ if not hasattr(self, '_set_single'):
+ self._set_single = self._set_single_rebuild
+ self._assign_extended_slice = self._assign_extended_slice_rebuild
+
+ super(ListMixin, self).__init__(*args, **kwargs)
+
+ def __getitem__(self, index):
+ "Get the item(s) at the specified index/slice."
+ if isinstance(index, slice):
+ return [self._get_single_external(i) for i in xrange(*index.indices(len(self)))]
+ else:
+ index = self._checkindex(index)
+ return self._get_single_external(index)
+
+ def __delitem__(self, index):
+ "Delete the item(s) at the specified index/slice."
+ if not isinstance(index, (int, long, slice)):
+ raise TypeError("%s is not a legal index" % index)
+
+ # calculate new length and dimensions
+ origLen = len(self)
+ if isinstance(index, (int, long)):
+ index = self._checkindex(index)
+ indexRange = [index]
+ else:
+ indexRange = range(*index.indices(origLen))
+
+ newLen = origLen - len(indexRange)
+ newItems = ( self._get_single_internal(i)
+ for i in xrange(origLen)
+ if i not in indexRange )
+
+ self._rebuild(newLen, newItems)
+
+ def __setitem__(self, index, val):
+ "Set the item(s) at the specified index/slice."
+ if isinstance(index, slice):
+ self._set_slice(index, val)
+ else:
+ index = self._checkindex(index)
+ self._check_allowed((val,))
+ self._set_single(index, val)
+
+ def __iter__(self):
+ "Iterate over the items in the list"
+ for i in xrange(len(self)):
+ yield self[i]
+
+ ### Special methods for arithmetic operations ###
+ def __add__(self, other):
+ 'add another list-like object'
+ return self.__class__(list(self) + list(other))
+
+ def __radd__(self, other):
+ 'add to another list-like object'
+ return other.__class__(list(other) + list(self))
+
+ def __iadd__(self, other):
+ 'add another list-like object to self'
+ self.extend(list(other))
+ return self
+
+ def __mul__(self, n):
+ 'multiply'
+ return self.__class__(list(self) * n)
+
+ def __rmul__(self, n):
+ 'multiply'
+ return self.__class__(list(self) * n)
+
+ def __imul__(self, n):
+ 'multiply'
+ if n <= 0:
+ del self[:]
+ else:
+ cache = list(self)
+ for i in range(n-1):
+ self.extend(cache)
+ return self
+
+ def __cmp__(self, other):
+ 'cmp'
+ slen = len(self)
+ for i in range(slen):
+ try:
+ c = cmp(self[i], other[i])
+ except IndexError:
+ # must be other is shorter
+ return 1
+ else:
+ # elements not equal
+ if c: return c
+
+ return cmp(slen, len(other))
+
+ ### Public list interface Methods ###
+ ## Non-mutating ##
+ def count(self, val):
+ "Standard list count method"
+ count = 0
+ for i in self:
+ if val == i: count += 1
+ return count
+
+ def index(self, val):
+ "Standard list index method"
+ for i in xrange(0, len(self)):
+ if self[i] == val: return i
+ raise ValueError('%s not found in object' % str(val))
+
+ ## Mutating ##
+ def append(self, val):
+ "Standard list append method"
+ self[len(self):] = [val]
+
+ def extend(self, vals):
+ "Standard list extend method"
+ self[len(self):] = vals
+
+ def insert(self, index, val):
+ "Standard list insert method"
+ if not isinstance(index, (int, long)):
+ raise TypeError("%s is not a legal index" % index)
+ self[index:index] = [val]
+
+ def pop(self, index=-1):
+ "Standard list pop method"
+ result = self[index]
+ del self[index]
+ return result
+
+ def remove(self, val):
+ "Standard list remove method"
+ del self[self.index(val)]
+
+ def reverse(self):
+ "Standard list reverse method"
+ self[:] = self[-1::-1]
+
+ def sort(self, cmp=cmp, key=None, reverse=False):
+ "Standard list sort method"
+ if key:
+ temp = [(key(v),v) for v in self]
+ temp.sort(cmp=cmp, key=lambda x: x[0], reverse=reverse)
+ self[:] = [v[1] for v in temp]
+ else:
+ temp = list(self)
+ temp.sort(cmp=cmp, reverse=reverse)
+ self[:] = temp
+
+ ### Private routines ###
+ def _rebuild(self, newLen, newItems):
+ if newLen < self._minlength:
+ raise ValueError('Must have at least %d items' % self._minlength)
+ if self._maxlength is not None and newLen > self._maxlength:
+ raise ValueError('Cannot have more than %d items' % self._maxlength)
+
+ self._set_list(newLen, newItems)
+
+ def _set_single_rebuild(self, index, value):
+ self._set_slice(slice(index, index + 1, 1), [value])
+
+ def _checkindex(self, index, correct=True):
+ length = len(self)
+ if 0 <= index < length:
+ return index
+ if correct and -length <= index < 0:
+ return index + length
+ raise self._IndexError('invalid index: %s' % str(index))
+
+ def _check_allowed(self, items):
+ if hasattr(self, '_allowed'):
+ if False in [isinstance(val, self._allowed) for val in items]:
+ raise TypeError('Invalid type encountered in the arguments.')
+
+ def _set_slice(self, index, values):
+ "Assign values to a slice of the object"
+ try:
+ iter(values)
+ except TypeError:
+ raise TypeError('can only assign an iterable to a slice')
+
+ self._check_allowed(values)
+
+ origLen = len(self)
+ valueList = list(values)
+ start, stop, step = index.indices(origLen)
+
+ # CAREFUL: index.step and step are not the same!
+ # step will never be None
+ if index.step is None:
+ self._assign_simple_slice(start, stop, valueList)
+ else:
+ self._assign_extended_slice(start, stop, step, valueList)
+
+ def _assign_extended_slice_rebuild(self, start, stop, step, valueList):
+ 'Assign an extended slice by rebuilding entire list'
+ indexList = range(start, stop, step)
+ # extended slice, only allow assigning slice of same size
+ if len(valueList) != len(indexList):
+ raise ValueError('attempt to assign sequence of size %d '
+ 'to extended slice of size %d'
+ % (len(valueList), len(indexList)))
+
+ # we're not changing the length of the sequence
+ newLen = len(self)
+ newVals = dict(zip(indexList, valueList))
+ def newItems():
+ for i in xrange(newLen):
+ if i in newVals:
+ yield newVals[i]
+ else:
+ yield self._get_single_internal(i)
+
+ self._rebuild(newLen, newItems())
+
+ def _assign_extended_slice(self, start, stop, step, valueList):
+ 'Assign an extended slice by re-assigning individual items'
+ indexList = range(start, stop, step)
+ # extended slice, only allow assigning slice of same size
+ if len(valueList) != len(indexList):
+ raise ValueError('attempt to assign sequence of size %d '
+ 'to extended slice of size %d'
+ % (len(valueList), len(indexList)))
+
+ for i, val in zip(indexList, valueList):
+ self._set_single(i, val)
+
+ def _assign_simple_slice(self, start, stop, valueList):
+ 'Assign a simple slice; Can assign slice of any length'
+ origLen = len(self)
+ stop = max(start, stop)
+ newLen = origLen - stop + start + len(valueList)
+ def newItems():
+ for i in xrange(origLen + 1):
+ if i == start:
+ for val in valueList:
+ yield val
+
+ if i < origLen:
+ if i < start or i >= stop:
+ yield self._get_single_internal(i)
+
+ self._rebuild(newLen, newItems())
diff --git a/parts/django/django/contrib/gis/geos/point.py b/parts/django/django/contrib/gis/geos/point.py
new file mode 100644
index 0000000..5c00a93
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/point.py
@@ -0,0 +1,135 @@
+from ctypes import c_uint
+from django.contrib.gis.geos.error import GEOSException
+from django.contrib.gis.geos.geometry import GEOSGeometry
+from django.contrib.gis.geos import prototypes as capi
+
+class Point(GEOSGeometry):
+ _minlength = 2
+ _maxlength = 3
+
+ def __init__(self, x, y=None, z=None, srid=None):
+ """
+ The Point object may be initialized with either a tuple, or individual
+ parameters.
+
+ For Example:
+ >>> p = Point((5, 23)) # 2D point, passed in as a tuple
+ >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
+ """
+ if isinstance(x, (tuple, list)):
+ # Here a tuple or list was passed in under the `x` parameter.
+ ndim = len(x)
+ coords = x
+ elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)):
+ # Here X, Y, and (optionally) Z were passed in individually, as parameters.
+ if isinstance(z, (int, float, long)):
+ ndim = 3
+ coords = [x, y, z]
+ else:
+ ndim = 2
+ coords = [x, y]
+ else:
+ raise TypeError('Invalid parameters given for Point initialization.')
+
+ point = self._create_point(ndim, coords)
+
+ # Initializing using the address returned from the GEOS
+ # createPoint factory.
+ super(Point, self).__init__(point, srid=srid)
+
+ def _create_point(self, ndim, coords):
+ """
+ Create a coordinate sequence, set X, Y, [Z], and create point
+ """
+ if ndim < 2 or ndim > 3:
+ raise TypeError('Invalid point dimension: %s' % str(ndim))
+
+ cs = capi.create_cs(c_uint(1), c_uint(ndim))
+ i = iter(coords)
+ capi.cs_setx(cs, 0, i.next())
+ capi.cs_sety(cs, 0, i.next())
+ if ndim == 3: capi.cs_setz(cs, 0, i.next())
+
+ return capi.create_point(cs)
+
+ def _set_list(self, length, items):
+ ptr = self._create_point(length, items)
+ if ptr:
+ capi.destroy_geom(self.ptr)
+ self._ptr = ptr
+ self._set_cs()
+ else:
+ # can this happen?
+ raise GEOSException('Geometry resulting from slice deletion was invalid.')
+
+ def _set_single(self, index, value):
+ self._cs.setOrdinate(index, 0, value)
+
+ def __iter__(self):
+ "Allows iteration over coordinates of this Point."
+ for i in xrange(len(self)):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the number of dimensions for this Point (either 0, 2 or 3)."
+ if self.empty: return 0
+ if self.hasz: return 3
+ else: return 2
+
+ def _get_single_external(self, index):
+ if index == 0:
+ return self.x
+ elif index == 1:
+ return self.y
+ elif index == 2:
+ return self.z
+
+ _get_single_internal = _get_single_external
+
+ def get_x(self):
+ "Returns the X component of the Point."
+ return self._cs.getOrdinate(0, 0)
+
+ def set_x(self, value):
+ "Sets the X component of the Point."
+ self._cs.setOrdinate(0, 0, value)
+
+ def get_y(self):
+ "Returns the Y component of the Point."
+ return self._cs.getOrdinate(1, 0)
+
+ def set_y(self, value):
+ "Sets the Y component of the Point."
+ self._cs.setOrdinate(1, 0, value)
+
+ def get_z(self):
+ "Returns the Z component of the Point."
+ if self.hasz:
+ return self._cs.getOrdinate(2, 0)
+ else:
+ return None
+
+ def set_z(self, value):
+ "Sets the Z component of the Point."
+ if self.hasz:
+ self._cs.setOrdinate(2, 0, value)
+ else:
+ raise GEOSException('Cannot set Z on 2D Point.')
+
+ # X, Y, Z properties
+ x = property(get_x, set_x)
+ y = property(get_y, set_y)
+ z = property(get_z, set_z)
+
+ ### Tuple setting and retrieval routines. ###
+ def get_coords(self):
+ "Returns a tuple of the point."
+ return self._cs.tuple
+
+ def set_coords(self, tup):
+ "Sets the coordinates of the point with the given tuple."
+ self._cs[0] = tup
+
+ # The tuple and coords properties
+ tuple = property(get_coords, set_coords)
+ coords = tuple
diff --git a/parts/django/django/contrib/gis/geos/polygon.py b/parts/django/django/contrib/gis/geos/polygon.py
new file mode 100644
index 0000000..92b2e4c
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/polygon.py
@@ -0,0 +1,166 @@
+from ctypes import c_uint, byref
+from django.contrib.gis.geos.error import GEOSIndexError
+from django.contrib.gis.geos.geometry import GEOSGeometry
+from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
+from django.contrib.gis.geos.linestring import LinearRing
+from django.contrib.gis.geos import prototypes as capi
+
+class Polygon(GEOSGeometry):
+ _minlength = 1
+
+ def __init__(self, *args, **kwargs):
+ """
+ Initializes on an exterior ring and a sequence of holes (both
+ instances may be either LinearRing instances, or a tuple/list
+ that may be constructed into a LinearRing).
+
+ Examples of initialization, where shell, hole1, and hole2 are
+ valid LinearRing geometries:
+ >>> poly = Polygon(shell, hole1, hole2)
+ >>> poly = Polygon(shell, (hole1, hole2))
+
+ Example where a tuple parameters are used:
+ >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)),
+ ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
+ """
+ if not args:
+ raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.')
+
+ # Getting the ext_ring and init_holes parameters from the argument list
+ ext_ring = args[0]
+ init_holes = args[1:]
+ n_holes = len(init_holes)
+
+ # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
+ if n_holes == 1 and isinstance(init_holes[0], (tuple, list)):
+ if len(init_holes[0]) == 0:
+ init_holes = ()
+ n_holes = 0
+ elif isinstance(init_holes[0][0], LinearRing):
+ init_holes = init_holes[0]
+ n_holes = len(init_holes)
+
+ polygon = self._create_polygon(n_holes + 1, (ext_ring,) + init_holes)
+ super(Polygon, self).__init__(polygon, **kwargs)
+
+ def __iter__(self):
+ "Iterates over each ring in the polygon."
+ for i in xrange(len(self)):
+ yield self[i]
+
+ def __len__(self):
+ "Returns the number of rings in this Polygon."
+ return self.num_interior_rings + 1
+
+ @classmethod
+ def from_bbox(cls, bbox):
+ "Constructs a Polygon from a bounding box (4-tuple)."
+ x0, y0, x1, y1 = bbox
+ return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % (
+ x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
+
+ ### These routines are needed for list-like operation w/ListMixin ###
+ def _create_polygon(self, length, items):
+ # Instantiate LinearRing objects if necessary, but don't clone them yet
+ # _construct_ring will throw a TypeError if a parameter isn't a valid ring
+ # If we cloned the pointers here, we wouldn't be able to clean up
+ # in case of error.
+ rings = []
+ for r in items:
+ if isinstance(r, GEOM_PTR):
+ rings.append(r)
+ else:
+ rings.append(self._construct_ring(r))
+
+ shell = self._clone(rings.pop(0))
+
+ n_holes = length - 1
+ if n_holes:
+ holes = get_pointer_arr(n_holes)
+ for i, r in enumerate(rings):
+ holes[i] = self._clone(r)
+ holes_param = byref(holes)
+ else:
+ holes_param = None
+
+ return capi.create_polygon(shell, holes_param, c_uint(n_holes))
+
+ def _clone(self, g):
+ if isinstance(g, GEOM_PTR):
+ return capi.geom_clone(g)
+ else:
+ return capi.geom_clone(g.ptr)
+
+ def _construct_ring(self, param, msg='Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'):
+ "Helper routine for trying to construct a ring from the given parameter."
+ if isinstance(param, LinearRing): return param
+ try:
+ ring = LinearRing(param)
+ return ring
+ except TypeError:
+ raise TypeError(msg)
+
+ def _set_list(self, length, items):
+ # Getting the current pointer, replacing with the newly constructed
+ # geometry, and destroying the old geometry.
+ prev_ptr = self.ptr
+ srid = self.srid
+ self.ptr = self._create_polygon(length, items)
+ if srid: self.srid = srid
+ capi.destroy_geom(prev_ptr)
+
+ def _get_single_internal(self, index):
+ """
+ Returns the ring at the specified index. The first index, 0, will
+ always return the exterior ring. Indices > 0 will return the
+ interior ring at the given index (e.g., poly[1] and poly[2] would
+ return the first and second interior ring, respectively).
+
+ CAREFUL: Internal/External are not the same as Interior/Exterior!
+ _get_single_internal returns a pointer from the existing geometries for use
+ internally by the object's methods. _get_single_external returns a clone
+ of the same geometry for use by external code.
+ """
+ if index == 0:
+ return capi.get_extring(self.ptr)
+ else:
+ # Getting the interior ring, have to subtract 1 from the index.
+ return capi.get_intring(self.ptr, index-1)
+
+ def _get_single_external(self, index):
+ return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid)
+
+ _set_single = GEOSGeometry._set_single_rebuild
+ _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild
+
+ #### Polygon Properties ####
+ @property
+ def num_interior_rings(self):
+ "Returns the number of interior rings."
+ # Getting the number of rings
+ return capi.get_nrings(self.ptr)
+
+ def _get_ext_ring(self):
+ "Gets the exterior ring of the Polygon."
+ return self[0]
+
+ def _set_ext_ring(self, ring):
+ "Sets the exterior ring of the Polygon."
+ self[0] = ring
+
+ # Properties for the exterior ring/shell.
+ exterior_ring = property(_get_ext_ring, _set_ext_ring)
+ shell = exterior_ring
+
+ @property
+ def tuple(self):
+ "Gets the tuple for each ring in this Polygon."
+ return tuple([self[i].tuple for i in xrange(len(self))])
+ coords = tuple
+
+ @property
+ def kml(self):
+ "Returns the KML representation of this Polygon."
+ inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml
+ for i in xrange(self.num_interior_rings)])
+ return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)
diff --git a/parts/django/django/contrib/gis/geos/prepared.py b/parts/django/django/contrib/gis/geos/prepared.py
new file mode 100644
index 0000000..68b812d
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prepared.py
@@ -0,0 +1,30 @@
+from django.contrib.gis.geos.base import GEOSBase
+from django.contrib.gis.geos.geometry import GEOSGeometry
+from django.contrib.gis.geos.prototypes import prepared as capi
+
+class PreparedGeometry(GEOSBase):
+ """
+ A geometry that is prepared for performing certain operations.
+ At the moment this includes the contains covers, and intersects
+ operations.
+ """
+ ptr_type = capi.PREPGEOM_PTR
+
+ def __init__(self, geom):
+ if not isinstance(geom, GEOSGeometry): raise TypeError
+ self.ptr = capi.geos_prepare(geom.ptr)
+
+ def __del__(self):
+ if self._ptr: capi.prepared_destroy(self._ptr)
+
+ def contains(self, other):
+ return capi.prepared_contains(self.ptr, other.ptr)
+
+ def contains_properly(self, other):
+ return capi.prepared_contains_properly(self.ptr, other.ptr)
+
+ def covers(self, other):
+ return capi.prepared_covers(self.ptr, other.ptr)
+
+ def intersects(self, other):
+ return capi.prepared_intersects(self.ptr, other.ptr)
diff --git a/parts/django/django/contrib/gis/geos/prototypes/__init__.py b/parts/django/django/contrib/gis/geos/prototypes/__init__.py
new file mode 100644
index 0000000..2355928
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/__init__.py
@@ -0,0 +1,30 @@
+"""
+ This module contains all of the GEOS ctypes function prototypes. Each
+ prototype handles the interaction between the GEOS library and Python
+ via ctypes.
+"""
+
+# Coordinate sequence routines.
+from django.contrib.gis.geos.prototypes.coordseq import create_cs, get_cs, \
+ cs_clone, cs_getordinate, cs_setordinate, cs_getx, cs_gety, cs_getz, \
+ cs_setx, cs_sety, cs_setz, cs_getsize, cs_getdims
+
+# Geometry routines.
+from django.contrib.gis.geos.prototypes.geom import from_hex, from_wkb, from_wkt, \
+ create_point, create_linestring, create_linearring, create_polygon, create_collection, \
+ destroy_geom, get_extring, get_intring, get_nrings, get_geomn, geom_clone, \
+ geos_normalize, geos_type, geos_typeid, geos_get_srid, geos_set_srid, \
+ get_dims, get_num_coords, get_num_geoms, \
+ to_hex, to_wkb, to_wkt
+
+# Miscellaneous routines.
+from django.contrib.gis.geos.prototypes.misc import geos_area, geos_distance, geos_length
+
+# Predicates
+from django.contrib.gis.geos.prototypes.predicates import geos_hasz, geos_isempty, \
+ geos_isring, geos_issimple, geos_isvalid, geos_contains, geos_crosses, \
+ geos_disjoint, geos_equals, geos_equalsexact, geos_intersects, \
+ geos_intersects, geos_overlaps, geos_relatepattern, geos_touches, geos_within
+
+# Topology routines
+from django.contrib.gis.geos.prototypes.topology import *
diff --git a/parts/django/django/contrib/gis/geos/prototypes/coordseq.py b/parts/django/django/contrib/gis/geos/prototypes/coordseq.py
new file mode 100644
index 0000000..68b9480
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/coordseq.py
@@ -0,0 +1,83 @@
+from ctypes import c_double, c_int, c_uint, POINTER
+from django.contrib.gis.geos.libgeos import GEOM_PTR, CS_PTR
+from django.contrib.gis.geos.prototypes.errcheck import last_arg_byref, GEOSException
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+## Error-checking routines specific to coordinate sequences. ##
+def check_cs_ptr(result, func, cargs):
+ "Error checking on routines that return Geometries."
+ if not result:
+ raise GEOSException('Error encountered checking Coordinate Sequence returned from GEOS C function "%s".' % func.__name__)
+ return result
+
+def check_cs_op(result, func, cargs):
+ "Checks the status code of a coordinate sequence operation."
+ if result == 0:
+ raise GEOSException('Could not set value on coordinate sequence')
+ else:
+ return result
+
+def check_cs_get(result, func, cargs):
+ "Checking the coordinate sequence retrieval."
+ check_cs_op(result, func, cargs)
+ # Object in by reference, return its value.
+ return last_arg_byref(cargs)
+
+## Coordinate sequence prototype generation functions. ##
+def cs_int(func):
+ "For coordinate sequence routines that return an integer."
+ func.argtypes = [CS_PTR, POINTER(c_uint)]
+ func.restype = c_int
+ func.errcheck = check_cs_get
+ return func
+
+def cs_operation(func, ordinate=False, get=False):
+ "For coordinate sequence operations."
+ if get:
+ # Get routines get double parameter passed-in by reference.
+ func.errcheck = check_cs_get
+ dbl_param = POINTER(c_double)
+ else:
+ func.errcheck = check_cs_op
+ dbl_param = c_double
+
+ if ordinate:
+ # Get/Set ordinate routines have an extra uint parameter.
+ func.argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
+ else:
+ func.argtypes = [CS_PTR, c_uint, dbl_param]
+
+ func.restype = c_int
+ return func
+
+def cs_output(func, argtypes):
+ "For routines that return a coordinate sequence."
+ func.argtypes = argtypes
+ func.restype = CS_PTR
+ func.errcheck = check_cs_ptr
+ return func
+
+## Coordinate Sequence ctypes prototypes ##
+
+# Coordinate Sequence constructors & cloning.
+cs_clone = cs_output(GEOSFunc('GEOSCoordSeq_clone'), [CS_PTR])
+create_cs = cs_output(GEOSFunc('GEOSCoordSeq_create'), [c_uint, c_uint])
+get_cs = cs_output(GEOSFunc('GEOSGeom_getCoordSeq'), [GEOM_PTR])
+
+# Getting, setting ordinate
+cs_getordinate = cs_operation(GEOSFunc('GEOSCoordSeq_getOrdinate'), ordinate=True, get=True)
+cs_setordinate = cs_operation(GEOSFunc('GEOSCoordSeq_setOrdinate'), ordinate=True)
+
+# For getting, x, y, z
+cs_getx = cs_operation(GEOSFunc('GEOSCoordSeq_getX'), get=True)
+cs_gety = cs_operation(GEOSFunc('GEOSCoordSeq_getY'), get=True)
+cs_getz = cs_operation(GEOSFunc('GEOSCoordSeq_getZ'), get=True)
+
+# For setting, x, y, z
+cs_setx = cs_operation(GEOSFunc('GEOSCoordSeq_setX'))
+cs_sety = cs_operation(GEOSFunc('GEOSCoordSeq_setY'))
+cs_setz = cs_operation(GEOSFunc('GEOSCoordSeq_setZ'))
+
+# These routines return size & dimensions.
+cs_getsize = cs_int(GEOSFunc('GEOSCoordSeq_getSize'))
+cs_getdims = cs_int(GEOSFunc('GEOSCoordSeq_getDimensions'))
diff --git a/parts/django/django/contrib/gis/geos/prototypes/errcheck.py b/parts/django/django/contrib/gis/geos/prototypes/errcheck.py
new file mode 100644
index 0000000..97fcd21
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/errcheck.py
@@ -0,0 +1,95 @@
+"""
+ Error checking functions for GEOS ctypes prototype functions.
+"""
+import os
+from ctypes import c_void_p, string_at, CDLL
+from django.contrib.gis.geos.error import GEOSException
+from django.contrib.gis.geos.libgeos import GEOS_VERSION
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+# Getting the `free` routine used to free the memory allocated for
+# string pointers returned by GEOS.
+if GEOS_VERSION >= (3, 1, 1):
+ # In versions 3.1.1 and above, `GEOSFree` was added to the C API
+ # because `free` isn't always available on all platforms.
+ free = GEOSFunc('GEOSFree')
+ free.argtypes = [c_void_p]
+ free.restype = None
+else:
+ # Getting the `free` routine from the C library of the platform.
+ if os.name == 'nt':
+ # On NT, use the MS C library.
+ libc = CDLL('msvcrt')
+ else:
+ # On POSIX platforms C library is obtained by passing None into `CDLL`.
+ libc = CDLL(None)
+ free = libc.free
+
+### ctypes error checking routines ###
+def last_arg_byref(args):
+ "Returns the last C argument's value by reference."
+ return args[-1]._obj.value
+
+def check_dbl(result, func, cargs):
+ "Checks the status code and returns the double value passed in by reference."
+ # Checking the status code
+ if result != 1: return None
+ # Double passed in by reference, return its value.
+ return last_arg_byref(cargs)
+
+def check_geom(result, func, cargs):
+ "Error checking on routines that return Geometries."
+ if not result:
+ raise GEOSException('Error encountered checking Geometry returned from GEOS C function "%s".' % func.__name__)
+ return result
+
+def check_minus_one(result, func, cargs):
+ "Error checking on routines that should not return -1."
+ if result == -1:
+ raise GEOSException('Error encountered in GEOS C function "%s".' % func.__name__)
+ else:
+ return result
+
+def check_predicate(result, func, cargs):
+ "Error checking for unary/binary predicate functions."
+ val = ord(result) # getting the ordinal from the character
+ if val == 1: return True
+ elif val == 0: return False
+ else:
+ raise GEOSException('Error encountered on GEOS C predicate function "%s".' % func.__name__)
+
+def check_sized_string(result, func, cargs):
+ """
+ Error checking for routines that return explicitly sized strings.
+
+ This frees the memory allocated by GEOS at the result pointer.
+ """
+ if not result:
+ raise GEOSException('Invalid string pointer returned by GEOS C function "%s"' % func.__name__)
+ # A c_size_t object is passed in by reference for the second
+ # argument on these routines, and its needed to determine the
+ # correct size.
+ s = string_at(result, last_arg_byref(cargs))
+ # Freeing the memory allocated within GEOS
+ free(result)
+ return s
+
+def check_string(result, func, cargs):
+ """
+ Error checking for routines that return strings.
+
+ This frees the memory allocated by GEOS at the result pointer.
+ """
+ if not result: raise GEOSException('Error encountered checking string return value in GEOS C function "%s".' % func.__name__)
+ # Getting the string value at the pointer address.
+ s = string_at(result)
+ # Freeing the memory allocated within GEOS
+ free(result)
+ return s
+
+def check_zero(result, func, cargs):
+ "Error checking on routines that should not return 0."
+ if result == 0:
+ raise GEOSException('Error encountered in GEOS C function "%s".' % func.__name__)
+ else:
+ return result
diff --git a/parts/django/django/contrib/gis/geos/prototypes/geom.py b/parts/django/django/contrib/gis/geos/prototypes/geom.py
new file mode 100644
index 0000000..03f9897
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/geom.py
@@ -0,0 +1,119 @@
+from ctypes import c_char_p, c_int, c_size_t, c_ubyte, c_uint, POINTER
+from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.prototypes.errcheck import \
+ check_geom, check_minus_one, check_sized_string, check_string, check_zero
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+# This is the return type used by binary output (WKB, HEX) routines.
+c_uchar_p = POINTER(c_ubyte)
+
+# We create a simple subclass of c_char_p here because when the response
+# type is set to c_char_p, you get a _Python_ string and there's no way
+# to access the string's address inside the error checking function.
+# In other words, you can't free the memory allocated inside GEOS. Previously,
+# the return type would just be omitted and the integer address would be
+# used -- but this allows us to be specific in the function definition and
+# keeps the reference so it may be free'd.
+class geos_char_p(c_char_p):
+ pass
+
+### ctypes generation functions ###
+def bin_constructor(func):
+ "Generates a prototype for binary construction (HEX, WKB) GEOS routines."
+ func.argtypes = [c_char_p, c_size_t]
+ func.restype = GEOM_PTR
+ func.errcheck = check_geom
+ return func
+
+# HEX & WKB output
+def bin_output(func):
+ "Generates a prototype for the routines that return a a sized string."
+ func.argtypes = [GEOM_PTR, POINTER(c_size_t)]
+ func.errcheck = check_sized_string
+ func.restype = c_uchar_p
+ return func
+
+def geom_output(func, argtypes):
+ "For GEOS routines that return a geometry."
+ if argtypes: func.argtypes = argtypes
+ func.restype = GEOM_PTR
+ func.errcheck = check_geom
+ return func
+
+def geom_index(func):
+ "For GEOS routines that return geometries from an index."
+ return geom_output(func, [GEOM_PTR, c_int])
+
+def int_from_geom(func, zero=False):
+ "Argument is a geometry, return type is an integer."
+ func.argtypes = [GEOM_PTR]
+ func.restype = c_int
+ if zero:
+ func.errcheck = check_zero
+ else:
+ func.errcheck = check_minus_one
+ return func
+
+def string_from_geom(func):
+ "Argument is a Geometry, return type is a string."
+ func.argtypes = [GEOM_PTR]
+ func.restype = geos_char_p
+ func.errcheck = check_string
+ return func
+
+### ctypes prototypes ###
+
+# Deprecated creation routines from WKB, HEX, WKT
+from_hex = bin_constructor(GEOSFunc('GEOSGeomFromHEX_buf'))
+from_wkb = bin_constructor(GEOSFunc('GEOSGeomFromWKB_buf'))
+from_wkt = geom_output(GEOSFunc('GEOSGeomFromWKT'), [c_char_p])
+
+# Deprecated output routines
+to_hex = bin_output(GEOSFunc('GEOSGeomToHEX_buf'))
+to_wkb = bin_output(GEOSFunc('GEOSGeomToWKB_buf'))
+to_wkt = string_from_geom(GEOSFunc('GEOSGeomToWKT'))
+
+# The GEOS geometry type, typeid, num_coordites and number of geometries
+geos_normalize = int_from_geom(GEOSFunc('GEOSNormalize'))
+geos_type = string_from_geom(GEOSFunc('GEOSGeomType'))
+geos_typeid = int_from_geom(GEOSFunc('GEOSGeomTypeId'))
+get_dims = int_from_geom(GEOSFunc('GEOSGeom_getDimensions'), zero=True)
+get_num_coords = int_from_geom(GEOSFunc('GEOSGetNumCoordinates'))
+get_num_geoms = int_from_geom(GEOSFunc('GEOSGetNumGeometries'))
+
+# Geometry creation factories
+create_point = geom_output(GEOSFunc('GEOSGeom_createPoint'), [CS_PTR])
+create_linestring = geom_output(GEOSFunc('GEOSGeom_createLineString'), [CS_PTR])
+create_linearring = geom_output(GEOSFunc('GEOSGeom_createLinearRing'), [CS_PTR])
+
+# Polygon and collection creation routines are special and will not
+# have their argument types defined.
+create_polygon = geom_output(GEOSFunc('GEOSGeom_createPolygon'), None)
+create_collection = geom_output(GEOSFunc('GEOSGeom_createCollection'), None)
+
+# Ring routines
+get_extring = geom_output(GEOSFunc('GEOSGetExteriorRing'), [GEOM_PTR])
+get_intring = geom_index(GEOSFunc('GEOSGetInteriorRingN'))
+get_nrings = int_from_geom(GEOSFunc('GEOSGetNumInteriorRings'))
+
+# Collection Routines
+get_geomn = geom_index(GEOSFunc('GEOSGetGeometryN'))
+
+# Cloning
+geom_clone = GEOSFunc('GEOSGeom_clone')
+geom_clone.argtypes = [GEOM_PTR]
+geom_clone.restype = GEOM_PTR
+
+# Destruction routine.
+destroy_geom = GEOSFunc('GEOSGeom_destroy')
+destroy_geom.argtypes = [GEOM_PTR]
+destroy_geom.restype = None
+
+# SRID routines
+geos_get_srid = GEOSFunc('GEOSGetSRID')
+geos_get_srid.argtypes = [GEOM_PTR]
+geos_get_srid.restype = c_int
+
+geos_set_srid = GEOSFunc('GEOSSetSRID')
+geos_set_srid.argtypes = [GEOM_PTR, c_int]
+geos_set_srid.restype = None
diff --git a/parts/django/django/contrib/gis/geos/prototypes/io.py b/parts/django/django/contrib/gis/geos/prototypes/io.py
new file mode 100644
index 0000000..5c0c8b5
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/io.py
@@ -0,0 +1,242 @@
+import threading
+from ctypes import byref, c_char_p, c_int, c_char, c_size_t, Structure, POINTER
+from django.contrib.gis.geos.base import GEOSBase
+from django.contrib.gis.geos.libgeos import GEOM_PTR
+from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string, check_sized_string
+from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+### The WKB/WKT Reader/Writer structures and pointers ###
+class WKTReader_st(Structure): pass
+class WKTWriter_st(Structure): pass
+class WKBReader_st(Structure): pass
+class WKBWriter_st(Structure): pass
+
+WKT_READ_PTR = POINTER(WKTReader_st)
+WKT_WRITE_PTR = POINTER(WKTWriter_st)
+WKB_READ_PTR = POINTER(WKBReader_st)
+WKB_WRITE_PTR = POINTER(WKBReader_st)
+
+### WKTReader routines ###
+wkt_reader_create = GEOSFunc('GEOSWKTReader_create')
+wkt_reader_create.restype = WKT_READ_PTR
+
+wkt_reader_destroy = GEOSFunc('GEOSWKTReader_destroy')
+wkt_reader_destroy.argtypes = [WKT_READ_PTR]
+
+wkt_reader_read = GEOSFunc('GEOSWKTReader_read')
+wkt_reader_read.argtypes = [WKT_READ_PTR, c_char_p]
+wkt_reader_read.restype = GEOM_PTR
+wkt_reader_read.errcheck = check_geom
+
+### WKTWriter routines ###
+wkt_writer_create = GEOSFunc('GEOSWKTWriter_create')
+wkt_writer_create.restype = WKT_WRITE_PTR
+
+wkt_writer_destroy = GEOSFunc('GEOSWKTWriter_destroy')
+wkt_writer_destroy.argtypes = [WKT_WRITE_PTR]
+
+wkt_writer_write = GEOSFunc('GEOSWKTWriter_write')
+wkt_writer_write.argtypes = [WKT_WRITE_PTR, GEOM_PTR]
+wkt_writer_write.restype = geos_char_p
+wkt_writer_write.errcheck = check_string
+
+### WKBReader routines ###
+wkb_reader_create = GEOSFunc('GEOSWKBReader_create')
+wkb_reader_create.restype = WKB_READ_PTR
+
+wkb_reader_destroy = GEOSFunc('GEOSWKBReader_destroy')
+wkb_reader_destroy.argtypes = [WKB_READ_PTR]
+
+def wkb_read_func(func):
+ # Although the function definitions take `const unsigned char *`
+ # as their parameter, we use c_char_p here so the function may
+ # take Python strings directly as parameters. Inside Python there
+ # is not a difference between signed and unsigned characters, so
+ # it is not a problem.
+ func.argtypes = [WKB_READ_PTR, c_char_p, c_size_t]
+ func.restype = GEOM_PTR
+ func.errcheck = check_geom
+ return func
+
+wkb_reader_read = wkb_read_func(GEOSFunc('GEOSWKBReader_read'))
+wkb_reader_read_hex = wkb_read_func(GEOSFunc('GEOSWKBReader_readHEX'))
+
+### WKBWriter routines ###
+wkb_writer_create = GEOSFunc('GEOSWKBWriter_create')
+wkb_writer_create.restype = WKB_WRITE_PTR
+
+wkb_writer_destroy = GEOSFunc('GEOSWKBWriter_destroy')
+wkb_writer_destroy.argtypes = [WKB_WRITE_PTR]
+
+# WKB Writing prototypes.
+def wkb_write_func(func):
+ func.argtypes = [WKB_WRITE_PTR, GEOM_PTR, POINTER(c_size_t)]
+ func.restype = c_uchar_p
+ func.errcheck = check_sized_string
+ return func
+
+wkb_writer_write = wkb_write_func(GEOSFunc('GEOSWKBWriter_write'))
+wkb_writer_write_hex = wkb_write_func(GEOSFunc('GEOSWKBWriter_writeHEX'))
+
+# WKBWriter property getter/setter prototypes.
+def wkb_writer_get(func, restype=c_int):
+ func.argtypes = [WKB_WRITE_PTR]
+ func.restype = restype
+ return func
+
+def wkb_writer_set(func, argtype=c_int):
+ func.argtypes = [WKB_WRITE_PTR, argtype]
+ return func
+
+wkb_writer_get_byteorder = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getByteOrder'))
+wkb_writer_set_byteorder = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setByteOrder'))
+wkb_writer_get_outdim = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getOutputDimension'))
+wkb_writer_set_outdim = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setOutputDimension'))
+wkb_writer_get_include_srid = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getIncludeSRID'), restype=c_char)
+wkb_writer_set_include_srid = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setIncludeSRID'), argtype=c_char)
+
+### Base I/O Class ###
+class IOBase(GEOSBase):
+ "Base class for GEOS I/O objects."
+ def __init__(self):
+ # Getting the pointer with the constructor.
+ self.ptr = self._constructor()
+
+ def __del__(self):
+ # Cleaning up with the appropriate destructor.
+ if self._ptr: self._destructor(self._ptr)
+
+### Base WKB/WKT Reading and Writing objects ###
+
+# Non-public WKB/WKT reader classes for internal use because
+# their `read` methods return _pointers_ instead of GEOSGeometry
+# objects.
+class _WKTReader(IOBase):
+ _constructor = wkt_reader_create
+ _destructor = wkt_reader_destroy
+ ptr_type = WKT_READ_PTR
+
+ def read(self, wkt):
+ if not isinstance(wkt, basestring): raise TypeError
+ return wkt_reader_read(self.ptr, wkt)
+
+class _WKBReader(IOBase):
+ _constructor = wkb_reader_create
+ _destructor = wkb_reader_destroy
+ ptr_type = WKB_READ_PTR
+
+ def read(self, wkb):
+ "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
+ if isinstance(wkb, buffer):
+ wkb_s = str(wkb)
+ return wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
+ elif isinstance(wkb, basestring):
+ return wkb_reader_read_hex(self.ptr, wkb, len(wkb))
+ else:
+ raise TypeError
+
+### WKB/WKT Writer Classes ###
+class WKTWriter(IOBase):
+ _constructor = wkt_writer_create
+ _destructor = wkt_writer_destroy
+ ptr_type = WKT_WRITE_PTR
+
+ def write(self, geom):
+ "Returns the WKT representation of the given geometry."
+ return wkt_writer_write(self.ptr, geom.ptr)
+
+class WKBWriter(IOBase):
+ _constructor = wkb_writer_create
+ _destructor = wkb_writer_destroy
+ ptr_type = WKB_WRITE_PTR
+
+ def write(self, geom):
+ "Returns the WKB representation of the given geometry."
+ return buffer(wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())))
+
+ def write_hex(self, geom):
+ "Returns the HEXEWKB representation of the given geometry."
+ return wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
+
+ ### WKBWriter Properties ###
+
+ # Property for getting/setting the byteorder.
+ def _get_byteorder(self):
+ return wkb_writer_get_byteorder(self.ptr)
+
+ def _set_byteorder(self, order):
+ if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
+ wkb_writer_set_byteorder(self.ptr, order)
+
+ byteorder = property(_get_byteorder, _set_byteorder)
+
+ # Property for getting/setting the output dimension.
+ def _get_outdim(self):
+ return wkb_writer_get_outdim(self.ptr)
+
+ def _set_outdim(self, new_dim):
+ if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3')
+ wkb_writer_set_outdim(self.ptr, new_dim)
+
+ outdim = property(_get_outdim, _set_outdim)
+
+ # Property for getting/setting the include srid flag.
+ def _get_include_srid(self):
+ return bool(ord(wkb_writer_get_include_srid(self.ptr)))
+
+ def _set_include_srid(self, include):
+ if bool(include): flag = chr(1)
+ else: flag = chr(0)
+ wkb_writer_set_include_srid(self.ptr, flag)
+
+ srid = property(_get_include_srid, _set_include_srid)
+
+# `ThreadLocalIO` object holds instances of the WKT and WKB reader/writer
+# objects that are local to the thread. The `GEOSGeometry` internals
+# access these instances by calling the module-level functions, defined
+# below.
+class ThreadLocalIO(threading.local):
+ wkt_r = None
+ wkt_w = None
+ wkb_r = None
+ wkb_w = None
+ ewkb_w = None
+ ewkb_w3d = None
+
+thread_context = ThreadLocalIO()
+
+# These module-level routines return the I/O object that is local to the
+# the thread. If the I/O object does not exist yet it will be initialized.
+def wkt_r():
+ if not thread_context.wkt_r:
+ thread_context.wkt_r = _WKTReader()
+ return thread_context.wkt_r
+
+def wkt_w():
+ if not thread_context.wkt_w:
+ thread_context.wkt_w = WKTWriter()
+ return thread_context.wkt_w
+
+def wkb_r():
+ if not thread_context.wkb_r:
+ thread_context.wkb_r = _WKBReader()
+ return thread_context.wkb_r
+
+def wkb_w():
+ if not thread_context.wkb_w:
+ thread_context.wkb_w = WKBWriter()
+ return thread_context.wkb_w
+
+def ewkb_w():
+ if not thread_context.ewkb_w:
+ thread_context.ewkb_w = WKBWriter()
+ thread_context.ewkb_w.srid = True
+ return thread_context.ewkb_w
+
+def ewkb_w3d():
+ if not thread_context.ewkb_w3d:
+ thread_context.ewkb_w3d = WKBWriter()
+ thread_context.ewkb_w3d.srid = True
+ thread_context.ewkb_w3d.outdim = 3
+ return thread_context.ewkb_w3d
diff --git a/parts/django/django/contrib/gis/geos/prototypes/misc.py b/parts/django/django/contrib/gis/geos/prototypes/misc.py
new file mode 100644
index 0000000..5b3b658
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/misc.py
@@ -0,0 +1,28 @@
+"""
+ This module is for the miscellaneous GEOS routines, particularly the
+ ones that return the area, distance, and length.
+"""
+from ctypes import c_int, c_double, POINTER
+from django.contrib.gis.geos.libgeos import GEOM_PTR
+from django.contrib.gis.geos.prototypes.errcheck import check_dbl
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+### ctypes generator function ###
+def dbl_from_geom(func, num_geom=1):
+ """
+ Argument is a Geometry, return type is double that is passed
+ in by reference as the last argument.
+ """
+ argtypes = [GEOM_PTR for i in xrange(num_geom)]
+ argtypes += [POINTER(c_double)]
+ func.argtypes = argtypes
+ func.restype = c_int # Status code returned
+ func.errcheck = check_dbl
+ return func
+
+### ctypes prototypes ###
+
+# Area, distance, and length prototypes.
+geos_area = dbl_from_geom(GEOSFunc('GEOSArea'))
+geos_distance = dbl_from_geom(GEOSFunc('GEOSDistance'), num_geom=2)
+geos_length = dbl_from_geom(GEOSFunc('GEOSLength'))
diff --git a/parts/django/django/contrib/gis/geos/prototypes/predicates.py b/parts/django/django/contrib/gis/geos/prototypes/predicates.py
new file mode 100644
index 0000000..bf69bb1
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/predicates.py
@@ -0,0 +1,44 @@
+"""
+ This module houses the GEOS ctypes prototype functions for the
+ unary and binary predicate operations on geometries.
+"""
+from ctypes import c_char, c_char_p, c_double
+from django.contrib.gis.geos.libgeos import GEOM_PTR
+from django.contrib.gis.geos.prototypes.errcheck import check_predicate
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+## Binary & unary predicate functions ##
+def binary_predicate(func, *args):
+ "For GEOS binary predicate functions."
+ argtypes = [GEOM_PTR, GEOM_PTR]
+ if args: argtypes += args
+ func.argtypes = argtypes
+ func.restype = c_char
+ func.errcheck = check_predicate
+ return func
+
+def unary_predicate(func):
+ "For GEOS unary predicate functions."
+ func.argtypes = [GEOM_PTR]
+ func.restype = c_char
+ func.errcheck = check_predicate
+ return func
+
+## Unary Predicates ##
+geos_hasz = unary_predicate(GEOSFunc('GEOSHasZ'))
+geos_isempty = unary_predicate(GEOSFunc('GEOSisEmpty'))
+geos_isring = unary_predicate(GEOSFunc('GEOSisRing'))
+geos_issimple = unary_predicate(GEOSFunc('GEOSisSimple'))
+geos_isvalid = unary_predicate(GEOSFunc('GEOSisValid'))
+
+## Binary Predicates ##
+geos_contains = binary_predicate(GEOSFunc('GEOSContains'))
+geos_crosses = binary_predicate(GEOSFunc('GEOSCrosses'))
+geos_disjoint = binary_predicate(GEOSFunc('GEOSDisjoint'))
+geos_equals = binary_predicate(GEOSFunc('GEOSEquals'))
+geos_equalsexact = binary_predicate(GEOSFunc('GEOSEqualsExact'), c_double)
+geos_intersects = binary_predicate(GEOSFunc('GEOSIntersects'))
+geos_overlaps = binary_predicate(GEOSFunc('GEOSOverlaps'))
+geos_relatepattern = binary_predicate(GEOSFunc('GEOSRelatePattern'), c_char_p)
+geos_touches = binary_predicate(GEOSFunc('GEOSTouches'))
+geos_within = binary_predicate(GEOSFunc('GEOSWithin'))
diff --git a/parts/django/django/contrib/gis/geos/prototypes/prepared.py b/parts/django/django/contrib/gis/geos/prototypes/prepared.py
new file mode 100644
index 0000000..7342d7d
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/prepared.py
@@ -0,0 +1,25 @@
+from ctypes import c_char
+from django.contrib.gis.geos.libgeos import GEOM_PTR, PREPGEOM_PTR
+from django.contrib.gis.geos.prototypes.errcheck import check_predicate
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+# Prepared geometry constructor and destructors.
+geos_prepare = GEOSFunc('GEOSPrepare')
+geos_prepare.argtypes = [GEOM_PTR]
+geos_prepare.restype = PREPGEOM_PTR
+
+prepared_destroy = GEOSFunc('GEOSPreparedGeom_destroy')
+prepared_destroy.argtpes = [PREPGEOM_PTR]
+prepared_destroy.restype = None
+
+# Prepared geometry binary predicate support.
+def prepared_predicate(func):
+ func.argtypes= [PREPGEOM_PTR, GEOM_PTR]
+ func.restype = c_char
+ func.errcheck = check_predicate
+ return func
+
+prepared_contains = prepared_predicate(GEOSFunc('GEOSPreparedContains'))
+prepared_contains_properly = prepared_predicate(GEOSFunc('GEOSPreparedContainsProperly'))
+prepared_covers = prepared_predicate(GEOSFunc('GEOSPreparedCovers'))
+prepared_intersects = prepared_predicate(GEOSFunc('GEOSPreparedIntersects'))
diff --git a/parts/django/django/contrib/gis/geos/prototypes/threadsafe.py b/parts/django/django/contrib/gis/geos/prototypes/threadsafe.py
new file mode 100644
index 0000000..5888ed1
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/threadsafe.py
@@ -0,0 +1,90 @@
+import threading
+from django.contrib.gis.geos.libgeos import lgeos, notice_h, error_h, CONTEXT_PTR
+
+class GEOSContextHandle(object):
+ """
+ Python object representing a GEOS context handle.
+ """
+ def __init__(self):
+ # Initializing the context handler for this thread with
+ # the notice and error handler.
+ self.ptr = lgeos.initGEOS_r(notice_h, error_h)
+
+ def __del__(self):
+ if self.ptr: lgeos.finishGEOS_r(self.ptr)
+
+# Defining a thread-local object and creating an instance
+# to hold a reference to GEOSContextHandle for this thread.
+class GEOSContext(threading.local):
+ handle = None
+
+thread_context = GEOSContext()
+
+def call_geos_threaded(cfunc, args):
+ """
+ This module-level routine calls the specified GEOS C thread-safe
+ function with the context for this current thread.
+ """
+ # If a context handle does not exist for this thread, initialize one.
+ if not thread_context.handle:
+ thread_context.handle = GEOSContextHandle()
+ # Call the threaded GEOS routine with pointer of the context handle
+ # as the first argument.
+ return cfunc(thread_context.handle.ptr, *args)
+
+class GEOSFunc(object):
+ """
+ Class that serves as a wrapper for GEOS C Functions, and will
+ use thread-safe function variants when available.
+ """
+ def __init__(self, func_name):
+ try:
+ # GEOS thread-safe function signatures end with '_r', and
+ # take an additional context handle parameter.
+ self.cfunc = getattr(lgeos, func_name + '_r')
+ self.threaded = True
+ except AttributeError:
+ # Otherwise, use usual function.
+ self.cfunc = getattr(lgeos, func_name)
+ self.threaded = False
+
+ def __call__(self, *args):
+ if self.threaded:
+ return call_geos_threaded(self.cfunc, args)
+ else:
+ return self.cfunc(*args)
+
+ def __str__(self):
+ return self.cfunc.__name__
+
+ # argtypes property
+ def _get_argtypes(self):
+ return self.cfunc.argtypes
+
+ def _set_argtypes(self, argtypes):
+ if self.threaded:
+ new_argtypes = [CONTEXT_PTR]
+ new_argtypes.extend(argtypes)
+ self.cfunc.argtypes = new_argtypes
+ else:
+ self.cfunc.argtypes = argtypes
+
+ argtypes = property(_get_argtypes, _set_argtypes)
+
+ # restype property
+ def _get_restype(self):
+ return self.cfunc.restype
+
+ def _set_restype(self, restype):
+ self.cfunc.restype = restype
+
+ restype = property(_get_restype, _set_restype)
+
+ # errcheck property
+ def _get_errcheck(self):
+ return self.cfunc.errcheck
+
+ def _set_errcheck(self, errcheck):
+ self.cfunc.errcheck = errcheck
+
+ errcheck = property(_get_errcheck, _set_errcheck)
diff --git a/parts/django/django/contrib/gis/geos/prototypes/topology.py b/parts/django/django/contrib/gis/geos/prototypes/topology.py
new file mode 100644
index 0000000..50817f9
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/prototypes/topology.py
@@ -0,0 +1,51 @@
+"""
+ This module houses the GEOS ctypes prototype functions for the
+ topological operations on geometries.
+"""
+__all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull',
+ 'geos_difference', 'geos_envelope', 'geos_intersection',
+ 'geos_linemerge', 'geos_pointonsurface', 'geos_preservesimplify',
+ 'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate']
+
+from ctypes import c_char_p, c_double, c_int
+from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
+from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
+from django.contrib.gis.geos.prototypes.geom import geos_char_p
+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
+
+def topology(func, *args):
+ "For GEOS unary topology functions."
+ argtypes = [GEOM_PTR]
+ if args: argtypes += args
+ func.argtypes = argtypes
+ func.restype = GEOM_PTR
+ func.errcheck = check_geom
+ return func
+
+### Topology Routines ###
+geos_boundary = topology(GEOSFunc('GEOSBoundary'))
+geos_buffer = topology(GEOSFunc('GEOSBuffer'), c_double, c_int)
+geos_centroid = topology(GEOSFunc('GEOSGetCentroid'))
+geos_convexhull = topology(GEOSFunc('GEOSConvexHull'))
+geos_difference = topology(GEOSFunc('GEOSDifference'), GEOM_PTR)
+geos_envelope = topology(GEOSFunc('GEOSEnvelope'))
+geos_intersection = topology(GEOSFunc('GEOSIntersection'), GEOM_PTR)
+geos_linemerge = topology(GEOSFunc('GEOSLineMerge'))
+geos_pointonsurface = topology(GEOSFunc('GEOSPointOnSurface'))
+geos_preservesimplify = topology(GEOSFunc('GEOSTopologyPreserveSimplify'), c_double)
+geos_simplify = topology(GEOSFunc('GEOSSimplify'), c_double)
+geos_symdifference = topology(GEOSFunc('GEOSSymDifference'), GEOM_PTR)
+geos_union = topology(GEOSFunc('GEOSUnion'), GEOM_PTR)
+
+# GEOSRelate returns a string, not a geometry.
+geos_relate = GEOSFunc('GEOSRelate')
+geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
+geos_relate.restype = geos_char_p
+geos_relate.errcheck = check_string
+
+# Routines only in GEOS 3.1+
+if GEOS_PREPARE:
+ geos_cascaded_union = GEOSFunc('GEOSUnionCascaded')
+ geos_cascaded_union.argtypes = [GEOM_PTR]
+ geos_cascaded_union.restype = GEOM_PTR
+ __all__.append('geos_cascaded_union')
diff --git a/parts/django/django/contrib/gis/geos/tests/__init__.py b/parts/django/django/contrib/gis/geos/tests/__init__.py
new file mode 100644
index 0000000..44c8e26
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/tests/__init__.py
@@ -0,0 +1,25 @@
+"""
+GEOS Testing module.
+"""
+from unittest import TestSuite, TextTestRunner
+import test_geos, test_io, test_geos_mutation, test_mutable_list
+
+test_suites = [
+ test_geos.suite(),
+ test_io.suite(),
+ test_geos_mutation.suite(),
+ test_mutable_list.suite(),
+ ]
+
+def suite():
+ "Builds a test suite for the GEOS tests."
+ s = TestSuite()
+ map(s.addTest, test_suites)
+ return s
+
+def run(verbosity=1):
+ "Runs the GEOS tests."
+ TextTestRunner(verbosity=verbosity).run(suite())
+
+if __name__ == '__main__':
+ run(2)
diff --git a/parts/django/django/contrib/gis/geos/tests/test_geos.py b/parts/django/django/contrib/gis/geos/tests/test_geos.py
new file mode 100644
index 0000000..3cd021e
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/tests/test_geos.py
@@ -0,0 +1,926 @@
+import ctypes, random, unittest, sys
+from django.contrib.gis.geos import *
+from django.contrib.gis.geos.base import gdal, numpy, GEOSBase
+from django.contrib.gis.geometry.test_data import TestDataMixin
+
+class GEOSTest(unittest.TestCase, TestDataMixin):
+
+ @property
+ def null_srid(self):
+ """
+ Returns the proper null SRID depending on the GEOS version.
+ See the comments in `test15_srid` for more details.
+ """
+ info = geos_version_info()
+ if info['version'] == '3.0.0' and info['release_candidate']:
+ return -1
+ else:
+ return None
+
+ def test00_base(self):
+ "Tests out the GEOSBase class."
+ # Testing out GEOSBase class, which provides a `ptr` property
+ # that abstracts out access to underlying C pointers.
+ class FakeGeom1(GEOSBase):
+ pass
+
+ # This one only accepts pointers to floats
+ c_float_p = ctypes.POINTER(ctypes.c_float)
+ class FakeGeom2(GEOSBase):
+ ptr_type = c_float_p
+
+ # Default ptr_type is `c_void_p`.
+ fg1 = FakeGeom1()
+ # Default ptr_type is C float pointer
+ fg2 = FakeGeom2()
+
+ # These assignments are OK -- None is allowed because
+ # it's equivalent to the NULL pointer.
+ fg1.ptr = ctypes.c_void_p()
+ fg1.ptr = None
+ fg2.ptr = c_float_p(ctypes.c_float(5.23))
+ fg2.ptr = None
+
+ # Because pointers have been set to NULL, an exception should be
+ # raised when we try to access it. Raising an exception is
+ # preferrable to a segmentation fault that commonly occurs when
+ # a C method is given a NULL memory reference.
+ for fg in (fg1, fg2):
+ # Equivalent to `fg.ptr`
+ self.assertRaises(GEOSException, fg._get_ptr)
+
+ # Anything that is either not None or the acceptable pointer type will
+ # result in a TypeError when trying to assign it to the `ptr` property.
+ # Thus, memmory addresses (integers) and pointers of the incorrect type
+ # (in `bad_ptrs`) will not be allowed.
+ bad_ptrs = (5, ctypes.c_char_p('foobar'))
+ for bad_ptr in bad_ptrs:
+ # Equivalent to `fg.ptr = bad_ptr`
+ self.assertRaises(TypeError, fg1._set_ptr, bad_ptr)
+ self.assertRaises(TypeError, fg2._set_ptr, bad_ptr)
+
+ def test01a_wkt(self):
+ "Testing WKT output."
+ for g in self.geometries.wkt_out:
+ geom = fromstr(g.wkt)
+ self.assertEqual(g.ewkt, geom.wkt)
+
+ def test01b_hex(self):
+ "Testing HEX output."
+ for g in self.geometries.hex_wkt:
+ geom = fromstr(g.wkt)
+ self.assertEqual(g.hex, geom.hex)
+
+ def test01b_hexewkb(self):
+ "Testing (HEX)EWKB output."
+ from binascii import a2b_hex
+
+ # For testing HEX(EWKB).
+ ogc_hex = '01010000000000000000000000000000000000F03F'
+ # `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));`
+ hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F'
+ # `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));`
+ hexewkb_3d = '01010000A0E61000000000000000000000000000000000F03F0000000000000040'
+
+ pnt_2d = Point(0, 1, srid=4326)
+ pnt_3d = Point(0, 1, 2, srid=4326)
+
+ # OGC-compliant HEX will not have SRID nor Z value.
+ self.assertEqual(ogc_hex, pnt_2d.hex)
+ self.assertEqual(ogc_hex, pnt_3d.hex)
+
+ # HEXEWKB should be appropriate for its dimension -- have to use an
+ # a WKBWriter w/dimension set accordingly, else GEOS will insert
+ # garbage into 3D coordinate if there is none. Also, GEOS has a
+ # a bug in versions prior to 3.1 that puts the X coordinate in
+ # place of Z; an exception should be raised on those versions.
+ self.assertEqual(hexewkb_2d, pnt_2d.hexewkb)
+ if GEOS_PREPARE:
+ self.assertEqual(hexewkb_3d, pnt_3d.hexewkb)
+ self.assertEqual(True, GEOSGeometry(hexewkb_3d).hasz)
+ else:
+ try:
+ hexewkb = pnt_3d.hexewkb
+ except GEOSException:
+ pass
+ else:
+ self.fail('Should have raised GEOSException.')
+
+ # Same for EWKB.
+ self.assertEqual(buffer(a2b_hex(hexewkb_2d)), pnt_2d.ewkb)
+ if GEOS_PREPARE:
+ self.assertEqual(buffer(a2b_hex(hexewkb_3d)), pnt_3d.ewkb)
+ else:
+ try:
+ ewkb = pnt_3d.ewkb
+ except GEOSException:
+ pass
+ else:
+ self.fail('Should have raised GEOSException')
+
+ # Redundant sanity check.
+ self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
+
+ def test01c_kml(self):
+ "Testing KML output."
+ for tg in self.geometries.wkt_out:
+ geom = fromstr(tg.wkt)
+ kml = getattr(tg, 'kml', False)
+ if kml: self.assertEqual(kml, geom.kml)
+
+ def test01d_errors(self):
+ "Testing the Error handlers."
+ # string-based
+ print "\nBEGIN - expecting GEOS_ERROR; safe to ignore.\n"
+ for err in self.geometries.errors:
+ try:
+ g = fromstr(err.wkt)
+ except (GEOSException, ValueError):
+ pass
+
+ # Bad WKB
+ self.assertRaises(GEOSException, GEOSGeometry, buffer('0'))
+
+ print "\nEND - expecting GEOS_ERROR; safe to ignore.\n"
+
+ class NotAGeometry(object):
+ pass
+
+ # Some other object
+ self.assertRaises(TypeError, GEOSGeometry, NotAGeometry())
+ # None
+ self.assertRaises(TypeError, GEOSGeometry, None)
+
+ def test01e_wkb(self):
+ "Testing WKB output."
+ from binascii import b2a_hex
+ for g in self.geometries.hex_wkt:
+ geom = fromstr(g.wkt)
+ wkb = geom.wkb
+ self.assertEqual(b2a_hex(wkb).upper(), g.hex)
+
+ def test01f_create_hex(self):
+ "Testing creation from HEX."
+ for g in self.geometries.hex_wkt:
+ geom_h = GEOSGeometry(g.hex)
+ # we need to do this so decimal places get normalised
+ geom_t = fromstr(g.wkt)
+ self.assertEqual(geom_t.wkt, geom_h.wkt)
+
+ def test01g_create_wkb(self):
+ "Testing creation from WKB."
+ from binascii import a2b_hex
+ for g in self.geometries.hex_wkt:
+ wkb = buffer(a2b_hex(g.hex))
+ geom_h = GEOSGeometry(wkb)
+ # we need to do this so decimal places get normalised
+ geom_t = fromstr(g.wkt)
+ self.assertEqual(geom_t.wkt, geom_h.wkt)
+
+ def test01h_ewkt(self):
+ "Testing EWKT."
+ srid = 32140
+ for p in self.geometries.polygons:
+ ewkt = 'SRID=%d;%s' % (srid, p.wkt)
+ poly = fromstr(ewkt)
+ self.assertEqual(srid, poly.srid)
+ self.assertEqual(srid, poly.shell.srid)
+ self.assertEqual(srid, fromstr(poly.ewkt).srid) # Checking export
+
+ def test01i_json(self):
+ "Testing GeoJSON input/output (via GDAL)."
+ if not gdal or not gdal.GEOJSON: return
+ for g in self.geometries.json_geoms:
+ geom = GEOSGeometry(g.wkt)
+ if not hasattr(g, 'not_equal'):
+ self.assertEqual(g.json, geom.json)
+ self.assertEqual(g.json, geom.geojson)
+ self.assertEqual(GEOSGeometry(g.wkt), GEOSGeometry(geom.json))
+
+ def test01k_fromfile(self):
+ "Testing the fromfile() factory."
+ from StringIO import StringIO
+ ref_pnt = GEOSGeometry('POINT(5 23)')
+
+ wkt_f = StringIO()
+ wkt_f.write(ref_pnt.wkt)
+ wkb_f = StringIO()
+ wkb_f.write(str(ref_pnt.wkb))
+
+ # Other tests use `fromfile()` on string filenames so those
+ # aren't tested here.
+ for fh in (wkt_f, wkb_f):
+ fh.seek(0)
+ pnt = fromfile(fh)
+ self.assertEqual(ref_pnt, pnt)
+
+ def test01k_eq(self):
+ "Testing equivalence."
+ p = fromstr('POINT(5 23)')
+ self.assertEqual(p, p.wkt)
+ self.assertNotEqual(p, 'foo')
+ ls = fromstr('LINESTRING(0 0, 1 1, 5 5)')
+ self.assertEqual(ls, ls.wkt)
+ self.assertNotEqual(p, 'bar')
+ # Error shouldn't be raise on equivalence testing with
+ # an invalid type.
+ for g in (p, ls):
+ self.assertNotEqual(g, None)
+ self.assertNotEqual(g, {'foo' : 'bar'})
+ self.assertNotEqual(g, False)
+
+ def test02a_points(self):
+ "Testing Point objects."
+ prev = fromstr('POINT(0 0)')
+ for p in self.geometries.points:
+ # Creating the point from the WKT
+ pnt = fromstr(p.wkt)
+ self.assertEqual(pnt.geom_type, 'Point')
+ self.assertEqual(pnt.geom_typeid, 0)
+ self.assertEqual(p.x, pnt.x)
+ self.assertEqual(p.y, pnt.y)
+ self.assertEqual(True, pnt == fromstr(p.wkt))
+ self.assertEqual(False, pnt == prev)
+
+ # Making sure that the point's X, Y components are what we expect
+ self.assertAlmostEqual(p.x, pnt.tuple[0], 9)
+ self.assertAlmostEqual(p.y, pnt.tuple[1], 9)
+
+ # Testing the third dimension, and getting the tuple arguments
+ if hasattr(p, 'z'):
+ self.assertEqual(True, pnt.hasz)
+ self.assertEqual(p.z, pnt.z)
+ self.assertEqual(p.z, pnt.tuple[2], 9)
+ tup_args = (p.x, p.y, p.z)
+ set_tup1 = (2.71, 3.14, 5.23)
+ set_tup2 = (5.23, 2.71, 3.14)
+ else:
+ self.assertEqual(False, pnt.hasz)
+ self.assertEqual(None, pnt.z)
+ tup_args = (p.x, p.y)
+ set_tup1 = (2.71, 3.14)
+ set_tup2 = (3.14, 2.71)
+
+ # Centroid operation on point should be point itself
+ self.assertEqual(p.centroid, pnt.centroid.tuple)
+
+ # Now testing the different constructors
+ pnt2 = Point(tup_args) # e.g., Point((1, 2))
+ pnt3 = Point(*tup_args) # e.g., Point(1, 2)
+ self.assertEqual(True, pnt == pnt2)
+ self.assertEqual(True, pnt == pnt3)
+
+ # Now testing setting the x and y
+ pnt.y = 3.14
+ pnt.x = 2.71
+ self.assertEqual(3.14, pnt.y)
+ self.assertEqual(2.71, pnt.x)
+
+ # Setting via the tuple/coords property
+ pnt.tuple = set_tup1
+ self.assertEqual(set_tup1, pnt.tuple)
+ pnt.coords = set_tup2
+ self.assertEqual(set_tup2, pnt.coords)
+
+ prev = pnt # setting the previous geometry
+
+ def test02b_multipoints(self):
+ "Testing MultiPoint objects."
+ for mp in self.geometries.multipoints:
+ mpnt = fromstr(mp.wkt)
+ self.assertEqual(mpnt.geom_type, 'MultiPoint')
+ self.assertEqual(mpnt.geom_typeid, 4)
+
+ self.assertAlmostEqual(mp.centroid[0], mpnt.centroid.tuple[0], 9)
+ self.assertAlmostEqual(mp.centroid[1], mpnt.centroid.tuple[1], 9)
+
+ self.assertRaises(GEOSIndexError, mpnt.__getitem__, len(mpnt))
+ self.assertEqual(mp.centroid, mpnt.centroid.tuple)
+ self.assertEqual(mp.coords, tuple(m.tuple for m in mpnt))
+ for p in mpnt:
+ self.assertEqual(p.geom_type, 'Point')
+ self.assertEqual(p.geom_typeid, 0)
+ self.assertEqual(p.empty, False)
+ self.assertEqual(p.valid, True)
+
+ def test03a_linestring(self):
+ "Testing LineString objects."
+ prev = fromstr('POINT(0 0)')
+ for l in self.geometries.linestrings:
+ ls = fromstr(l.wkt)
+ self.assertEqual(ls.geom_type, 'LineString')
+ self.assertEqual(ls.geom_typeid, 1)
+ self.assertEqual(ls.empty, False)
+ self.assertEqual(ls.ring, False)
+ if hasattr(l, 'centroid'):
+ self.assertEqual(l.centroid, ls.centroid.tuple)
+ if hasattr(l, 'tup'):
+ self.assertEqual(l.tup, ls.tuple)
+
+ self.assertEqual(True, ls == fromstr(l.wkt))
+ self.assertEqual(False, ls == prev)
+ self.assertRaises(GEOSIndexError, ls.__getitem__, len(ls))
+ prev = ls
+
+ # Creating a LineString from a tuple, list, and numpy array
+ self.assertEqual(ls, LineString(ls.tuple)) # tuple
+ self.assertEqual(ls, LineString(*ls.tuple)) # as individual arguments
+ self.assertEqual(ls, LineString([list(tup) for tup in ls.tuple])) # as list
+ self.assertEqual(ls.wkt, LineString(*tuple(Point(tup) for tup in ls.tuple)).wkt) # Point individual arguments
+ if numpy: self.assertEqual(ls, LineString(numpy.array(ls.tuple))) # as numpy array
+
+ def test03b_multilinestring(self):
+ "Testing MultiLineString objects."
+ prev = fromstr('POINT(0 0)')
+ for l in self.geometries.multilinestrings:
+ ml = fromstr(l.wkt)
+ self.assertEqual(ml.geom_type, 'MultiLineString')
+ self.assertEqual(ml.geom_typeid, 5)
+
+ self.assertAlmostEqual(l.centroid[0], ml.centroid.x, 9)
+ self.assertAlmostEqual(l.centroid[1], ml.centroid.y, 9)
+
+ self.assertEqual(True, ml == fromstr(l.wkt))
+ self.assertEqual(False, ml == prev)
+ prev = ml
+
+ for ls in ml:
+ self.assertEqual(ls.geom_type, 'LineString')
+ self.assertEqual(ls.geom_typeid, 1)
+ self.assertEqual(ls.empty, False)
+
+ self.assertRaises(GEOSIndexError, ml.__getitem__, len(ml))
+ self.assertEqual(ml.wkt, MultiLineString(*tuple(s.clone() for s in ml)).wkt)
+ self.assertEqual(ml, MultiLineString(*tuple(LineString(s.tuple) for s in ml)))
+
+ def test04_linearring(self):
+ "Testing LinearRing objects."
+ for rr in self.geometries.linearrings:
+ lr = fromstr(rr.wkt)
+ self.assertEqual(lr.geom_type, 'LinearRing')
+ self.assertEqual(lr.geom_typeid, 2)
+ self.assertEqual(rr.n_p, len(lr))
+ self.assertEqual(True, lr.valid)
+ self.assertEqual(False, lr.empty)
+
+ # Creating a LinearRing from a tuple, list, and numpy array
+ self.assertEqual(lr, LinearRing(lr.tuple))
+ self.assertEqual(lr, LinearRing(*lr.tuple))
+ self.assertEqual(lr, LinearRing([list(tup) for tup in lr.tuple]))
+ if numpy: self.assertEqual(lr, LinearRing(numpy.array(lr.tuple)))
+
+ def test05a_polygons(self):
+ "Testing Polygon objects."
+
+ # Testing `from_bbox` class method
+ bbox = (-180, -90, 180, 90)
+ p = Polygon.from_bbox( bbox )
+ self.assertEqual(bbox, p.extent)
+
+ prev = fromstr('POINT(0 0)')
+ for p in self.geometries.polygons:
+ # Creating the Polygon, testing its properties.
+ poly = fromstr(p.wkt)
+ self.assertEqual(poly.geom_type, 'Polygon')
+ self.assertEqual(poly.geom_typeid, 3)
+ self.assertEqual(poly.empty, False)
+ self.assertEqual(poly.ring, False)
+ self.assertEqual(p.n_i, poly.num_interior_rings)
+ self.assertEqual(p.n_i + 1, len(poly)) # Testing __len__
+ self.assertEqual(p.n_p, poly.num_points)
+
+ # Area & Centroid
+ self.assertAlmostEqual(p.area, poly.area, 9)
+ self.assertAlmostEqual(p.centroid[0], poly.centroid.tuple[0], 9)
+ self.assertAlmostEqual(p.centroid[1], poly.centroid.tuple[1], 9)
+
+ # Testing the geometry equivalence
+ self.assertEqual(True, poly == fromstr(p.wkt))
+ self.assertEqual(False, poly == prev) # Should not be equal to previous geometry
+ self.assertEqual(True, poly != prev)
+
+ # Testing the exterior ring
+ ring = poly.exterior_ring
+ self.assertEqual(ring.geom_type, 'LinearRing')
+ self.assertEqual(ring.geom_typeid, 2)
+ if p.ext_ring_cs:
+ self.assertEqual(p.ext_ring_cs, ring.tuple)
+ self.assertEqual(p.ext_ring_cs, poly[0].tuple) # Testing __getitem__
+
+ # Testing __getitem__ and __setitem__ on invalid indices
+ self.assertRaises(GEOSIndexError, poly.__getitem__, len(poly))
+ self.assertRaises(GEOSIndexError, poly.__setitem__, len(poly), False)
+ self.assertRaises(GEOSIndexError, poly.__getitem__, -1 * len(poly) - 1)
+
+ # Testing __iter__
+ for r in poly:
+ self.assertEqual(r.geom_type, 'LinearRing')
+ self.assertEqual(r.geom_typeid, 2)
+
+ # Testing polygon construction.
+ self.assertRaises(TypeError, Polygon.__init__, 0, [1, 2, 3])
+ self.assertRaises(TypeError, Polygon.__init__, 'foo')
+
+ # Polygon(shell, (hole1, ... holeN))
+ rings = tuple(r for r in poly)
+ self.assertEqual(poly, Polygon(rings[0], rings[1:]))
+
+ # Polygon(shell_tuple, hole_tuple1, ... , hole_tupleN)
+ ring_tuples = tuple(r.tuple for r in poly)
+ self.assertEqual(poly, Polygon(*ring_tuples))
+
+ # Constructing with tuples of LinearRings.
+ self.assertEqual(poly.wkt, Polygon(*tuple(r for r in poly)).wkt)
+ self.assertEqual(poly.wkt, Polygon(*tuple(LinearRing(r.tuple) for r in poly)).wkt)
+
+ def test05b_multipolygons(self):
+ "Testing MultiPolygon objects."
+ print "\nBEGIN - expecting GEOS_NOTICE; safe to ignore.\n"
+ prev = fromstr('POINT (0 0)')
+ for mp in self.geometries.multipolygons:
+ mpoly = fromstr(mp.wkt)
+ self.assertEqual(mpoly.geom_type, 'MultiPolygon')
+ self.assertEqual(mpoly.geom_typeid, 6)
+ self.assertEqual(mp.valid, mpoly.valid)
+
+ if mp.valid:
+ self.assertEqual(mp.num_geom, mpoly.num_geom)
+ self.assertEqual(mp.n_p, mpoly.num_coords)
+ self.assertEqual(mp.num_geom, len(mpoly))
+ self.assertRaises(GEOSIndexError, mpoly.__getitem__, len(mpoly))
+ for p in mpoly:
+ self.assertEqual(p.geom_type, 'Polygon')
+ self.assertEqual(p.geom_typeid, 3)
+ self.assertEqual(p.valid, True)
+ self.assertEqual(mpoly.wkt, MultiPolygon(*tuple(poly.clone() for poly in mpoly)).wkt)
+
+ print "\nEND - expecting GEOS_NOTICE; safe to ignore.\n"
+
+ def test06a_memory_hijinks(self):
+ "Testing Geometry __del__() on rings and polygons."
+ #### Memory issues with rings and polygons
+
+ # These tests are needed to ensure sanity with writable geometries.
+
+ # Getting a polygon with interior rings, and pulling out the interior rings
+ poly = fromstr(self.geometries.polygons[1].wkt)
+ ring1 = poly[0]
+ ring2 = poly[1]
+
+ # These deletes should be 'harmless' since they are done on child geometries
+ del ring1
+ del ring2
+ ring1 = poly[0]
+ ring2 = poly[1]
+
+ # Deleting the polygon
+ del poly
+
+ # Access to these rings is OK since they are clones.
+ s1, s2 = str(ring1), str(ring2)
+
+ def test08_coord_seq(self):
+ "Testing Coordinate Sequence objects."
+ for p in self.geometries.polygons:
+ if p.ext_ring_cs:
+ # Constructing the polygon and getting the coordinate sequence
+ poly = fromstr(p.wkt)
+ cs = poly.exterior_ring.coord_seq
+
+ self.assertEqual(p.ext_ring_cs, cs.tuple) # done in the Polygon test too.
+ self.assertEqual(len(p.ext_ring_cs), len(cs)) # Making sure __len__ works
+
+ # Checks __getitem__ and __setitem__
+ for i in xrange(len(p.ext_ring_cs)):
+ c1 = p.ext_ring_cs[i] # Expected value
+ c2 = cs[i] # Value from coordseq
+ self.assertEqual(c1, c2)
+
+ # Constructing the test value to set the coordinate sequence with
+ if len(c1) == 2: tset = (5, 23)
+ else: tset = (5, 23, 8)
+ cs[i] = tset
+
+ # Making sure every set point matches what we expect
+ for j in range(len(tset)):
+ cs[i] = tset
+ self.assertEqual(tset[j], cs[i][j])
+
+ def test09_relate_pattern(self):
+ "Testing relate() and relate_pattern()."
+ g = fromstr('POINT (0 0)')
+ self.assertRaises(GEOSException, g.relate_pattern, 0, 'invalid pattern, yo')
+ for rg in self.geometries.relate_geoms:
+ a = fromstr(rg.wkt_a)
+ b = fromstr(rg.wkt_b)
+ self.assertEqual(rg.result, a.relate_pattern(b, rg.pattern))
+ self.assertEqual(rg.pattern, a.relate(b))
+
+ def test10_intersection(self):
+ "Testing intersects() and intersection()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = fromstr(self.geometries.topology_geoms[i].wkt_a)
+ b = fromstr(self.geometries.topology_geoms[i].wkt_b)
+ i1 = fromstr(self.geometries.intersect_geoms[i].wkt)
+ self.assertEqual(True, a.intersects(b))
+ i2 = a.intersection(b)
+ self.assertEqual(i1, i2)
+ self.assertEqual(i1, a & b) # __and__ is intersection operator
+ a &= b # testing __iand__
+ self.assertEqual(i1, a)
+
+ def test11_union(self):
+ "Testing union()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = fromstr(self.geometries.topology_geoms[i].wkt_a)
+ b = fromstr(self.geometries.topology_geoms[i].wkt_b)
+ u1 = fromstr(self.geometries.union_geoms[i].wkt)
+ u2 = a.union(b)
+ self.assertEqual(u1, u2)
+ self.assertEqual(u1, a | b) # __or__ is union operator
+ a |= b # testing __ior__
+ self.assertEqual(u1, a)
+
+ def test12_difference(self):
+ "Testing difference()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = fromstr(self.geometries.topology_geoms[i].wkt_a)
+ b = fromstr(self.geometries.topology_geoms[i].wkt_b)
+ d1 = fromstr(self.geometries.diff_geoms[i].wkt)
+ d2 = a.difference(b)
+ self.assertEqual(d1, d2)
+ self.assertEqual(d1, a - b) # __sub__ is difference operator
+ a -= b # testing __isub__
+ self.assertEqual(d1, a)
+
+ def test13_symdifference(self):
+ "Testing sym_difference()."
+ for i in xrange(len(self.geometries.topology_geoms)):
+ a = fromstr(self.geometries.topology_geoms[i].wkt_a)
+ b = fromstr(self.geometries.topology_geoms[i].wkt_b)
+ d1 = fromstr(self.geometries.sdiff_geoms[i].wkt)
+ d2 = a.sym_difference(b)
+ self.assertEqual(d1, d2)
+ self.assertEqual(d1, a ^ b) # __xor__ is symmetric difference operator
+ a ^= b # testing __ixor__
+ self.assertEqual(d1, a)
+
+ def test14_buffer(self):
+ "Testing buffer()."
+ for bg in self.geometries.buffer_geoms:
+ g = fromstr(bg.wkt)
+
+ # The buffer we expect
+ exp_buf = fromstr(bg.buffer_wkt)
+ quadsegs = bg.quadsegs
+ width = bg.width
+
+ # Can't use a floating-point for the number of quadsegs.
+ self.assertRaises(ctypes.ArgumentError, g.buffer, width, float(quadsegs))
+
+ # Constructing our buffer
+ buf = g.buffer(width, quadsegs)
+ self.assertEqual(exp_buf.num_coords, buf.num_coords)
+ self.assertEqual(len(exp_buf), len(buf))
+
+ # Now assuring that each point in the buffer is almost equal
+ for j in xrange(len(exp_buf)):
+ exp_ring = exp_buf[j]
+ buf_ring = buf[j]
+ self.assertEqual(len(exp_ring), len(buf_ring))
+ for k in xrange(len(exp_ring)):
+ # Asserting the X, Y of each point are almost equal (due to floating point imprecision)
+ self.assertAlmostEqual(exp_ring[k][0], buf_ring[k][0], 9)
+ self.assertAlmostEqual(exp_ring[k][1], buf_ring[k][1], 9)
+
+ def test15_srid(self):
+ "Testing the SRID property and keyword."
+ # Testing SRID keyword on Point
+ pnt = Point(5, 23, srid=4326)
+ self.assertEqual(4326, pnt.srid)
+ pnt.srid = 3084
+ self.assertEqual(3084, pnt.srid)
+ self.assertRaises(ctypes.ArgumentError, pnt.set_srid, '4326')
+
+ # Testing SRID keyword on fromstr(), and on Polygon rings.
+ poly = fromstr(self.geometries.polygons[1].wkt, srid=4269)
+ self.assertEqual(4269, poly.srid)
+ for ring in poly: self.assertEqual(4269, ring.srid)
+ poly.srid = 4326
+ self.assertEqual(4326, poly.shell.srid)
+
+ # Testing SRID keyword on GeometryCollection
+ gc = GeometryCollection(Point(5, 23), LineString((0, 0), (1.5, 1.5), (3, 3)), srid=32021)
+ self.assertEqual(32021, gc.srid)
+ for i in range(len(gc)): self.assertEqual(32021, gc[i].srid)
+
+ # GEOS may get the SRID from HEXEWKB
+ # 'POINT(5 23)' at SRID=4326 in hex form -- obtained from PostGIS
+ # using `SELECT GeomFromText('POINT (5 23)', 4326);`.
+ hex = '0101000020E610000000000000000014400000000000003740'
+ p1 = fromstr(hex)
+ self.assertEqual(4326, p1.srid)
+
+ # In GEOS 3.0.0rc1-4 when the EWKB and/or HEXEWKB is exported,
+ # the SRID information is lost and set to -1 -- this is not a
+ # problem on the 3.0.0 version (another reason to upgrade).
+ exp_srid = self.null_srid
+
+ p2 = fromstr(p1.hex)
+ self.assertEqual(exp_srid, p2.srid)
+ p3 = fromstr(p1.hex, srid=-1) # -1 is intended.
+ self.assertEqual(-1, p3.srid)
+
+ def test16_mutable_geometries(self):
+ "Testing the mutability of Polygons and Geometry Collections."
+ ### Testing the mutability of Polygons ###
+ for p in self.geometries.polygons:
+ poly = fromstr(p.wkt)
+
+ # Should only be able to use __setitem__ with LinearRing geometries.
+ self.assertRaises(TypeError, poly.__setitem__, 0, LineString((1, 1), (2, 2)))
+
+ # Constructing the new shell by adding 500 to every point in the old shell.
+ shell_tup = poly.shell.tuple
+ new_coords = []
+ for point in shell_tup: new_coords.append((point[0] + 500., point[1] + 500.))
+ new_shell = LinearRing(*tuple(new_coords))
+
+ # Assigning polygon's exterior ring w/the new shell
+ poly.exterior_ring = new_shell
+ s = str(new_shell) # new shell is still accessible
+ self.assertEqual(poly.exterior_ring, new_shell)
+ self.assertEqual(poly[0], new_shell)
+
+ ### Testing the mutability of Geometry Collections
+ for tg in self.geometries.multipoints:
+ mp = fromstr(tg.wkt)
+ for i in range(len(mp)):
+ # Creating a random point.
+ pnt = mp[i]
+ new = Point(random.randint(1, 100), random.randint(1, 100))
+ # Testing the assignment
+ mp[i] = new
+ s = str(new) # what was used for the assignment is still accessible
+ self.assertEqual(mp[i], new)
+ self.assertEqual(mp[i].wkt, new.wkt)
+ self.assertNotEqual(pnt, mp[i])
+
+ # MultiPolygons involve much more memory management because each
+ # Polygon w/in the collection has its own rings.
+ for tg in self.geometries.multipolygons:
+ mpoly = fromstr(tg.wkt)
+ for i in xrange(len(mpoly)):
+ poly = mpoly[i]
+ old_poly = mpoly[i]
+ # Offsetting the each ring in the polygon by 500.
+ for j in xrange(len(poly)):
+ r = poly[j]
+ for k in xrange(len(r)): r[k] = (r[k][0] + 500., r[k][1] + 500.)
+ poly[j] = r
+
+ self.assertNotEqual(mpoly[i], poly)
+ # Testing the assignment
+ mpoly[i] = poly
+ s = str(poly) # Still accessible
+ self.assertEqual(mpoly[i], poly)
+ self.assertNotEqual(mpoly[i], old_poly)
+
+ # Extreme (!!) __setitem__ -- no longer works, have to detect
+ # in the first object that __setitem__ is called in the subsequent
+ # objects -- maybe mpoly[0, 0, 0] = (3.14, 2.71)?
+ #mpoly[0][0][0] = (3.14, 2.71)
+ #self.assertEqual((3.14, 2.71), mpoly[0][0][0])
+ # Doing it more slowly..
+ #self.assertEqual((3.14, 2.71), mpoly[0].shell[0])
+ #del mpoly
+
+ def test17_threed(self):
+ "Testing three-dimensional geometries."
+ # Testing a 3D Point
+ pnt = Point(2, 3, 8)
+ self.assertEqual((2.,3.,8.), pnt.coords)
+ self.assertRaises(TypeError, pnt.set_coords, (1.,2.))
+ pnt.coords = (1.,2.,3.)
+ self.assertEqual((1.,2.,3.), pnt.coords)
+
+ # Testing a 3D LineString
+ ls = LineString((2., 3., 8.), (50., 250., -117.))
+ self.assertEqual(((2.,3.,8.), (50.,250.,-117.)), ls.tuple)
+ self.assertRaises(TypeError, ls.__setitem__, 0, (1.,2.))
+ ls[0] = (1.,2.,3.)
+ self.assertEqual((1.,2.,3.), ls[0])
+
+ def test18_distance(self):
+ "Testing the distance() function."
+ # Distance to self should be 0.
+ pnt = Point(0, 0)
+ self.assertEqual(0.0, pnt.distance(Point(0, 0)))
+
+ # Distance should be 1
+ self.assertEqual(1.0, pnt.distance(Point(0, 1)))
+
+ # Distance should be ~ sqrt(2)
+ self.assertAlmostEqual(1.41421356237, pnt.distance(Point(1, 1)), 11)
+
+ # Distances are from the closest vertex in each geometry --
+ # should be 3 (distance from (2, 2) to (5, 2)).
+ ls1 = LineString((0, 0), (1, 1), (2, 2))
+ ls2 = LineString((5, 2), (6, 1), (7, 0))
+ self.assertEqual(3, ls1.distance(ls2))
+
+ def test19_length(self):
+ "Testing the length property."
+ # Points have 0 length.
+ pnt = Point(0, 0)
+ self.assertEqual(0.0, pnt.length)
+
+ # Should be ~ sqrt(2)
+ ls = LineString((0, 0), (1, 1))
+ self.assertAlmostEqual(1.41421356237, ls.length, 11)
+
+ # Should be circumfrence of Polygon
+ poly = Polygon(LinearRing((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
+ self.assertEqual(4.0, poly.length)
+
+ # Should be sum of each element's length in collection.
+ mpoly = MultiPolygon(poly.clone(), poly)
+ self.assertEqual(8.0, mpoly.length)
+
+ def test20a_emptyCollections(self):
+ "Testing empty geometries and collections."
+ gc1 = GeometryCollection([])
+ gc2 = fromstr('GEOMETRYCOLLECTION EMPTY')
+ pnt = fromstr('POINT EMPTY')
+ ls = fromstr('LINESTRING EMPTY')
+ poly = fromstr('POLYGON EMPTY')
+ mls = fromstr('MULTILINESTRING EMPTY')
+ mpoly1 = fromstr('MULTIPOLYGON EMPTY')
+ mpoly2 = MultiPolygon(())
+
+ for g in [gc1, gc2, pnt, ls, poly, mls, mpoly1, mpoly2]:
+ self.assertEqual(True, g.empty)
+
+ # Testing len() and num_geom.
+ if isinstance(g, Polygon):
+ self.assertEqual(1, len(g)) # Has one empty linear ring
+ self.assertEqual(1, g.num_geom)
+ self.assertEqual(0, len(g[0]))
+ elif isinstance(g, (Point, LineString)):
+ self.assertEqual(1, g.num_geom)
+ self.assertEqual(0, len(g))
+ else:
+ self.assertEqual(0, g.num_geom)
+ self.assertEqual(0, len(g))
+
+ # Testing __getitem__ (doesn't work on Point or Polygon)
+ if isinstance(g, Point):
+ self.assertRaises(GEOSIndexError, g.get_x)
+ elif isinstance(g, Polygon):
+ lr = g.shell
+ self.assertEqual('LINEARRING EMPTY', lr.wkt)
+ self.assertEqual(0, len(lr))
+ self.assertEqual(True, lr.empty)
+ self.assertRaises(GEOSIndexError, lr.__getitem__, 0)
+ else:
+ self.assertRaises(GEOSIndexError, g.__getitem__, 0)
+
+ def test20b_collections_of_collections(self):
+ "Testing GeometryCollection handling of other collections."
+ # Creating a GeometryCollection WKT string composed of other
+ # collections and polygons.
+ coll = [mp.wkt for mp in self.geometries.multipolygons if mp.valid]
+ coll.extend([mls.wkt for mls in self.geometries.multilinestrings])
+ coll.extend([p.wkt for p in self.geometries.polygons])
+ coll.extend([mp.wkt for mp in self.geometries.multipoints])
+ gc_wkt = 'GEOMETRYCOLLECTION(%s)' % ','.join(coll)
+
+ # Should construct ok from WKT
+ gc1 = GEOSGeometry(gc_wkt)
+
+ # Should also construct ok from individual geometry arguments.
+ gc2 = GeometryCollection(*tuple(g for g in gc1))
+
+ # And, they should be equal.
+ self.assertEqual(gc1, gc2)
+
+ def test21_test_gdal(self):
+ "Testing `ogr` and `srs` properties."
+ if not gdal.HAS_GDAL: return
+ g1 = fromstr('POINT(5 23)')
+ self.assertEqual(True, isinstance(g1.ogr, gdal.OGRGeometry))
+ self.assertEqual(g1.srs, None)
+
+ g2 = fromstr('LINESTRING(0 0, 5 5, 23 23)', srid=4326)
+ self.assertEqual(True, isinstance(g2.ogr, gdal.OGRGeometry))
+ self.assertEqual(True, isinstance(g2.srs, gdal.SpatialReference))
+ self.assertEqual(g2.hex, g2.ogr.hex)
+ self.assertEqual('WGS 84', g2.srs.name)
+
+ def test22_copy(self):
+ "Testing use with the Python `copy` module."
+ import django.utils.copycompat as copy
+ poly = GEOSGeometry('POLYGON((0 0, 0 23, 23 23, 23 0, 0 0), (5 5, 5 10, 10 10, 10 5, 5 5))')
+ cpy1 = copy.copy(poly)
+ cpy2 = copy.deepcopy(poly)
+ self.assertNotEqual(poly._ptr, cpy1._ptr)
+ self.assertNotEqual(poly._ptr, cpy2._ptr)
+
+ def test23_transform(self):
+ "Testing `transform` method."
+ if not gdal.HAS_GDAL: return
+ orig = GEOSGeometry('POINT (-104.609 38.255)', 4326)
+ trans = GEOSGeometry('POINT (992385.4472045 481455.4944650)', 2774)
+
+ # Using a srid, a SpatialReference object, and a CoordTransform object
+ # for transformations.
+ t1, t2, t3 = orig.clone(), orig.clone(), orig.clone()
+ t1.transform(trans.srid)
+ t2.transform(gdal.SpatialReference('EPSG:2774'))
+ ct = gdal.CoordTransform(gdal.SpatialReference('WGS84'), gdal.SpatialReference(2774))
+ t3.transform(ct)
+
+ # Testing use of the `clone` keyword.
+ k1 = orig.clone()
+ k2 = k1.transform(trans.srid, clone=True)
+ self.assertEqual(k1, orig)
+ self.assertNotEqual(k1, k2)
+
+ prec = 3
+ for p in (t1, t2, t3, k2):
+ self.assertAlmostEqual(trans.x, p.x, prec)
+ self.assertAlmostEqual(trans.y, p.y, prec)
+
+ def test24_extent(self):
+ "Testing `extent` method."
+ # The xmin, ymin, xmax, ymax of the MultiPoint should be returned.
+ mp = MultiPoint(Point(5, 23), Point(0, 0), Point(10, 50))
+ self.assertEqual((0.0, 0.0, 10.0, 50.0), mp.extent)
+ pnt = Point(5.23, 17.8)
+ # Extent of points is just the point itself repeated.
+ self.assertEqual((5.23, 17.8, 5.23, 17.8), pnt.extent)
+ # Testing on the 'real world' Polygon.
+ poly = fromstr(self.geometries.polygons[3].wkt)
+ ring = poly.shell
+ x, y = ring.x, ring.y
+ xmin, ymin = min(x), min(y)
+ xmax, ymax = max(x), max(y)
+ self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
+
+ def test25_pickle(self):
+ "Testing pickling and unpickling support."
+ # Using both pickle and cPickle -- just 'cause.
+ import pickle, cPickle
+
+ # Creating a list of test geometries for pickling,
+ # and setting the SRID on some of them.
+ def get_geoms(lst, srid=None):
+ return [GEOSGeometry(tg.wkt, srid) for tg in lst]
+ tgeoms = get_geoms(self.geometries.points)
+ tgeoms.extend(get_geoms(self.geometries.multilinestrings, 4326))
+ tgeoms.extend(get_geoms(self.geometries.polygons, 3084))
+ tgeoms.extend(get_geoms(self.geometries.multipolygons, 900913))
+
+ # The SRID won't be exported in GEOS 3.0 release candidates.
+ no_srid = self.null_srid == -1
+ for geom in tgeoms:
+ s1, s2 = cPickle.dumps(geom), pickle.dumps(geom)
+ g1, g2 = cPickle.loads(s1), pickle.loads(s2)
+ for tmpg in (g1, g2):
+ self.assertEqual(geom, tmpg)
+ if not no_srid: self.assertEqual(geom.srid, tmpg.srid)
+
+ def test26_prepared(self):
+ "Testing PreparedGeometry support."
+ if not GEOS_PREPARE: return
+ # Creating a simple multipolygon and getting a prepared version.
+ mpoly = GEOSGeometry('MULTIPOLYGON(((0 0,0 5,5 5,5 0,0 0)),((5 5,5 10,10 10,10 5,5 5)))')
+ prep = mpoly.prepared
+
+ # A set of test points.
+ pnts = [Point(5, 5), Point(7.5, 7.5), Point(2.5, 7.5)]
+ covers = [True, True, False] # No `covers` op for regular GEOS geoms.
+ for pnt, c in zip(pnts, covers):
+ # Results should be the same (but faster)
+ self.assertEqual(mpoly.contains(pnt), prep.contains(pnt))
+ self.assertEqual(mpoly.intersects(pnt), prep.intersects(pnt))
+ self.assertEqual(c, prep.covers(pnt))
+
+ def test26_line_merge(self):
+ "Testing line merge support"
+ ref_geoms = (fromstr('LINESTRING(1 1, 1 1, 3 3)'),
+ fromstr('MULTILINESTRING((1 1, 3 3), (3 3, 4 2))'),
+ )
+ ref_merged = (fromstr('LINESTRING(1 1, 3 3)'),
+ fromstr('LINESTRING (1 1, 3 3, 4 2)'),
+ )
+ for geom, merged in zip(ref_geoms, ref_merged):
+ self.assertEqual(merged, geom.merged)
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(GEOSTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/geos/tests/test_geos_mutation.py b/parts/django/django/contrib/gis/geos/tests/test_geos_mutation.py
new file mode 100644
index 0000000..28f484d
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/tests/test_geos_mutation.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2008-2009 Aryeh Leib Taurog, all rights reserved.
+# Modified from original contribution by Aryeh Leib Taurog, which was
+# released under the New BSD license.
+import unittest
+
+import django.utils.copycompat as copy
+
+from django.contrib.gis.geos import *
+from django.contrib.gis.geos.error import GEOSIndexError
+
+def getItem(o,i): return o[i]
+def delItem(o,i): del o[i]
+def setItem(o,i,v): o[i] = v
+
+def api_get_distance(x): return x.distance(Point(-200,-200))
+def api_get_buffer(x): return x.buffer(10)
+def api_get_geom_typeid(x): return x.geom_typeid
+def api_get_num_coords(x): return x.num_coords
+def api_get_centroid(x): return x.centroid
+def api_get_empty(x): return x.empty
+def api_get_valid(x): return x.valid
+def api_get_simple(x): return x.simple
+def api_get_ring(x): return x.ring
+def api_get_boundary(x): return x.boundary
+def api_get_convex_hull(x): return x.convex_hull
+def api_get_extent(x): return x.extent
+def api_get_area(x): return x.area
+def api_get_length(x): return x.length
+
+geos_function_tests = [ val for name, val in vars().items()
+ if hasattr(val, '__call__')
+ and name.startswith('api_get_') ]
+
+class GEOSMutationTest(unittest.TestCase):
+ """
+ Tests Pythonic Mutability of Python GEOS geometry wrappers
+ get/set/delitem on a slice, normal list methods
+ """
+
+ def test00_GEOSIndexException(self):
+ 'Testing Geometry GEOSIndexError'
+ p = Point(1,2)
+ for i in range(-2,2): p._checkindex(i)
+ self.assertRaises(GEOSIndexError, p._checkindex, 2)
+ self.assertRaises(GEOSIndexError, p._checkindex, -3)
+
+ def test01_PointMutations(self):
+ 'Testing Point mutations'
+ for p in (Point(1,2,3), fromstr('POINT (1 2 3)')):
+ self.assertEqual(p._get_single_external(1), 2.0, 'Point _get_single_external')
+
+ # _set_single
+ p._set_single(0,100)
+ self.assertEqual(p.coords, (100.0,2.0,3.0), 'Point _set_single')
+
+ # _set_list
+ p._set_list(2,(50,3141))
+ self.assertEqual(p.coords, (50.0,3141.0), 'Point _set_list')
+
+ def test02_PointExceptions(self):
+ 'Testing Point exceptions'
+ self.assertRaises(TypeError, Point, range(1))
+ self.assertRaises(TypeError, Point, range(4))
+
+ def test03_PointApi(self):
+ 'Testing Point API'
+ q = Point(4,5,3)
+ for p in (Point(1,2,3), fromstr('POINT (1 2 3)')):
+ p[0:2] = [4,5]
+ for f in geos_function_tests:
+ self.assertEqual(f(q), f(p), 'Point ' + f.__name__)
+
+ def test04_LineStringMutations(self):
+ 'Testing LineString mutations'
+ for ls in (LineString((1,0),(4,1),(6,-1)),
+ fromstr('LINESTRING (1 0,4 1,6 -1)')):
+ self.assertEqual(ls._get_single_external(1), (4.0,1.0), 'LineString _get_single_external')
+
+ # _set_single
+ ls._set_single(0,(-50,25))
+ self.assertEqual(ls.coords, ((-50.0,25.0),(4.0,1.0),(6.0,-1.0)), 'LineString _set_single')
+
+ # _set_list
+ ls._set_list(2, ((-50.0,25.0),(6.0,-1.0)))
+ self.assertEqual(ls.coords, ((-50.0,25.0),(6.0,-1.0)), 'LineString _set_list')
+
+ lsa = LineString(ls.coords)
+ for f in geos_function_tests:
+ self.assertEqual(f(lsa), f(ls), 'LineString ' + f.__name__)
+
+ def test05_Polygon(self):
+ 'Testing Polygon mutations'
+ for pg in (Polygon(((1,0),(4,1),(6,-1),(8,10),(1,0)),
+ ((5,4),(6,4),(6,3),(5,4))),
+ fromstr('POLYGON ((1 0,4 1,6 -1,8 10,1 0),(5 4,6 4,6 3,5 4))')):
+ self.assertEqual(pg._get_single_external(0),
+ LinearRing((1,0),(4,1),(6,-1),(8,10),(1,0)),
+ 'Polygon _get_single_external(0)')
+ self.assertEqual(pg._get_single_external(1),
+ LinearRing((5,4),(6,4),(6,3),(5,4)),
+ 'Polygon _get_single_external(1)')
+
+ # _set_list
+ pg._set_list(2, (((1,2),(10,0),(12,9),(-1,15),(1,2)),
+ ((4,2),(5,2),(5,3),(4,2))))
+ self.assertEqual(pg.coords,
+ (((1.0,2.0),(10.0,0.0),(12.0,9.0),(-1.0,15.0),(1.0,2.0)),
+ ((4.0,2.0),(5.0,2.0),(5.0,3.0),(4.0,2.0))),
+ 'Polygon _set_list')
+
+ lsa = Polygon(*pg.coords)
+ for f in geos_function_tests:
+ self.assertEqual(f(lsa), f(pg), 'Polygon ' + f.__name__)
+
+ def test06_Collection(self):
+ 'Testing Collection mutations'
+ for mp in (MultiPoint(*map(Point,((3,4),(-1,2),(5,-4),(2,8)))),
+ fromstr('MULTIPOINT (3 4,-1 2,5 -4,2 8)')):
+ self.assertEqual(mp._get_single_external(2), Point(5,-4), 'Collection _get_single_external')
+
+ mp._set_list(3, map(Point,((5,5),(3,-2),(8,1))))
+ self.assertEqual(mp.coords, ((5.0,5.0),(3.0,-2.0),(8.0,1.0)), 'Collection _set_list')
+
+ lsa = MultiPoint(*map(Point,((5,5),(3,-2),(8,1))))
+ for f in geos_function_tests:
+ self.assertEqual(f(lsa), f(mp), 'MultiPoint ' + f.__name__)
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(GEOSMutationTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
+
+if __name__ == '__main__':
+ run()
diff --git a/parts/django/django/contrib/gis/geos/tests/test_io.py b/parts/django/django/contrib/gis/geos/tests/test_io.py
new file mode 100644
index 0000000..cc0f1ed
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/tests/test_io.py
@@ -0,0 +1,112 @@
+import binascii, ctypes, unittest
+from django.contrib.gis.geos import GEOSGeometry, WKTReader, WKTWriter, WKBReader, WKBWriter, geos_version_info
+
+class GEOSIOTest(unittest.TestCase):
+
+ def test01_wktreader(self):
+ # Creating a WKTReader instance
+ wkt_r = WKTReader()
+ wkt = 'POINT (5 23)'
+
+ # read() should return a GEOSGeometry
+ ref = GEOSGeometry(wkt)
+ g1 = wkt_r.read(wkt)
+ g2 = wkt_r.read(unicode(wkt))
+
+ for geom in (g1, g2):
+ self.assertEqual(ref, geom)
+
+ # Should only accept basestring objects.
+ self.assertRaises(TypeError, wkt_r.read, 1)
+ self.assertRaises(TypeError, wkt_r.read, buffer('foo'))
+
+ def test02_wktwriter(self):
+ # Creating a WKTWriter instance, testing its ptr property.
+ wkt_w = WKTWriter()
+ self.assertRaises(TypeError, wkt_w._set_ptr, WKTReader.ptr_type())
+
+ ref = GEOSGeometry('POINT (5 23)')
+ ref_wkt = 'POINT (5.0000000000000000 23.0000000000000000)'
+ self.assertEqual(ref_wkt, wkt_w.write(ref))
+
+ def test03_wkbreader(self):
+ # Creating a WKBReader instance
+ wkb_r = WKBReader()
+
+ hex = '000000000140140000000000004037000000000000'
+ wkb = buffer(binascii.a2b_hex(hex))
+ ref = GEOSGeometry(hex)
+
+ # read() should return a GEOSGeometry on either a hex string or
+ # a WKB buffer.
+ g1 = wkb_r.read(wkb)
+ g2 = wkb_r.read(hex)
+ for geom in (g1, g2):
+ self.assertEqual(ref, geom)
+
+ bad_input = (1, 5.23, None, False)
+ for bad_wkb in bad_input:
+ self.assertRaises(TypeError, wkb_r.read, bad_wkb)
+
+ def test04_wkbwriter(self):
+ wkb_w = WKBWriter()
+
+ # Representations of 'POINT (5 23)' in hex -- one normal and
+ # the other with the byte order changed.
+ g = GEOSGeometry('POINT (5 23)')
+ hex1 = '010100000000000000000014400000000000003740'
+ wkb1 = buffer(binascii.a2b_hex(hex1))
+ hex2 = '000000000140140000000000004037000000000000'
+ wkb2 = buffer(binascii.a2b_hex(hex2))
+
+ self.assertEqual(hex1, wkb_w.write_hex(g))
+ self.assertEqual(wkb1, wkb_w.write(g))
+
+ # Ensuring bad byteorders are not accepted.
+ for bad_byteorder in (-1, 2, 523, 'foo', None):
+ # Equivalent of `wkb_w.byteorder = bad_byteorder`
+ self.assertRaises(ValueError, wkb_w._set_byteorder, bad_byteorder)
+
+ # Setting the byteorder to 0 (for Big Endian)
+ wkb_w.byteorder = 0
+ self.assertEqual(hex2, wkb_w.write_hex(g))
+ self.assertEqual(wkb2, wkb_w.write(g))
+
+ # Back to Little Endian
+ wkb_w.byteorder = 1
+
+ # Now, trying out the 3D and SRID flags.
+ g = GEOSGeometry('POINT (5 23 17)')
+ g.srid = 4326
+
+ hex3d = '0101000080000000000000144000000000000037400000000000003140'
+ wkb3d = buffer(binascii.a2b_hex(hex3d))
+ hex3d_srid = '01010000A0E6100000000000000000144000000000000037400000000000003140'
+ wkb3d_srid = buffer(binascii.a2b_hex(hex3d_srid))
+
+ # Ensuring bad output dimensions are not accepted
+ for bad_outdim in (-1, 0, 1, 4, 423, 'foo', None):
+ # Equivalent of `wkb_w.outdim = bad_outdim`
+ self.assertRaises(ValueError, wkb_w._set_outdim, bad_outdim)
+
+ # These tests will fail on 3.0.0 because of a bug that was fixed in 3.1:
+ # http://trac.osgeo.org/geos/ticket/216
+ if not geos_version_info()['version'].startswith('3.0.'):
+ # Now setting the output dimensions to be 3
+ wkb_w.outdim = 3
+
+ self.assertEqual(hex3d, wkb_w.write_hex(g))
+ self.assertEqual(wkb3d, wkb_w.write(g))
+
+ # Telling the WKBWriter to inlcude the srid in the representation.
+ wkb_w.srid = True
+ self.assertEqual(hex3d_srid, wkb_w.write_hex(g))
+ self.assertEqual(wkb3d_srid, wkb_w.write(g))
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(GEOSIOTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/geos/tests/test_mutable_list.py b/parts/django/django/contrib/gis/geos/tests/test_mutable_list.py
new file mode 100644
index 0000000..ebbe8ff
--- /dev/null
+++ b/parts/django/django/contrib/gis/geos/tests/test_mutable_list.py
@@ -0,0 +1,398 @@
+# Copyright (c) 2008-2009 Aryeh Leib Taurog, http://www.aryehleib.com
+# All rights reserved.
+#
+# Modified from original contribution by Aryeh Leib Taurog, which was
+# released under the New BSD license.
+import unittest
+from django.contrib.gis.geos.mutable_list import ListMixin
+
+class UserListA(ListMixin):
+ _mytype = tuple
+ def __init__(self, i_list, *args, **kwargs):
+ self._list = self._mytype(i_list)
+ super(UserListA, self).__init__(*args, **kwargs)
+
+ def __len__(self): return len(self._list)
+
+ def __str__(self): return str(self._list)
+
+ def __repr__(self): return repr(self._list)
+
+ def _set_list(self, length, items):
+ # this would work:
+ # self._list = self._mytype(items)
+ # but then we wouldn't be testing length parameter
+ itemList = ['x'] * length
+ for i, v in enumerate(items):
+ itemList[i] = v
+
+ self._list = self._mytype(itemList)
+
+ def _get_single_external(self, index):
+ return self._list[index]
+
+class UserListB(UserListA):
+ _mytype = list
+
+ def _set_single(self, index, value):
+ self._list[index] = value
+
+def nextRange(length):
+ nextRange.start += 100
+ return range(nextRange.start, nextRange.start + length)
+
+nextRange.start = 0
+
+class ListMixinTest(unittest.TestCase):
+ """
+ Tests base class ListMixin by comparing a list clone which is
+ a ListMixin subclass with a real Python list.
+ """
+ limit = 3
+ listType = UserListA
+
+ def lists_of_len(self, length=None):
+ if length is None: length = self.limit
+ pl = range(length)
+ return pl, self.listType(pl)
+
+ def limits_plus(self, b):
+ return range(-self.limit - b, self.limit + b)
+
+ def step_range(self):
+ return range(-1 - self.limit, 0) + range(1, 1 + self.limit)
+
+ def test01_getslice(self):
+ 'Slice retrieval'
+ pl, ul = self.lists_of_len()
+ for i in self.limits_plus(1):
+ self.assertEqual(pl[i:], ul[i:], 'slice [%d:]' % (i))
+ self.assertEqual(pl[:i], ul[:i], 'slice [:%d]' % (i))
+
+ for j in self.limits_plus(1):
+ self.assertEqual(pl[i:j], ul[i:j], 'slice [%d:%d]' % (i,j))
+ for k in self.step_range():
+ self.assertEqual(pl[i:j:k], ul[i:j:k], 'slice [%d:%d:%d]' % (i,j,k))
+
+ for k in self.step_range():
+ self.assertEqual(pl[i::k], ul[i::k], 'slice [%d::%d]' % (i,k))
+ self.assertEqual(pl[:i:k], ul[:i:k], 'slice [:%d:%d]' % (i,k))
+
+ for k in self.step_range():
+ self.assertEqual(pl[::k], ul[::k], 'slice [::%d]' % (k))
+
+ def test02_setslice(self):
+ 'Slice assignment'
+ def setfcn(x,i,j,k,L): x[i:j:k] = range(L)
+ pl, ul = self.lists_of_len()
+ for slen in range(self.limit + 1):
+ ssl = nextRange(slen)
+ ul[:] = ssl
+ pl[:] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [:]')
+
+ for i in self.limits_plus(1):
+ ssl = nextRange(slen)
+ ul[i:] = ssl
+ pl[i:] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [%d:]' % (i))
+
+ ssl = nextRange(slen)
+ ul[:i] = ssl
+ pl[:i] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [:%d]' % (i))
+
+ for j in self.limits_plus(1):
+ ssl = nextRange(slen)
+ ul[i:j] = ssl
+ pl[i:j] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [%d:%d]' % (i, j))
+
+ for k in self.step_range():
+ ssl = nextRange( len(ul[i:j:k]) )
+ ul[i:j:k] = ssl
+ pl[i:j:k] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [%d:%d:%d]' % (i, j, k))
+
+ sliceLen = len(ul[i:j:k])
+ self.assertRaises(ValueError, setfcn, ul, i, j, k, sliceLen + 1)
+ if sliceLen > 2:
+ self.assertRaises(ValueError, setfcn, ul, i, j, k, sliceLen - 1)
+
+ for k in self.step_range():
+ ssl = nextRange( len(ul[i::k]) )
+ ul[i::k] = ssl
+ pl[i::k] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [%d::%d]' % (i, k))
+
+ ssl = nextRange( len(ul[:i:k]) )
+ ul[:i:k] = ssl
+ pl[:i:k] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [:%d:%d]' % (i, k))
+
+ for k in self.step_range():
+ ssl = nextRange(len(ul[::k]))
+ ul[::k] = ssl
+ pl[::k] = ssl
+ self.assertEqual(pl, ul[:], 'set slice [::%d]' % (k))
+
+
+ def test03_delslice(self):
+ 'Delete slice'
+ for Len in range(self.limit):
+ pl, ul = self.lists_of_len(Len)
+ del pl[:]
+ del ul[:]
+ self.assertEqual(pl[:], ul[:], 'del slice [:]')
+ for i in range(-Len - 1, Len + 1):
+ pl, ul = self.lists_of_len(Len)
+ del pl[i:]
+ del ul[i:]
+ self.assertEqual(pl[:], ul[:], 'del slice [%d:]' % (i))
+ pl, ul = self.lists_of_len(Len)
+ del pl[:i]
+ del ul[:i]
+ self.assertEqual(pl[:], ul[:], 'del slice [:%d]' % (i))
+ for j in range(-Len - 1, Len + 1):
+ pl, ul = self.lists_of_len(Len)
+ del pl[i:j]
+ del ul[i:j]
+ self.assertEqual(pl[:], ul[:], 'del slice [%d:%d]' % (i,j))
+ for k in range(-Len - 1,0) + range(1,Len):
+ pl, ul = self.lists_of_len(Len)
+ del pl[i:j:k]
+ del ul[i:j:k]
+ self.assertEqual(pl[:], ul[:], 'del slice [%d:%d:%d]' % (i,j,k))
+
+ for k in range(-Len - 1,0) + range(1,Len):
+ pl, ul = self.lists_of_len(Len)
+ del pl[:i:k]
+ del ul[:i:k]
+ self.assertEqual(pl[:], ul[:], 'del slice [:%d:%d]' % (i,k))
+
+ pl, ul = self.lists_of_len(Len)
+ del pl[i::k]
+ del ul[i::k]
+ self.assertEqual(pl[:], ul[:], 'del slice [%d::%d]' % (i,k))
+
+ for k in range(-Len - 1,0) + range(1,Len):
+ pl, ul = self.lists_of_len(Len)
+ del pl[::k]
+ del ul[::k]
+ self.assertEqual(pl[:], ul[:], 'del slice [::%d]' % (k))
+
+ def test04_get_set_del_single(self):
+ 'Get/set/delete single item'
+ pl, ul = self.lists_of_len()
+ for i in self.limits_plus(0):
+ self.assertEqual(pl[i], ul[i], 'get single item [%d]' % i)
+
+ for i in self.limits_plus(0):
+ pl, ul = self.lists_of_len()
+ pl[i] = 100
+ ul[i] = 100
+ self.assertEqual(pl[:], ul[:], 'set single item [%d]' % i)
+
+ for i in self.limits_plus(0):
+ pl, ul = self.lists_of_len()
+ del pl[i]
+ del ul[i]
+ self.assertEqual(pl[:], ul[:], 'del single item [%d]' % i)
+
+ def test05_out_of_range_exceptions(self):
+ 'Out of range exceptions'
+ def setfcn(x, i): x[i] = 20
+ def getfcn(x, i): return x[i]
+ def delfcn(x, i): del x[i]
+ pl, ul = self.lists_of_len()
+ for i in (-1 - self.limit, self.limit):
+ self.assertRaises(IndexError, setfcn, ul, i) # 'set index %d' % i)
+ self.assertRaises(IndexError, getfcn, ul, i) # 'get index %d' % i)
+ self.assertRaises(IndexError, delfcn, ul, i) # 'del index %d' % i)
+
+ def test06_list_methods(self):
+ 'List methods'
+ pl, ul = self.lists_of_len()
+ pl.append(40)
+ ul.append(40)
+ self.assertEqual(pl[:], ul[:], 'append')
+
+ pl.extend(range(50,55))
+ ul.extend(range(50,55))
+ self.assertEqual(pl[:], ul[:], 'extend')
+
+ pl.reverse()
+ ul.reverse()
+ self.assertEqual(pl[:], ul[:], 'reverse')
+
+ for i in self.limits_plus(1):
+ pl, ul = self.lists_of_len()
+ pl.insert(i,50)
+ ul.insert(i,50)
+ self.assertEqual(pl[:], ul[:], 'insert at %d' % i)
+
+ for i in self.limits_plus(0):
+ pl, ul = self.lists_of_len()
+ self.assertEqual(pl.pop(i), ul.pop(i), 'popped value at %d' % i)
+ self.assertEqual(pl[:], ul[:], 'after pop at %d' % i)
+
+ pl, ul = self.lists_of_len()
+ self.assertEqual(pl.pop(), ul.pop(i), 'popped value')
+ self.assertEqual(pl[:], ul[:], 'after pop')
+
+ pl, ul = self.lists_of_len()
+ def popfcn(x, i): x.pop(i)
+ self.assertRaises(IndexError, popfcn, ul, self.limit)
+ self.assertRaises(IndexError, popfcn, ul, -1 - self.limit)
+
+ pl, ul = self.lists_of_len()
+ for val in range(self.limit):
+ self.assertEqual(pl.index(val), ul.index(val), 'index of %d' % val)
+
+ for val in self.limits_plus(2):
+ self.assertEqual(pl.count(val), ul.count(val), 'count %d' % val)
+
+ for val in range(self.limit):
+ pl, ul = self.lists_of_len()
+ pl.remove(val)
+ ul.remove(val)
+ self.assertEqual(pl[:], ul[:], 'after remove val %d' % val)
+
+ def indexfcn(x, v): return x.index(v)
+ def removefcn(x, v): return x.remove(v)
+ self.assertRaises(ValueError, indexfcn, ul, 40)
+ self.assertRaises(ValueError, removefcn, ul, 40)
+
+ def test07_allowed_types(self):
+ 'Type-restricted list'
+ pl, ul = self.lists_of_len()
+ ul._allowed = (int, long)
+ ul[1] = 50
+ ul[:2] = [60, 70, 80]
+ def setfcn(x, i, v): x[i] = v
+ self.assertRaises(TypeError, setfcn, ul, 2, 'hello')
+ self.assertRaises(TypeError, setfcn, ul, slice(0,3,2), ('hello','goodbye'))
+
+ def test08_min_length(self):
+ 'Length limits'
+ pl, ul = self.lists_of_len()
+ ul._minlength = 1
+ def delfcn(x,i): del x[:i]
+ def setfcn(x,i): x[:i] = []
+ for i in range(self.limit - ul._minlength + 1, self.limit + 1):
+ self.assertRaises(ValueError, delfcn, ul, i)
+ self.assertRaises(ValueError, setfcn, ul, i)
+ del ul[:ul._minlength]
+
+ ul._maxlength = 4
+ for i in range(0, ul._maxlength - len(ul)):
+ ul.append(i)
+ self.assertRaises(ValueError, ul.append, 10)
+
+ def test09_iterable_check(self):
+ 'Error on assigning non-iterable to slice'
+ pl, ul = self.lists_of_len(self.limit + 1)
+ def setfcn(x, i, v): x[i] = v
+ self.assertRaises(TypeError, setfcn, ul, slice(0,3,2), 2)
+
+ def test10_checkindex(self):
+ 'Index check'
+ pl, ul = self.lists_of_len()
+ for i in self.limits_plus(0):
+ if i < 0:
+ self.assertEqual(ul._checkindex(i), i + self.limit, '_checkindex(neg index)')
+ else:
+ self.assertEqual(ul._checkindex(i), i, '_checkindex(pos index)')
+
+ for i in (-self.limit - 1, self.limit):
+ self.assertRaises(IndexError, ul._checkindex, i)
+
+ ul._IndexError = TypeError
+ self.assertRaises(TypeError, ul._checkindex, -self.limit - 1)
+
+ def test_11_sorting(self):
+ 'Sorting'
+ pl, ul = self.lists_of_len()
+ pl.insert(0, pl.pop())
+ ul.insert(0, ul.pop())
+ pl.sort()
+ ul.sort()
+ self.assertEqual(pl[:], ul[:], 'sort')
+ mid = pl[len(pl) / 2]
+ pl.sort(key=lambda x: (mid-x)**2)
+ ul.sort(key=lambda x: (mid-x)**2)
+ self.assertEqual(pl[:], ul[:], 'sort w/ key')
+
+ pl.insert(0, pl.pop())
+ ul.insert(0, ul.pop())
+ pl.sort(reverse=True)
+ ul.sort(reverse=True)
+ self.assertEqual(pl[:], ul[:], 'sort w/ reverse')
+ mid = pl[len(pl) / 2]
+ pl.sort(key=lambda x: (mid-x)**2)
+ ul.sort(key=lambda x: (mid-x)**2)
+ self.assertEqual(pl[:], ul[:], 'sort w/ key')
+
+ def test_12_arithmetic(self):
+ 'Arithmetic'
+ pl, ul = self.lists_of_len()
+ al = range(10,14)
+ self.assertEqual(list(pl + al), list(ul + al), 'add')
+ self.assertEqual(type(ul), type(ul + al), 'type of add result')
+ self.assertEqual(list(al + pl), list(al + ul), 'radd')
+ self.assertEqual(type(al), type(al + ul), 'type of radd result')
+ objid = id(ul)
+ pl += al
+ ul += al
+ self.assertEqual(pl[:], ul[:], 'in-place add')
+ self.assertEqual(objid, id(ul), 'in-place add id')
+
+ for n in (-1,0,1,3):
+ pl, ul = self.lists_of_len()
+ self.assertEqual(list(pl * n), list(ul * n), 'mul by %d' % n)
+ self.assertEqual(type(ul), type(ul * n), 'type of mul by %d result' % n)
+ self.assertEqual(list(n * pl), list(n * ul), 'rmul by %d' % n)
+ self.assertEqual(type(ul), type(n * ul), 'type of rmul by %d result' % n)
+ objid = id(ul)
+ pl *= n
+ ul *= n
+ self.assertEqual(pl[:], ul[:], 'in-place mul by %d' % n)
+ self.assertEqual(objid, id(ul), 'in-place mul by %d id' % n)
+
+ pl, ul = self.lists_of_len()
+ self.assertEqual(pl, ul, 'cmp for equal')
+ self.assert_(pl >= ul, 'cmp for gte self')
+ self.assert_(pl <= ul, 'cmp for lte self')
+ self.assert_(ul >= pl, 'cmp for self gte')
+ self.assert_(ul <= pl, 'cmp for self lte')
+
+ self.assert_(pl + [5] > ul, 'cmp')
+ self.assert_(pl + [5] >= ul, 'cmp')
+ self.assert_(pl < ul + [2], 'cmp')
+ self.assert_(pl <= ul + [2], 'cmp')
+ self.assert_(ul + [5] > pl, 'cmp')
+ self.assert_(ul + [5] >= pl, 'cmp')
+ self.assert_(ul < pl + [2], 'cmp')
+ self.assert_(ul <= pl + [2], 'cmp')
+
+ pl[1] = 20
+ self.assert_(pl > ul, 'cmp for gt self')
+ self.assert_(ul < pl, 'cmp for self lt')
+ pl[1] = -20
+ self.assert_(pl < ul, 'cmp for lt self')
+ self.assert_(pl < ul, 'cmp for lt self')
+
+class ListMixinTestSingle(ListMixinTest):
+ listType = UserListB
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(ListMixinTest))
+ s.addTest(unittest.makeSuite(ListMixinTestSingle))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
+
+if __name__ == '__main__':
+ run()
diff --git a/parts/django/django/contrib/gis/management/__init__.py b/parts/django/django/contrib/gis/management/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/management/__init__.py
diff --git a/parts/django/django/contrib/gis/management/base.py b/parts/django/django/contrib/gis/management/base.py
new file mode 100644
index 0000000..c998063
--- /dev/null
+++ b/parts/django/django/contrib/gis/management/base.py
@@ -0,0 +1,15 @@
+from django.core.management.base import BaseCommand, CommandError
+
+class ArgsCommand(BaseCommand):
+ """
+ Command class for commands that take multiple arguments.
+ """
+ args = '<arg arg ...>'
+
+ def handle(self, *args, **options):
+ if not args:
+ raise CommandError('Must provide the following arguments: %s' % self.args)
+ return self.handle_args(*args, **options)
+
+ def handle_args(self, *args, **options):
+ raise NotImplementedError()
diff --git a/parts/django/django/contrib/gis/management/commands/__init__.py b/parts/django/django/contrib/gis/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/management/commands/__init__.py
diff --git a/parts/django/django/contrib/gis/management/commands/inspectdb.py b/parts/django/django/contrib/gis/management/commands/inspectdb.py
new file mode 100644
index 0000000..937bb8e
--- /dev/null
+++ b/parts/django/django/contrib/gis/management/commands/inspectdb.py
@@ -0,0 +1,32 @@
+from optparse import make_option
+
+from django.core.management.base import CommandError
+from django.core.management.commands.inspectdb import Command as InspectDBCommand
+
+class Command(InspectDBCommand):
+ db_module = 'django.contrib.gis.db'
+ gis_tables = {}
+
+ def get_field_type(self, connection, table_name, row):
+ field_type, field_params, field_notes = super(Command, self).get_field_type(connection, table_name, row)
+ if field_type == 'GeometryField':
+ geo_col = row[0]
+ # Getting a more specific field type and any additional parameters
+ # from the `get_geometry_type` routine for the spatial backend.
+ field_type, geo_params = connection.introspection.get_geometry_type(table_name, geo_col)
+ field_params.update(geo_params)
+ # Adding the table name and column to the `gis_tables` dictionary, this
+ # allows us to track which tables need a GeoManager.
+ if table_name in self.gis_tables:
+ self.gis_tables[table_name].append(geo_col)
+ else:
+ self.gis_tables[table_name] = [geo_col]
+ return field_type, field_params, field_notes
+
+ def get_meta(self, table_name):
+ meta_lines = super(Command, self).get_meta(table_name)
+ if table_name in self.gis_tables:
+ # If the table is a geographic one, then we need make
+ # GeoManager the default manager for the model.
+ meta_lines.insert(0, ' objects = models.GeoManager()')
+ return meta_lines
diff --git a/parts/django/django/contrib/gis/management/commands/ogrinspect.py b/parts/django/django/contrib/gis/management/commands/ogrinspect.py
new file mode 100644
index 0000000..a495787
--- /dev/null
+++ b/parts/django/django/contrib/gis/management/commands/ogrinspect.py
@@ -0,0 +1,122 @@
+import os, sys
+from optparse import make_option
+from django.contrib.gis import gdal
+from django.contrib.gis.management.base import ArgsCommand, CommandError
+
+def layer_option(option, opt, value, parser):
+ """
+ Callback for `make_option` for the `ogrinspect` `layer_key`
+ keyword option which may be an integer or a string.
+ """
+ try:
+ dest = int(value)
+ except ValueError:
+ dest = value
+ setattr(parser.values, option.dest, dest)
+
+def list_option(option, opt, value, parser):
+ """
+ Callback for `make_option` for `ogrinspect` keywords that require
+ a string list. If the string is 'True'/'true' then the option
+ value will be a boolean instead.
+ """
+ if value.lower() == 'true':
+ dest = True
+ else:
+ dest = [s for s in value.split(',')]
+ setattr(parser.values, option.dest, dest)
+
+class Command(ArgsCommand):
+ help = ('Inspects the given OGR-compatible data source (e.g., a shapefile) and outputs\n'
+ 'a GeoDjango model with the given model name. For example:\n'
+ ' ./manage.py ogrinspect zipcode.shp Zipcode')
+ args = '[data_source] [model_name]'
+
+ option_list = ArgsCommand.option_list + (
+ make_option('--blank', dest='blank', type='string', action='callback',
+ callback=list_option, default=False,
+ help='Use a comma separated list of OGR field names to add '
+ 'the `blank=True` option to the field definition. Set with'
+ '`true` to apply to all applicable fields.'),
+ make_option('--decimal', dest='decimal', type='string', action='callback',
+ callback=list_option, default=False,
+ help='Use a comma separated list of OGR float fields to '
+ 'generate `DecimalField` instead of the default '
+ '`FloatField`. Set to `true` to apply to all OGR float fields.'),
+ make_option('--geom-name', dest='geom_name', type='string', default='geom',
+ help='Specifies the model name for the Geometry Field '
+ '(defaults to `geom`)'),
+ make_option('--layer', dest='layer_key', type='string', action='callback',
+ callback=layer_option, default=0,
+ help='The key for specifying which layer in the OGR data '
+ 'source to use. Defaults to 0 (the first layer). May be '
+ 'an integer or a string identifier for the layer.'),
+ make_option('--multi-geom', action='store_true', dest='multi_geom', default=False,
+ help='Treat the geometry in the data source as a geometry collection.'),
+ make_option('--name-field', dest='name_field',
+ help='Specifies a field name to return for the `__unicode__` function.'),
+ make_option('--no-imports', action='store_false', dest='imports', default=True,
+ help='Do not include `from django.contrib.gis.db import models` '
+ 'statement.'),
+ make_option('--null', dest='null', type='string', action='callback',
+ callback=list_option, default=False,
+ help='Use a comma separated list of OGR field names to add '
+ 'the `null=True` option to the field definition. Set with'
+ '`true` to apply to all applicable fields.'),
+ make_option('--srid', dest='srid',
+ help='The SRID to use for the Geometry Field. If it can be '
+ 'determined, the SRID of the data source is used.'),
+ make_option('--mapping', action='store_true', dest='mapping',
+ help='Generate mapping dictionary for use with `LayerMapping`.')
+ )
+
+ requires_model_validation = False
+
+ def handle_args(self, *args, **options):
+ try:
+ data_source, model_name = args
+ except ValueError:
+ raise CommandError('Invalid arguments, must provide: %s' % self.args)
+
+ if not gdal.HAS_GDAL:
+ raise CommandError('GDAL is required to inspect geospatial data sources.')
+
+ # TODO: Support non file-based OGR datasources.
+ if not os.path.isfile(data_source):
+ raise CommandError('The given data source cannot be found: "%s"' % data_source)
+
+ # Removing options with `None` values.
+ options = dict([(k, v) for k, v in options.items() if not v is None])
+
+ # Getting the OGR DataSource from the string parameter.
+ try:
+ ds = gdal.DataSource(data_source)
+ except gdal.OGRException, msg:
+ raise CommandError(msg)
+
+ # Whether the user wants to generate the LayerMapping dictionary as well.
+ show_mapping = options.pop('mapping', False)
+
+ # Popping the verbosity global option, as it's not accepted by `_ogrinspect`.
+ verbosity = options.pop('verbosity', False)
+
+ # Returning the output of ogrinspect with the given arguments
+ # and options.
+ from django.contrib.gis.utils.ogrinspect import _ogrinspect, mapping
+ output = [s for s in _ogrinspect(ds, model_name, **options)]
+ if show_mapping:
+ # Constructing the keyword arguments for `mapping`, and
+ # calling it on the data source.
+ kwargs = {'geom_name' : options['geom_name'],
+ 'layer_key' : options['layer_key'],
+ 'multi_geom' : options['multi_geom'],
+ }
+ mapping_dict = mapping(ds, **kwargs)
+ # This extra legwork is so that the dictionary definition comes
+ # out in the same order as the fields in the model definition.
+ rev_mapping = dict([(v, k) for k, v in mapping_dict.items()])
+ output.extend(['', '# Auto-generated `LayerMapping` dictionary for %s model' % model_name,
+ '%s_mapping = {' % model_name.lower()])
+ output.extend([" '%s' : '%s'," % (rev_mapping[ogr_fld], ogr_fld) for ogr_fld in ds[options['layer_key']].fields])
+ output.extend([" '%s' : '%s'," % (options['geom_name'], mapping_dict[options['geom_name']]), '}'])
+ return '\n'.join(output)
diff --git a/parts/django/django/contrib/gis/maps/__init__.py b/parts/django/django/contrib/gis/maps/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/maps/__init__.py
diff --git a/parts/django/django/contrib/gis/maps/google/__init__.py b/parts/django/django/contrib/gis/maps/google/__init__.py
new file mode 100644
index 0000000..9be689c
--- /dev/null
+++ b/parts/django/django/contrib/gis/maps/google/__init__.py
@@ -0,0 +1,61 @@
+"""
+ This module houses the GoogleMap object, used for generating
+ the needed javascript to embed Google Maps in a Web page.
+
+ Google(R) is a registered trademark of Google, Inc. of Mountain View, California.
+
+ Example:
+
+ * In the view:
+ return render_to_response('template.html', {'google' : GoogleMap(key="abcdefg")})
+
+ * In the template:
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ {{ google.xhtml }}
+ <head>
+ <title>Google Maps via GeoDjango</title>
+ {{ google.style }}
+ {{ google.scripts }}
+ </head>
+ {{ google.body }}
+ <div id="{{ google.dom_id }}" style="width:600px;height:400px;"></div>
+ </body>
+ </html>
+
+ Note: If you want to be more explicit in your templates, the following are
+ equivalent:
+ {{ google.body }} => "<body {{ google.onload }} {{ google.onunload }}>"
+ {{ google.xhtml }} => "<html xmlns="http://www.w3.org/1999/xhtml" {{ google.xmlns }}>"
+ {{ google.style }} => "<style>{{ google.vml_css }}</style>"
+
+ Explanation:
+ - The `xhtml` property provides the correct XML namespace needed for
+ Google Maps to operate in IE using XHTML. Google Maps on IE uses
+ VML to draw polylines. Returns, by default:
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
+
+ - The `style` property provides the correct style tag for the CSS
+ properties required by Google Maps on IE:
+ <style type="text/css">v\:* {behavior:url(#default#VML);}</style>
+
+ - The `scripts` property provides the necessary <script> tags for
+ including the Google Maps javascript, as well as including the
+ generated javascript.
+
+ - The `body` property provides the correct attributes for the
+ body tag to load the generated javascript. By default, returns:
+ <body onload="gmap_load()" onunload="GUnload()">
+
+ - The `dom_id` property returns the DOM id for the map. Defaults to "map".
+
+ The following attributes may be set or customized in your local settings:
+ * GOOGLE_MAPS_API_KEY: String of your Google Maps API key. These are tied to
+ to a domain. May be obtained from http://www.google.com/apis/maps/
+ * GOOGLE_MAPS_API_VERSION (optional): Defaults to using "2.x"
+ * GOOGLE_MAPS_URL (optional): Must have a substitution ('%s') for the API
+ version.
+"""
+from django.contrib.gis.maps.google.gmap import GoogleMap, GoogleMapSet
+from django.contrib.gis.maps.google.overlays import GEvent, GIcon, GMarker, GPolygon, GPolyline
+from django.contrib.gis.maps.google.zoom import GoogleZoom
diff --git a/parts/django/django/contrib/gis/maps/google/gmap.py b/parts/django/django/contrib/gis/maps/google/gmap.py
new file mode 100644
index 0000000..cca5dc9
--- /dev/null
+++ b/parts/django/django/contrib/gis/maps/google/gmap.py
@@ -0,0 +1,226 @@
+from django.conf import settings
+from django.contrib.gis import geos
+from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
+
+class GoogleMapException(Exception): pass
+from django.contrib.gis.maps.google.overlays import GPolygon, GPolyline, GMarker, GIcon
+
+# The default Google Maps URL (for the API javascript)
+# TODO: Internationalize for Japan, UK, etc.
+GOOGLE_MAPS_URL='http://maps.google.com/maps?file=api&amp;v=%s&amp;key='
+
+class GoogleMap(object):
+ "A class for generating Google Maps JavaScript."
+
+ # String constants
+ onunload = mark_safe('onunload="GUnload()"') # Cleans up after Google Maps
+ vml_css = mark_safe('v\:* {behavior:url(#default#VML);}') # CSS for IE VML
+ xmlns = mark_safe('xmlns:v="urn:schemas-microsoft-com:vml"') # XML Namespace (for IE VML).
+
+ def __init__(self, key=None, api_url=None, version=None,
+ center=None, zoom=None, dom_id='map',
+ kml_urls=[], polylines=None, polygons=None, markers=None,
+ template='gis/google/google-map.js',
+ js_module='geodjango',
+ extra_context={}):
+
+ # The Google Maps API Key defined in the settings will be used
+ # if not passed in as a parameter. The use of an API key is
+ # _required_.
+ if not key:
+ try:
+ self.key = settings.GOOGLE_MAPS_API_KEY
+ except AttributeError:
+ raise GoogleMapException('Google Maps API Key not found (try adding GOOGLE_MAPS_API_KEY to your settings).')
+ else:
+ self.key = key
+
+ # Getting the Google Maps API version, defaults to using the latest ("2.x"),
+ # this is not necessarily the most stable.
+ if not version:
+ self.version = getattr(settings, 'GOOGLE_MAPS_API_VERSION', '2.x')
+ else:
+ self.version = version
+
+ # Can specify the API URL in the `api_url` keyword.
+ if not api_url:
+ self.api_url = mark_safe(getattr(settings, 'GOOGLE_MAPS_URL', GOOGLE_MAPS_URL) % self.version)
+ else:
+ self.api_url = api_url
+
+ # Setting the DOM id of the map, the load function, the JavaScript
+ # template, and the KML URLs array.
+ self.dom_id = dom_id
+ self.extra_context = extra_context
+ self.js_module = js_module
+ self.template = template
+ self.kml_urls = kml_urls
+
+ # Does the user want any GMarker, GPolygon, and/or GPolyline overlays?
+ overlay_info = [[GMarker, markers, 'markers'],
+ [GPolygon, polygons, 'polygons'],
+ [GPolyline, polylines, 'polylines']]
+
+ for overlay_class, overlay_list, varname in overlay_info:
+ setattr(self, varname, [])
+ if overlay_list:
+ for overlay in overlay_list:
+ if isinstance(overlay, overlay_class):
+ getattr(self, varname).append(overlay)
+ else:
+ getattr(self, varname).append(overlay_class(overlay))
+
+ # If GMarker, GPolygons, and/or GPolylines are used the zoom will be
+ # automatically calculated via the Google Maps API. If both a zoom
+ # level and a center coordinate are provided with polygons/polylines,
+ # no automatic determination will occur.
+ self.calc_zoom = False
+ if self.polygons or self.polylines or self.markers:
+ if center is None or zoom is None:
+ self.calc_zoom = True
+
+ # Defaults for the zoom level and center coordinates if the zoom
+ # is not automatically calculated.
+ if zoom is None: zoom = 4
+ self.zoom = zoom
+ if center is None: center = (0, 0)
+ self.center = center
+
+ def render(self):
+ """
+ Generates the JavaScript necessary for displaying this Google Map.
+ """
+ params = {'calc_zoom' : self.calc_zoom,
+ 'center' : self.center,
+ 'dom_id' : self.dom_id,
+ 'js_module' : self.js_module,
+ 'kml_urls' : self.kml_urls,
+ 'zoom' : self.zoom,
+ 'polygons' : self.polygons,
+ 'polylines' : self.polylines,
+ 'icons': self.icons,
+ 'markers' : self.markers,
+ }
+ params.update(self.extra_context)
+ return render_to_string(self.template, params)
+
+ @property
+ def body(self):
+ "Returns HTML body tag for loading and unloading Google Maps javascript."
+ return mark_safe('<body %s %s>' % (self.onload, self.onunload))
+
+ @property
+ def onload(self):
+ "Returns the `onload` HTML <body> attribute."
+ return mark_safe('onload="%s.%s_load()"' % (self.js_module, self.dom_id))
+
+ @property
+ def api_script(self):
+ "Returns the <script> tag for the Google Maps API javascript."
+ return mark_safe('<script src="%s%s" type="text/javascript"></script>' % (self.api_url, self.key))
+
+ @property
+ def js(self):
+ "Returns only the generated Google Maps JavaScript (no <script> tags)."
+ return self.render()
+
+ @property
+ def scripts(self):
+ "Returns all <script></script> tags required with Google Maps JavaScript."
+ return mark_safe('%s\n <script type="text/javascript">\n//<![CDATA[\n%s//]]>\n </script>' % (self.api_script, self.js))
+
+ @property
+ def style(self):
+ "Returns additional CSS styling needed for Google Maps on IE."
+ return mark_safe('<style type="text/css">%s</style>' % self.vml_css)
+
+ @property
+ def xhtml(self):
+ "Returns XHTML information needed for IE VML overlays."
+ return mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" %s>' % self.xmlns)
+
+ @property
+ def icons(self):
+ "Returns a sequence of GIcon objects in this map."
+ return set([marker.icon for marker in self.markers if marker.icon])
+
+class GoogleMapSet(GoogleMap):
+
+ def __init__(self, *args, **kwargs):
+ """
+ A class for generating sets of Google Maps that will be shown on the
+ same page together.
+
+ Example:
+ gmapset = GoogleMapSet( GoogleMap( ... ), GoogleMap( ... ) )
+ gmapset = GoogleMapSet( [ gmap1, gmap2] )
+ """
+ # The `google-multi.js` template is used instead of `google-single.js`
+ # by default.
+ template = kwargs.pop('template', 'gis/google/google-multi.js')
+
+ # This is the template used to generate the GMap load JavaScript for
+ # each map in the set.
+ self.map_template = kwargs.pop('map_template', 'gis/google/google-single.js')
+
+ # Running GoogleMap.__init__(), and resetting the template
+ # value with default obtained above.
+ super(GoogleMapSet, self).__init__(**kwargs)
+ self.template = template
+
+ # If a tuple/list passed in as first element of args, then assume
+ if isinstance(args[0], (tuple, list)):
+ self.maps = args[0]
+ else:
+ self.maps = args
+
+ # Generating DOM ids for each of the maps in the set.
+ self.dom_ids = ['map%d' % i for i in xrange(len(self.maps))]
+
+ def load_map_js(self):
+ """
+ Returns JavaScript containing all of the loading routines for each
+ map in this set.
+ """
+ result = []
+ for dom_id, gmap in zip(self.dom_ids, self.maps):
+ # Backup copies the GoogleMap DOM id and template attributes.
+ # They are overridden on each GoogleMap instance in the set so
+ # that only the loading JavaScript (and not the header variables)
+ # is used with the generated DOM ids.
+ tmp = (gmap.template, gmap.dom_id)
+ gmap.template = self.map_template
+ gmap.dom_id = dom_id
+ result.append(gmap.js)
+ # Restoring the backup values.
+ gmap.template, gmap.dom_id = tmp
+ return mark_safe(''.join(result))
+
+ def render(self):
+ """
+ Generates the JavaScript for the collection of Google Maps in
+ this set.
+ """
+ params = {'js_module' : self.js_module,
+ 'dom_ids' : self.dom_ids,
+ 'load_map_js' : self.load_map_js(),
+ 'icons' : self.icons,
+ }
+ params.update(self.extra_context)
+ return render_to_string(self.template, params)
+
+ @property
+ def onload(self):
+ "Returns the `onload` HTML <body> attribute."
+ # Overloaded to use the `load` function defined in the
+ # `google-multi.js`, which calls the load routines for
+ # each one of the individual maps in the set.
+ return mark_safe('onload="%s.load()"' % self.js_module)
+
+ @property
+ def icons(self):
+ "Returns a sequence of all icons in each map of the set."
+ icons = set()
+ for map in self.maps: icons |= map.icons
+ return icons
diff --git a/parts/django/django/contrib/gis/maps/google/overlays.py b/parts/django/django/contrib/gis/maps/google/overlays.py
new file mode 100644
index 0000000..c2ebb3c
--- /dev/null
+++ b/parts/django/django/contrib/gis/maps/google/overlays.py
@@ -0,0 +1,301 @@
+from django.utils.safestring import mark_safe
+from django.contrib.gis.geos import fromstr, Point, LineString, LinearRing, Polygon
+
+class GEvent(object):
+ """
+ A Python wrapper for the Google GEvent object.
+
+ Events can be attached to any object derived from GOverlayBase with the
+ add_event() call.
+
+ For more information please see the Google Maps API Reference:
+ http://code.google.com/apis/maps/documentation/reference.html#GEvent
+
+ Example:
+
+ from django.shortcuts import render_to_response
+ from django.contrib.gis.maps.google import GoogleMap, GEvent, GPolyline
+
+ def sample_request(request):
+ polyline = GPolyline('LINESTRING(101 26, 112 26, 102 31)')
+ event = GEvent('click',
+ 'function() { location.href = "http://www.google.com"}')
+ polyline.add_event(event)
+ return render_to_response('mytemplate.html',
+ {'google' : GoogleMap(polylines=[polyline])})
+ """
+
+ def __init__(self, event, action):
+ """
+ Initializes a GEvent object.
+
+ Parameters:
+
+ event:
+ string for the event, such as 'click'. The event must be a valid
+ event for the object in the Google Maps API.
+ There is no validation of the event type within Django.
+
+ action:
+ string containing a Javascript function, such as
+ 'function() { location.href = "newurl";}'
+ The string must be a valid Javascript function. Again there is no
+ validation fo the function within Django.
+ """
+ self.event = event
+ self.action = action
+
+ def __unicode__(self):
+ "Returns the parameter part of a GEvent."
+ return mark_safe('"%s", %s' %(self.event, self.action))
+
+class GOverlayBase(object):
+ def __init__(self):
+ self.events = []
+
+ def latlng_from_coords(self, coords):
+ "Generates a JavaScript array of GLatLng objects for the given coordinates."
+ return '[%s]' % ','.join(['new GLatLng(%s,%s)' % (y, x) for x, y in coords])
+
+ def add_event(self, event):
+ "Attaches a GEvent to the overlay object."
+ self.events.append(event)
+
+ def __unicode__(self):
+ "The string representation is the JavaScript API call."
+ return mark_safe('%s(%s)' % (self.__class__.__name__, self.js_params))
+
+class GPolygon(GOverlayBase):
+ """
+ A Python wrapper for the Google GPolygon object. For more information
+ please see the Google Maps API Reference:
+ http://code.google.com/apis/maps/documentation/reference.html#GPolygon
+ """
+ def __init__(self, poly,
+ stroke_color='#0000ff', stroke_weight=2, stroke_opacity=1,
+ fill_color='#0000ff', fill_opacity=0.4):
+ """
+ The GPolygon object initializes on a GEOS Polygon or a parameter that
+ may be instantiated into GEOS Polygon. Please note that this will not
+ depict a Polygon's internal rings.
+
+ Keyword Options:
+
+ stroke_color:
+ The color of the polygon outline. Defaults to '#0000ff' (blue).
+
+ stroke_weight:
+ The width of the polygon outline, in pixels. Defaults to 2.
+
+ stroke_opacity:
+ The opacity of the polygon outline, between 0 and 1. Defaults to 1.
+
+ fill_color:
+ The color of the polygon fill. Defaults to '#0000ff' (blue).
+
+ fill_opacity:
+ The opacity of the polygon fill. Defaults to 0.4.
+ """
+ if isinstance(poly, basestring): poly = fromstr(poly)
+ if isinstance(poly, (tuple, list)): poly = Polygon(poly)
+ if not isinstance(poly, Polygon):
+ raise TypeError('GPolygon may only initialize on GEOS Polygons.')
+
+ # Getting the envelope of the input polygon (used for automatically
+ # determining the zoom level).
+ self.envelope = poly.envelope
+
+ # Translating the coordinates into a JavaScript array of
+ # Google `GLatLng` objects.
+ self.points = self.latlng_from_coords(poly.shell.coords)
+
+ # Stroke settings.
+ self.stroke_color, self.stroke_opacity, self.stroke_weight = stroke_color, stroke_opacity, stroke_weight
+
+ # Fill settings.
+ self.fill_color, self.fill_opacity = fill_color, fill_opacity
+
+ super(GPolygon, self).__init__()
+
+ @property
+ def js_params(self):
+ return '%s, "%s", %s, %s, "%s", %s' % (self.points, self.stroke_color, self.stroke_weight, self.stroke_opacity,
+ self.fill_color, self.fill_opacity)
+
+class GPolyline(GOverlayBase):
+ """
+ A Python wrapper for the Google GPolyline object. For more information
+ please see the Google Maps API Reference:
+ http://code.google.com/apis/maps/documentation/reference.html#GPolyline
+ """
+ def __init__(self, geom, color='#0000ff', weight=2, opacity=1):
+ """
+ The GPolyline object may be initialized on GEOS LineStirng, LinearRing,
+ and Polygon objects (internal rings not supported) or a parameter that
+ may instantiated into one of the above geometries.
+
+ Keyword Options:
+
+ color:
+ The color to use for the polyline. Defaults to '#0000ff' (blue).
+
+ weight:
+ The width of the polyline, in pixels. Defaults to 2.
+
+ opacity:
+ The opacity of the polyline, between 0 and 1. Defaults to 1.
+ """
+ # If a GEOS geometry isn't passed in, try to contsruct one.
+ if isinstance(geom, basestring): geom = fromstr(geom)
+ if isinstance(geom, (tuple, list)): geom = Polygon(geom)
+ # Generating the lat/lng coordinate pairs.
+ if isinstance(geom, (LineString, LinearRing)):
+ self.latlngs = self.latlng_from_coords(geom.coords)
+ elif isinstance(geom, Polygon):
+ self.latlngs = self.latlng_from_coords(geom.shell.coords)
+ else:
+ raise TypeError('GPolyline may only initialize on GEOS LineString, LinearRing, and/or Polygon geometries.')
+
+ # Getting the envelope for automatic zoom determination.
+ self.envelope = geom.envelope
+ self.color, self.weight, self.opacity = color, weight, opacity
+ super(GPolyline, self).__init__()
+
+ @property
+ def js_params(self):
+ return '%s, "%s", %s, %s' % (self.latlngs, self.color, self.weight, self.opacity)
+
+
+class GIcon(object):
+ """
+ Creates a GIcon object to pass into a Gmarker object.
+
+ The keyword arguments map to instance attributes of the same name. These,
+ in turn, correspond to a subset of the attributes of the official GIcon
+ javascript object:
+
+ http://code.google.com/apis/maps/documentation/reference.html#GIcon
+
+ Because a Google map often uses several different icons, a name field has
+ been added to the required arguments.
+
+ Required Arguments:
+ varname:
+ A string which will become the basis for the js variable name of
+ the marker, for this reason, your code should assign a unique
+ name for each GIcon you instantiate, otherwise there will be
+ name space collisions in your javascript.
+
+ Keyword Options:
+ image:
+ The url of the image to be used as the icon on the map defaults
+ to 'G_DEFAULT_ICON'
+
+ iconsize:
+ a tuple representing the pixel size of the foreground (not the
+ shadow) image of the icon, in the format: (width, height) ex.:
+
+ GIcon('fast_food',
+ image="/media/icon/star.png",
+ iconsize=(15,10))
+
+ Would indicate your custom icon was 15px wide and 10px height.
+
+ shadow:
+ the url of the image of the icon's shadow
+
+ shadowsize:
+ a tuple representing the pixel size of the shadow image, format is
+ the same as ``iconsize``
+
+ iconanchor:
+ a tuple representing the pixel coordinate relative to the top left
+ corner of the icon image at which this icon is anchored to the map.
+ In (x, y) format. x increases to the right in the Google Maps
+ coordinate system and y increases downwards in the Google Maps
+ coordinate system.)
+
+ infowindowanchor:
+ The pixel coordinate relative to the top left corner of the icon
+ image at which the info window is anchored to this icon.
+
+ """
+ def __init__(self, varname, image=None, iconsize=None,
+ shadow=None, shadowsize=None, iconanchor=None,
+ infowindowanchor=None):
+ self.varname = varname
+ self.image = image
+ self.iconsize = iconsize
+ self.shadow = shadow
+ self.shadowsize = shadowsize
+ self.iconanchor = iconanchor
+ self.infowindowanchor = infowindowanchor
+
+ def __cmp__(self, other):
+ return cmp(self.varname, other.varname)
+
+ def __hash__(self):
+ # XOR with hash of GIcon type so that hash('varname') won't
+ # equal hash(GIcon('varname')).
+ return hash(self.__class__) ^ hash(self.varname)
+
+class GMarker(GOverlayBase):
+ """
+ A Python wrapper for the Google GMarker object. For more information
+ please see the Google Maps API Reference:
+ http://code.google.com/apis/maps/documentation/reference.html#GMarker
+
+ Example:
+
+ from django.shortcuts import render_to_response
+ from django.contrib.gis.maps.google.overlays import GMarker, GEvent
+
+ def sample_request(request):
+ marker = GMarker('POINT(101 26)')
+ event = GEvent('click',
+ 'function() { location.href = "http://www.google.com"}')
+ marker.add_event(event)
+ return render_to_response('mytemplate.html',
+ {'google' : GoogleMap(markers=[marker])})
+ """
+ def __init__(self, geom, title=None, draggable=False, icon=None):
+ """
+ The GMarker object may initialize on GEOS Points or a parameter
+ that may be instantiated into a GEOS point. Keyword options map to
+ GMarkerOptions -- so far only the title option is supported.
+
+ Keyword Options:
+ title:
+ Title option for GMarker, will be displayed as a tooltip.
+
+ draggable:
+ Draggable option for GMarker, disabled by default.
+ """
+ # If a GEOS geometry isn't passed in, try to construct one.
+ if isinstance(geom, basestring): geom = fromstr(geom)
+ if isinstance(geom, (tuple, list)): geom = Point(geom)
+ if isinstance(geom, Point):
+ self.latlng = self.latlng_from_coords(geom.coords)
+ else:
+ raise TypeError('GMarker may only initialize on GEOS Point geometry.')
+ # Getting the envelope for automatic zoom determination.
+ self.envelope = geom.envelope
+ # TODO: Add support for more GMarkerOptions
+ self.title = title
+ self.draggable = draggable
+ self.icon = icon
+ super(GMarker, self).__init__()
+
+ def latlng_from_coords(self, coords):
+ return 'new GLatLng(%s,%s)' %(coords[1], coords[0])
+
+ def options(self):
+ result = []
+ if self.title: result.append('title: "%s"' % self.title)
+ if self.icon: result.append('icon: %s' % self.icon.varname)
+ if self.draggable: result.append('draggable: true')
+ return '{%s}' % ','.join(result)
+
+ @property
+ def js_params(self):
+ return '%s, %s' % (self.latlng, self.options())
diff --git a/parts/django/django/contrib/gis/maps/google/zoom.py b/parts/django/django/contrib/gis/maps/google/zoom.py
new file mode 100644
index 0000000..abc3fbf
--- /dev/null
+++ b/parts/django/django/contrib/gis/maps/google/zoom.py
@@ -0,0 +1,161 @@
+from django.contrib.gis.geos import GEOSGeometry, LinearRing, Polygon, Point
+from django.contrib.gis.maps.google.gmap import GoogleMapException
+from math import pi, sin, cos, log, exp, atan
+
+# Constants used for degree to radian conversion, and vice-versa.
+DTOR = pi / 180.
+RTOD = 180. / pi
+
+class GoogleZoom(object):
+ """
+ GoogleZoom is a utility for performing operations related to the zoom
+ levels on Google Maps.
+
+ This class is inspired by the OpenStreetMap Mapnik tile generation routine
+ `generate_tiles.py`, and the article "How Big Is the World" (Hack #16) in
+ "Google Maps Hacks" by Rich Gibson and Schuyler Erle.
+
+ `generate_tiles.py` may be found at:
+ http://trac.openstreetmap.org/browser/applications/rendering/mapnik/generate_tiles.py
+
+ "Google Maps Hacks" may be found at http://safari.oreilly.com/0596101619
+ """
+
+ def __init__(self, num_zoom=19, tilesize=256):
+ "Initializes the Google Zoom object."
+ # Google's tilesize is 256x256, square tiles are assumed.
+ self._tilesize = tilesize
+
+ # The number of zoom levels
+ self._nzoom = num_zoom
+
+ # Initializing arrays to hold the parameters for each one of the
+ # zoom levels.
+ self._degpp = [] # Degrees per pixel
+ self._radpp = [] # Radians per pixel
+ self._npix = [] # 1/2 the number of pixels for a tile at the given zoom level
+
+ # Incrementing through the zoom levels and populating the parameter arrays.
+ z = tilesize # The number of pixels per zoom level.
+ for i in xrange(num_zoom):
+ # Getting the degrees and radians per pixel, and the 1/2 the number of
+ # for every zoom level.
+ self._degpp.append(z / 360.) # degrees per pixel
+ self._radpp.append(z / (2 * pi)) # radians per pixl
+ self._npix.append(z / 2) # number of pixels to center of tile
+
+ # Multiplying `z` by 2 for the next iteration.
+ z *= 2
+
+ def __len__(self):
+ "Returns the number of zoom levels."
+ return self._nzoom
+
+ def get_lon_lat(self, lonlat):
+ "Unpacks longitude, latitude from GEOS Points and 2-tuples."
+ if isinstance(lonlat, Point):
+ lon, lat = lonlat.coords
+ else:
+ lon, lat = lonlat
+ return lon, lat
+
+ def lonlat_to_pixel(self, lonlat, zoom):
+ "Converts a longitude, latitude coordinate pair for the given zoom level."
+ # Setting up, unpacking the longitude, latitude values and getting the
+ # number of pixels for the given zoom level.
+ lon, lat = self.get_lon_lat(lonlat)
+ npix = self._npix[zoom]
+
+ # Calculating the pixel x coordinate by multiplying the longitude value
+ # with with the number of degrees/pixel at the given zoom level.
+ px_x = round(npix + (lon * self._degpp[zoom]))
+
+ # Creating the factor, and ensuring that 1 or -1 is not passed in as the
+ # base to the logarithm. Here's why:
+ # if fac = -1, we'll get log(0) which is undefined;
+ # if fac = 1, our logarithm base will be divided by 0, also undefined.
+ fac = min(max(sin(DTOR * lat), -0.9999), 0.9999)
+
+ # Calculating the pixel y coordinate.
+ px_y = round(npix + (0.5 * log((1 + fac)/(1 - fac)) * (-1.0 * self._radpp[zoom])))
+
+ # Returning the pixel x, y to the caller of the function.
+ return (px_x, px_y)
+
+ def pixel_to_lonlat(self, px, zoom):
+ "Converts a pixel to a longitude, latitude pair at the given zoom level."
+ if len(px) != 2:
+ raise TypeError('Pixel should be a sequence of two elements.')
+
+ # Getting the number of pixels for the given zoom level.
+ npix = self._npix[zoom]
+
+ # Calculating the longitude value, using the degrees per pixel.
+ lon = (px[0] - npix) / self._degpp[zoom]
+
+ # Calculating the latitude value.
+ lat = RTOD * ( 2 * atan(exp((px[1] - npix)/ (-1.0 * self._radpp[zoom]))) - 0.5 * pi)
+
+ # Returning the longitude, latitude coordinate pair.
+ return (lon, lat)
+
+ def tile(self, lonlat, zoom):
+ """
+ Returns a Polygon corresponding to the region represented by a fictional
+ Google Tile for the given longitude/latitude pair and zoom level. This
+ tile is used to determine the size of a tile at the given point.
+ """
+ # The given lonlat is the center of the tile.
+ delta = self._tilesize / 2
+
+ # Getting the pixel coordinates corresponding to the
+ # the longitude/latitude.
+ px = self.lonlat_to_pixel(lonlat, zoom)
+
+ # Getting the lower-left and upper-right lat/lon coordinates
+ # for the bounding box of the tile.
+ ll = self.pixel_to_lonlat((px[0]-delta, px[1]-delta), zoom)
+ ur = self.pixel_to_lonlat((px[0]+delta, px[1]+delta), zoom)
+
+ # Constructing the Polygon, representing the tile and returning.
+ return Polygon(LinearRing(ll, (ll[0], ur[1]), ur, (ur[0], ll[1]), ll), srid=4326)
+
+ def get_zoom(self, geom):
+ "Returns the optimal Zoom level for the given geometry."
+ # Checking the input type.
+ if not isinstance(geom, GEOSGeometry) or geom.srid != 4326:
+ raise TypeError('get_zoom() expects a GEOS Geometry with an SRID of 4326.')
+
+ # Getting the envelope for the geometry, and its associated width, height
+ # and centroid.
+ env = geom.envelope
+ env_w, env_h = self.get_width_height(env.extent)
+ center = env.centroid
+
+ for z in xrange(self._nzoom):
+ # Getting the tile at the zoom level.
+ tile_w, tile_h = self.get_width_height(self.tile(center, z).extent)
+
+ # When we span more than one tile, this is an approximately good
+ # zoom level.
+ if (env_w > tile_w) or (env_h > tile_h):
+ if z == 0:
+ raise GoogleMapException('Geometry width and height should not exceed that of the Earth.')
+ return z-1
+
+ # Otherwise, we've zoomed in to the max.
+ return self._nzoom-1
+
+ def get_width_height(self, extent):
+ """
+ Returns the width and height for the given extent.
+ """
+ # Getting the lower-left, upper-left, and upper-right
+ # coordinates from the extent.
+ ll = Point(extent[:2])
+ ul = Point(extent[0], extent[3])
+ ur = Point(extent[2:])
+ # Calculating the width and height.
+ height = ll.distance(ul)
+ width = ul.distance(ur)
+ return width, height
diff --git a/parts/django/django/contrib/gis/maps/openlayers/__init__.py b/parts/django/django/contrib/gis/maps/openlayers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/maps/openlayers/__init__.py
diff --git a/parts/django/django/contrib/gis/measure.py b/parts/django/django/contrib/gis/measure.py
new file mode 100644
index 0000000..a60398b
--- /dev/null
+++ b/parts/django/django/contrib/gis/measure.py
@@ -0,0 +1,336 @@
+# Copyright (c) 2007, Robert Coup <robert.coup@onetrackmind.co.nz>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# 3. Neither the name of Distance nor the names of its contributors may be used
+# to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+"""
+Distance and Area objects to allow for sensible and convienient calculation
+and conversions.
+
+Authors: Robert Coup, Justin Bronn
+
+Inspired by GeoPy (http://exogen.case.edu/projects/geopy/)
+and Geoff Biggs' PhD work on dimensioned units for robotics.
+"""
+__all__ = ['A', 'Area', 'D', 'Distance']
+from decimal import Decimal
+
+class MeasureBase(object):
+ def default_units(self, kwargs):
+ """
+ Return the unit value and the default units specified
+ from the given keyword arguments dictionary.
+ """
+ val = 0.0
+ for unit, value in kwargs.iteritems():
+ if not isinstance(value, float): value = float(value)
+ if unit in self.UNITS:
+ val += self.UNITS[unit] * value
+ default_unit = unit
+ elif unit in self.ALIAS:
+ u = self.ALIAS[unit]
+ val += self.UNITS[u] * value
+ default_unit = u
+ else:
+ lower = unit.lower()
+ if lower in self.UNITS:
+ val += self.UNITS[lower] * value
+ default_unit = lower
+ elif lower in self.LALIAS:
+ u = self.LALIAS[lower]
+ val += self.UNITS[u] * value
+ default_unit = u
+ else:
+ raise AttributeError('Unknown unit type: %s' % unit)
+ return val, default_unit
+
+ @classmethod
+ def unit_attname(cls, unit_str):
+ """
+ Retrieves the unit attribute name for the given unit string.
+ For example, if the given unit string is 'metre', 'm' would be returned.
+ An exception is raised if an attribute cannot be found.
+ """
+ lower = unit_str.lower()
+ if unit_str in cls.UNITS:
+ return unit_str
+ elif lower in cls.UNITS:
+ return lower
+ elif lower in cls.LALIAS:
+ return cls.LALIAS[lower]
+ else:
+ raise Exception('Could not find a unit keyword associated with "%s"' % unit_str)
+
+class Distance(MeasureBase):
+ UNITS = {
+ 'chain' : 20.1168,
+ 'chain_benoit' : 20.116782,
+ 'chain_sears' : 20.1167645,
+ 'british_chain_benoit' : 20.1167824944,
+ 'british_chain_sears' : 20.1167651216,
+ 'british_chain_sears_truncated' : 20.116756,
+ 'cm' : 0.01,
+ 'british_ft' : 0.304799471539,
+ 'british_yd' : 0.914398414616,
+ 'clarke_ft' : 0.3047972654,
+ 'clarke_link' : 0.201166195164,
+ 'fathom' : 1.8288,
+ 'ft': 0.3048,
+ 'german_m' : 1.0000135965,
+ 'gold_coast_ft' : 0.304799710181508,
+ 'indian_yd' : 0.914398530744,
+ 'inch' : 0.0254,
+ 'km': 1000.0,
+ 'link' : 0.201168,
+ 'link_benoit' : 0.20116782,
+ 'link_sears' : 0.20116765,
+ 'm': 1.0,
+ 'mi': 1609.344,
+ 'mm' : 0.001,
+ 'nm': 1852.0,
+ 'nm_uk' : 1853.184,
+ 'rod' : 5.0292,
+ 'sears_yd' : 0.91439841,
+ 'survey_ft' : 0.304800609601,
+ 'um' : 0.000001,
+ 'yd': 0.9144,
+ }
+
+ # Unit aliases for `UNIT` terms encountered in Spatial Reference WKT.
+ ALIAS = {
+ 'centimeter' : 'cm',
+ 'foot' : 'ft',
+ 'inches' : 'inch',
+ 'kilometer' : 'km',
+ 'kilometre' : 'km',
+ 'meter' : 'm',
+ 'metre' : 'm',
+ 'micrometer' : 'um',
+ 'micrometre' : 'um',
+ 'millimeter' : 'mm',
+ 'millimetre' : 'mm',
+ 'mile' : 'mi',
+ 'yard' : 'yd',
+ 'British chain (Benoit 1895 B)' : 'british_chain_benoit',
+ 'British chain (Sears 1922)' : 'british_chain_sears',
+ 'British chain (Sears 1922 truncated)' : 'british_chain_sears_truncated',
+ 'British foot (Sears 1922)' : 'british_ft',
+ 'British foot' : 'british_ft',
+ 'British yard (Sears 1922)' : 'british_yd',
+ 'British yard' : 'british_yd',
+ "Clarke's Foot" : 'clarke_ft',
+ "Clarke's link" : 'clarke_link',
+ 'Chain (Benoit)' : 'chain_benoit',
+ 'Chain (Sears)' : 'chain_sears',
+ 'Foot (International)' : 'ft',
+ 'German legal metre' : 'german_m',
+ 'Gold Coast foot' : 'gold_coast_ft',
+ 'Indian yard' : 'indian_yd',
+ 'Link (Benoit)': 'link_benoit',
+ 'Link (Sears)': 'link_sears',
+ 'Nautical Mile' : 'nm',
+ 'Nautical Mile (UK)' : 'nm_uk',
+ 'US survey foot' : 'survey_ft',
+ 'U.S. Foot' : 'survey_ft',
+ 'Yard (Indian)' : 'indian_yd',
+ 'Yard (Sears)' : 'sears_yd'
+ }
+ LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
+
+ def __init__(self, default_unit=None, **kwargs):
+ # The base unit is in meters.
+ self.m, self._default_unit = self.default_units(kwargs)
+ if default_unit and isinstance(default_unit, str):
+ self._default_unit = default_unit
+
+ def __getattr__(self, name):
+ if name in self.UNITS:
+ return self.m / self.UNITS[name]
+ else:
+ raise AttributeError('Unknown unit type: %s' % name)
+
+ def __repr__(self):
+ return 'Distance(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
+
+ def __str__(self):
+ return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
+
+ def __cmp__(self, other):
+ if isinstance(other, Distance):
+ return cmp(self.m, other.m)
+ else:
+ return NotImplemented
+
+ def __add__(self, other):
+ if isinstance(other, Distance):
+ return Distance(default_unit=self._default_unit, m=(self.m + other.m))
+ else:
+ raise TypeError('Distance must be added with Distance')
+
+ def __iadd__(self, other):
+ if isinstance(other, Distance):
+ self.m += other.m
+ return self
+ else:
+ raise TypeError('Distance must be added with Distance')
+
+ def __sub__(self, other):
+ if isinstance(other, Distance):
+ return Distance(default_unit=self._default_unit, m=(self.m - other.m))
+ else:
+ raise TypeError('Distance must be subtracted from Distance')
+
+ def __isub__(self, other):
+ if isinstance(other, Distance):
+ self.m -= other.m
+ return self
+ else:
+ raise TypeError('Distance must be subtracted from Distance')
+
+ def __mul__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ return Distance(default_unit=self._default_unit, m=(self.m * float(other)))
+ elif isinstance(other, Distance):
+ return Area(default_unit='sq_' + self._default_unit, sq_m=(self.m * other.m))
+ else:
+ raise TypeError('Distance must be multiplied with number or Distance')
+
+ def __imul__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ self.m *= float(other)
+ return self
+ else:
+ raise TypeError('Distance must be multiplied with number')
+
+ def __rmul__(self, other):
+ return self * other
+
+ def __div__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ return Distance(default_unit=self._default_unit, m=(self.m / float(other)))
+ else:
+ raise TypeError('Distance must be divided with number')
+
+ def __idiv__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ self.m /= float(other)
+ return self
+ else:
+ raise TypeError('Distance must be divided with number')
+
+ def __nonzero__(self):
+ return bool(self.m)
+
+class Area(MeasureBase):
+ # Getting the square units values and the alias dictionary.
+ UNITS = dict([('sq_%s' % k, v ** 2) for k, v in Distance.UNITS.items()])
+ ALIAS = dict([(k, 'sq_%s' % v) for k, v in Distance.ALIAS.items()])
+ LALIAS = dict([(k.lower(), v) for k, v in ALIAS.items()])
+
+ def __init__(self, default_unit=None, **kwargs):
+ self.sq_m, self._default_unit = self.default_units(kwargs)
+ if default_unit and isinstance(default_unit, str):
+ self._default_unit = default_unit
+
+ def __getattr__(self, name):
+ if name in self.UNITS:
+ return self.sq_m / self.UNITS[name]
+ else:
+ raise AttributeError('Unknown unit type: ' + name)
+
+ def __repr__(self):
+ return 'Area(%s=%s)' % (self._default_unit, getattr(self, self._default_unit))
+
+ def __str__(self):
+ return '%s %s' % (getattr(self, self._default_unit), self._default_unit)
+
+ def __cmp__(self, other):
+ if isinstance(other, Area):
+ return cmp(self.sq_m, other.sq_m)
+ else:
+ return NotImplemented
+
+ def __add__(self, other):
+ if isinstance(other, Area):
+ return Area(default_unit=self._default_unit, sq_m=(self.sq_m + other.sq_m))
+ else:
+ raise TypeError('Area must be added with Area')
+
+ def __iadd__(self, other):
+ if isinstance(other, Area):
+ self.sq_m += other.sq_m
+ return self
+ else:
+ raise TypeError('Area must be added with Area')
+
+ def __sub__(self, other):
+ if isinstance(other, Area):
+ return Area(default_unit=self._default_unit, sq_m=(self.sq_m - other.sq_m))
+ else:
+ raise TypeError('Area must be subtracted from Area')
+
+ def __isub__(self, other):
+ if isinstance(other, Area):
+ self.sq_m -= other.sq_m
+ return self
+ else:
+ raise TypeError('Area must be subtracted from Area')
+
+ def __mul__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ return Area(default_unit=self._default_unit, sq_m=(self.sq_m * float(other)))
+ else:
+ raise TypeError('Area must be multiplied with number')
+
+ def __imul__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ self.sq_m *= float(other)
+ return self
+ else:
+ raise TypeError('Area must be multiplied with number')
+
+ def __rmul__(self, other):
+ return self * other
+
+ def __div__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ return Area(default_unit=self._default_unit, sq_m=(self.sq_m / float(other)))
+ else:
+ raise TypeError('Area must be divided with number')
+
+ def __idiv__(self, other):
+ if isinstance(other, (int, float, long, Decimal)):
+ self.sq_m /= float(other)
+ return self
+ else:
+ raise TypeError('Area must be divided with number')
+
+ def __nonzero__(self):
+ return bool(self.sq_m)
+
+# Shortcuts
+D = Distance
+A = Area
diff --git a/parts/django/django/contrib/gis/models.py b/parts/django/django/contrib/gis/models.py
new file mode 100644
index 0000000..e379e82
--- /dev/null
+++ b/parts/django/django/contrib/gis/models.py
@@ -0,0 +1,9 @@
+from django.db import connection
+
+if (hasattr(connection.ops, 'spatial_version') and
+ not connection.ops.mysql):
+ # Getting the `SpatialRefSys` and `GeometryColumns`
+ # models for the default spatial backend. These
+ # aliases are provided for backwards-compatibility.
+ SpatialRefSys = connection.ops.spatial_ref_sys()
+ GeometryColumns = connection.ops.geometry_columns()
diff --git a/parts/django/django/contrib/gis/shortcuts.py b/parts/django/django/contrib/gis/shortcuts.py
new file mode 100644
index 0000000..a6fb892
--- /dev/null
+++ b/parts/django/django/contrib/gis/shortcuts.py
@@ -0,0 +1,32 @@
+import cStringIO, zipfile
+from django.conf import settings
+from django.http import HttpResponse
+from django.template import loader
+
+def compress_kml(kml):
+ "Returns compressed KMZ from the given KML string."
+ kmz = cStringIO.StringIO()
+ zf = zipfile.ZipFile(kmz, 'a', zipfile.ZIP_DEFLATED)
+ zf.writestr('doc.kml', kml.encode(settings.DEFAULT_CHARSET))
+ zf.close()
+ kmz.seek(0)
+ return kmz.read()
+
+def render_to_kml(*args, **kwargs):
+ "Renders the response as KML (using the correct MIME type)."
+ return HttpResponse(loader.render_to_string(*args, **kwargs),
+ mimetype='application/vnd.google-earth.kml+xml')
+
+def render_to_kmz(*args, **kwargs):
+ """
+ Compresses the KML content and returns as KMZ (using the correct
+ MIME type).
+ """
+ return HttpResponse(compress_kml(loader.render_to_string(*args, **kwargs)),
+ mimetype='application/vnd.google-earth.kmz')
+
+
+def render_to_text(*args, **kwargs):
+ "Renders the response using the MIME type for plain text."
+ return HttpResponse(loader.render_to_string(*args, **kwargs),
+ mimetype='text/plain')
diff --git a/parts/django/django/contrib/gis/sitemaps/__init__.py b/parts/django/django/contrib/gis/sitemaps/__init__.py
new file mode 100644
index 0000000..9b6287f
--- /dev/null
+++ b/parts/django/django/contrib/gis/sitemaps/__init__.py
@@ -0,0 +1,4 @@
+# Geo-enabled Sitemap classes.
+from django.contrib.gis.sitemaps.georss import GeoRSSSitemap
+from django.contrib.gis.sitemaps.kml import KMLSitemap, KMZSitemap
+
diff --git a/parts/django/django/contrib/gis/sitemaps/georss.py b/parts/django/django/contrib/gis/sitemaps/georss.py
new file mode 100644
index 0000000..f75cf80
--- /dev/null
+++ b/parts/django/django/contrib/gis/sitemaps/georss.py
@@ -0,0 +1,53 @@
+from django.core import urlresolvers
+from django.contrib.sitemaps import Sitemap
+
+class GeoRSSSitemap(Sitemap):
+ """
+ A minimal hook to produce sitemaps for GeoRSS feeds.
+ """
+ def __init__(self, feed_dict, slug_dict=None):
+ """
+ This sitemap object initializes on a feed dictionary (as would be passed
+ to `django.contrib.syndication.views.feed`) and a slug dictionary.
+ If the slug dictionary is not defined, then it's assumed the keys provide
+ the URL parameter to the feed. However, if you have a complex feed (e.g.,
+ you override `get_object`, then you'll need to provide a slug dictionary.
+ The slug dictionary should have the same keys as the feed dictionary, but
+ each value in the slug dictionary should be a sequence of slugs that may
+ be used for valid feeds. For example, let's say we have a feed that
+ returns objects for a specific ZIP code in our feed dictionary:
+
+ feed_dict = {'zipcode' : ZipFeed}
+
+ Then we would use a slug dictionary with a list of the zip code slugs
+ corresponding to feeds you want listed in the sitemap:
+
+ slug_dict = {'zipcode' : ['77002', '77054']}
+ """
+ # Setting up.
+ self.feed_dict = feed_dict
+ self.locations = []
+ if slug_dict is None: slug_dict = {}
+ # Getting the feed locations.
+ for section in feed_dict.keys():
+ if slug_dict.get(section, False):
+ for slug in slug_dict[section]:
+ self.locations.append('%s/%s' % (section, slug))
+ else:
+ self.locations.append(section)
+
+ def get_urls(self, page=1, site=None):
+ """
+ This method is overrridden so the appropriate `geo_format` attribute
+ is placed on each URL element.
+ """
+ urls = Sitemap.get_urls(self, page=page, site=site)
+ for url in urls: url['geo_format'] = 'georss'
+ return urls
+
+ def items(self):
+ return self.locations
+
+ def location(self, obj):
+ return urlresolvers.reverse('django.contrib.syndication.views.feed', args=(obj,))
+
diff --git a/parts/django/django/contrib/gis/sitemaps/kml.py b/parts/django/django/contrib/gis/sitemaps/kml.py
new file mode 100644
index 0000000..db30606
--- /dev/null
+++ b/parts/django/django/contrib/gis/sitemaps/kml.py
@@ -0,0 +1,63 @@
+from django.core import urlresolvers
+from django.contrib.sitemaps import Sitemap
+from django.contrib.gis.db.models.fields import GeometryField
+from django.db import models
+
+class KMLSitemap(Sitemap):
+ """
+ A minimal hook to produce KML sitemaps.
+ """
+ geo_format = 'kml'
+
+ def __init__(self, locations=None):
+ # If no locations specified, then we try to build for
+ # every model in installed applications.
+ self.locations = self._build_kml_sources(locations)
+
+ def _build_kml_sources(self, sources):
+ """
+ Goes through the given sources and returns a 3-tuple of
+ the application label, module name, and field name of every
+ GeometryField encountered in the sources.
+
+ If no sources are provided, then all models.
+ """
+ kml_sources = []
+ if sources is None:
+ sources = models.get_models()
+ for source in sources:
+ if isinstance(source, models.base.ModelBase):
+ for field in source._meta.fields:
+ if isinstance(field, GeometryField):
+ kml_sources.append((source._meta.app_label,
+ source._meta.module_name,
+ field.name))
+ elif isinstance(source, (list, tuple)):
+ if len(source) != 3:
+ raise ValueError('Must specify a 3-tuple of (app_label, module_name, field_name).')
+ kml_sources.append(source)
+ else:
+ raise TypeError('KML Sources must be a model or a 3-tuple.')
+ return kml_sources
+
+ def get_urls(self, page=1, site=None):
+ """
+ This method is overrridden so the appropriate `geo_format` attribute
+ is placed on each URL element.
+ """
+ urls = Sitemap.get_urls(self, page=page, site=site)
+ for url in urls: url['geo_format'] = self.geo_format
+ return urls
+
+ def items(self):
+ return self.locations
+
+ def location(self, obj):
+ return urlresolvers.reverse('django.contrib.gis.sitemaps.views.%s' % self.geo_format,
+ kwargs={'label' : obj[0],
+ 'model' : obj[1],
+ 'field_name': obj[2],
+ }
+ )
+class KMZSitemap(KMLSitemap):
+ geo_format = 'kmz'
diff --git a/parts/django/django/contrib/gis/sitemaps/views.py b/parts/django/django/contrib/gis/sitemaps/views.py
new file mode 100644
index 0000000..02a0fc0
--- /dev/null
+++ b/parts/django/django/contrib/gis/sitemaps/views.py
@@ -0,0 +1,111 @@
+from django.http import HttpResponse, Http404
+from django.template import loader
+from django.contrib.sites.models import get_current_site
+from django.core import urlresolvers
+from django.core.paginator import EmptyPage, PageNotAnInteger
+from django.contrib.gis.db.models.fields import GeometryField
+from django.db import connections, DEFAULT_DB_ALIAS
+from django.db.models import get_model
+from django.utils.encoding import smart_str
+
+from django.contrib.gis.shortcuts import render_to_kml, render_to_kmz
+
+def index(request, sitemaps):
+ """
+ This view generates a sitemap index that uses the proper view
+ for resolving geographic section sitemap URLs.
+ """
+ current_site = get_current_site(request)
+ sites = []
+ protocol = request.is_secure() and 'https' or 'http'
+ for section, site in sitemaps.items():
+ if callable(site):
+ pages = site().paginator.num_pages
+ else:
+ pages = site.paginator.num_pages
+ sitemap_url = urlresolvers.reverse('django.contrib.gis.sitemaps.views.sitemap', kwargs={'section': section})
+ sites.append('%s://%s%s' % (protocol, current_site.domain, sitemap_url))
+
+ if pages > 1:
+ for page in range(2, pages+1):
+ sites.append('%s://%s%s?p=%s' % (protocol, current_site.domain, sitemap_url, page))
+ xml = loader.render_to_string('sitemap_index.xml', {'sitemaps': sites})
+ return HttpResponse(xml, mimetype='application/xml')
+
+def sitemap(request, sitemaps, section=None):
+ """
+ This view generates a sitemap with additional geographic
+ elements defined by Google.
+ """
+ maps, urls = [], []
+ if section is not None:
+ if section not in sitemaps:
+ raise Http404("No sitemap available for section: %r" % section)
+ maps.append(sitemaps[section])
+ else:
+ maps = sitemaps.values()
+
+ page = request.GET.get("p", 1)
+ current_site = get_current_site(request)
+ for site in maps:
+ try:
+ if callable(site):
+ urls.extend(site().get_urls(page=page, site=current_site))
+ else:
+ urls.extend(site.get_urls(page=page, site=current_site))
+ except EmptyPage:
+ raise Http404("Page %s empty" % page)
+ except PageNotAnInteger:
+ raise Http404("No page '%s'" % page)
+ xml = smart_str(loader.render_to_string('gis/sitemaps/geo_sitemap.xml', {'urlset': urls}))
+ return HttpResponse(xml, mimetype='application/xml')
+
+def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB_ALIAS):
+ """
+ This view generates KML for the given app label, model, and field name.
+
+ The model's default manager must be GeoManager, and the field name
+ must be that of a geographic field.
+ """
+ placemarks = []
+ klass = get_model(label, model)
+ if not klass:
+ raise Http404('You must supply a valid app label and module name. Got "%s.%s"' % (label, model))
+
+ if field_name:
+ try:
+ info = klass._meta.get_field_by_name(field_name)
+ if not isinstance(info[0], GeometryField):
+ raise Exception
+ except:
+ raise Http404('Invalid geometry field.')
+
+ connection = connections[using]
+
+ if connection.ops.postgis:
+ # PostGIS will take care of transformation.
+ placemarks = klass._default_manager.using(using).kml(field_name=field_name)
+ else:
+ # There's no KML method on Oracle or MySQL, so we use the `kml`
+ # attribute of the lazy geometry instead.
+ placemarks = []
+ if connection.ops.oracle:
+ qs = klass._default_manager.using(using).transform(4326, field_name=field_name)
+ else:
+ qs = klass._default_manager.using(using).all()
+ for mod in qs:
+ setattr(mod, 'kml', getattr(mod, field_name).kml)
+ placemarks.append(mod)
+
+ # Getting the render function and rendering to the correct.
+ if compress:
+ render = render_to_kmz
+ else:
+ render = render_to_kml
+ return render('gis/kml/placemarks.kml', {'places' : placemarks})
+
+def kmz(request, label, model, field_name=None, using=DEFAULT_DB_ALIAS):
+ """
+ This view returns KMZ for the given app label, model, and field name.
+ """
+ return kml(request, label, model, field_name, compress=True, using=using)
diff --git a/parts/django/django/contrib/gis/templates/gis/admin/openlayers.html b/parts/django/django/contrib/gis/templates/gis/admin/openlayers.html
new file mode 100644
index 0000000..4292eb6
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/admin/openlayers.html
@@ -0,0 +1,37 @@
+{% block extrastyle %}
+<style type="text/css">
+ #{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; }
+ #{{ id }}_map .aligned label { float:inherit; }
+ #{{ id }}_admin_map { position: relative; vertical-align: top; float: {{ LANGUAGE_BIDI|yesno:"right,left" }}; }
+ {% if not display_wkt %}#{{ id }} { display: none; }{% endif %}
+ .olControlEditingToolbar .olControlModifyFeatureItemActive {
+ background-image: url("{{ ADMIN_MEDIA_PREFIX }}img/gis/move_vertex_on.png");
+ background-repeat: no-repeat;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemInactive {
+ background-image: url("{{ ADMIN_MEDIA_PREFIX }}img/gis/move_vertex_off.png");
+ background-repeat: no-repeat;
+ }
+</style>
+<!--[if IE]>
+<style type="text/css">
+ /* This fixes the mouse offset issues in IE. */
+ #{{ id }}_admin_map { position: static; vertical-align: top; }
+ /* `font-size: 0` fixes the 1px border between tiles, but borks LayerSwitcher.
+ Thus, this is disabled until a better fix is found.
+ #{{ id }}_map { width: {{ map_width }}px; height: {{ map_height }}px; font-size: 0; } */
+</style>
+<![endif]-->
+{% endblock %}
+<span id="{{ id }}_admin_map">
+<script type="text/javascript">
+//<![CDATA[
+{% block openlayers %}{% include "gis/admin/openlayers.js" %}{% endblock %}
+//]]>
+</script>
+<div id="{{ id }}_map"{% if LANGUAGE_BIDI %} dir="ltr"{% endif %}></div>
+<a href="javascript:{{ module }}.clearFeatures()">Delete all Features</a>
+{% if display_wkt %}<p> WKT debugging window:</p>{% endif %}
+<textarea id="{{ id }}" class="vWKTField required" cols="150" rows="10" name="{{ name }}">{{ wkt }}</textarea>
+<script type="text/javascript">{% block init_function %}{{ module }}.init();{% endblock %}</script>
+</span>
diff --git a/parts/django/django/contrib/gis/templates/gis/admin/openlayers.js b/parts/django/django/contrib/gis/templates/gis/admin/openlayers.js
new file mode 100644
index 0000000..4324693
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/admin/openlayers.js
@@ -0,0 +1,167 @@
+{# Author: Justin Bronn, Travis Pinney & Dane Springmeyer #}
+{% block vars %}var {{ module }} = {};
+{{ module }}.map = null; {{ module }}.controls = null; {{ module }}.panel = null; {{ module }}.re = new RegExp("^SRID=\d+;(.+)", "i"); {{ module }}.layers = {};
+{{ module }}.modifiable = {{ modifiable|yesno:"true,false" }};
+{{ module }}.wkt_f = new OpenLayers.Format.WKT();
+{{ module }}.is_collection = {{ is_collection|yesno:"true,false" }};
+{{ module }}.collection_type = '{{ collection_type }}';
+{{ module }}.is_linestring = {{ is_linestring|yesno:"true,false" }};
+{{ module }}.is_polygon = {{ is_polygon|yesno:"true,false" }};
+{{ module }}.is_point = {{ is_point|yesno:"true,false" }};
+{% endblock %}
+{{ module }}.get_ewkt = function(feat){return 'SRID={{ srid }};' + {{ module }}.wkt_f.write(feat);}
+{{ module }}.read_wkt = function(wkt){
+ // OpenLayers cannot handle EWKT -- we make sure to strip it out.
+ // EWKT is only exposed to OL if there's a validation error in the admin.
+ var match = {{ module }}.re.exec(wkt);
+ if (match){wkt = match[1];}
+ return {{ module }}.wkt_f.read(wkt);
+}
+{{ module }}.write_wkt = function(feat){
+ if ({{ module }}.is_collection){ {{ module }}.num_geom = feat.geometry.components.length;}
+ else { {{ module }}.num_geom = 1;}
+ document.getElementById('{{ id }}').value = {{ module }}.get_ewkt(feat);
+}
+{{ module }}.add_wkt = function(event){
+ // This function will sync the contents of the `vector` layer with the
+ // WKT in the text field.
+ if ({{ module }}.is_collection){
+ var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}());
+ for (var i = 0; i < {{ module }}.layers.vector.features.length; i++){
+ feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]);
+ }
+ {{ module }}.write_wkt(feat);
+ } else {
+ // Make sure to remove any previously added features.
+ if ({{ module }}.layers.vector.features.length > 1){
+ old_feats = [{{ module }}.layers.vector.features[0]];
+ {{ module }}.layers.vector.removeFeatures(old_feats);
+ {{ module }}.layers.vector.destroyFeatures(old_feats);
+ }
+ {{ module }}.write_wkt(event.feature);
+ }
+}
+{{ module }}.modify_wkt = function(event){
+ if ({{ module }}.is_collection){
+ if ({{ module }}.is_point){
+ {{ module }}.add_wkt(event);
+ return;
+ } else {
+ // When modifying the selected components are added to the
+ // vector layer so we only increment to the `num_geom` value.
+ var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.{{ geom_type }}());
+ for (var i = 0; i < {{ module }}.num_geom; i++){
+ feat.geometry.addComponents([{{ module }}.layers.vector.features[i].geometry]);
+ }
+ {{ module }}.write_wkt(feat);
+ }
+ } else {
+ {{ module }}.write_wkt(event.feature);
+ }
+}
+// Function to clear vector features and purge wkt from div
+{{ module }}.deleteFeatures = function(){
+ {{ module }}.layers.vector.removeFeatures({{ module }}.layers.vector.features);
+ {{ module }}.layers.vector.destroyFeatures();
+}
+{{ module }}.clearFeatures = function (){
+ {{ module }}.deleteFeatures();
+ document.getElementById('{{ id }}').value = '';
+ {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }});
+}
+// Add Select control
+{{ module }}.addSelectControl = function(){
+ var select = new OpenLayers.Control.SelectFeature({{ module }}.layers.vector, {'toggle' : true, 'clickout' : true});
+ {{ module }}.map.addControl(select);
+ select.activate();
+}
+{{ module }}.enableDrawing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.DrawFeature')[0].activate();}
+{{ module }}.enableEditing = function(){ {{ module }}.map.getControlsByClass('OpenLayers.Control.ModifyFeature')[0].activate();}
+// Create an array of controls based on geometry type
+{{ module }}.getControls = function(lyr){
+ {{ module }}.panel = new OpenLayers.Control.Panel({'displayClass': 'olControlEditingToolbar'});
+ var nav = new OpenLayers.Control.Navigation();
+ var draw_ctl;
+ if ({{ module }}.is_linestring){
+ draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'});
+ } else if ({{ module }}.is_polygon){
+ draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'});
+ } else if ({{ module }}.is_point){
+ draw_ctl = new OpenLayers.Control.DrawFeature(lyr, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'});
+ }
+ if ({{ module }}.modifiable){
+ var mod = new OpenLayers.Control.ModifyFeature(lyr, {'displayClass': 'olControlModifyFeature'});
+ {{ module }}.controls = [nav, draw_ctl, mod];
+ } else {
+ if(!lyr.features.length){
+ {{ module }}.controls = [nav, draw_ctl];
+ } else {
+ {{ module }}.controls = [nav];
+ }
+ }
+}
+{{ module }}.init = function(){
+ {% block map_options %}// The options hash, w/ zoom, resolution, and projection settings.
+ var options = {
+{% autoescape off %}{% for item in map_options.items %} '{{ item.0 }}' : {{ item.1 }}{% if not forloop.last %},{% endif %}
+{% endfor %}{% endautoescape %} };{% endblock %}
+ // The admin map for this geometry field.
+ {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options);
+ // Base Layer
+ {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS( "{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'} );{% endblock %}
+ {{ module }}.map.addLayer({{ module }}.layers.base);
+ {% block extra_layers %}{% endblock %}
+ {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %}
+ {{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}");
+ {{ module }}.map.addLayer({{ module }}.layers.vector);
+ // Read WKT from the text field.
+ var wkt = document.getElementById('{{ id }}').value;
+ if (wkt){
+ // After reading into geometry, immediately write back to
+ // WKT <textarea> as EWKT (so that SRID is included).
+ var admin_geom = {{ module }}.read_wkt(wkt);
+ {{ module }}.write_wkt(admin_geom);
+ if ({{ module }}.is_collection){
+ // If geometry collection, add each component individually so they may be
+ // edited individually.
+ for (var i = 0; i < {{ module }}.num_geom; i++){
+ {{ module }}.layers.vector.addFeatures([new OpenLayers.Feature.Vector(admin_geom.geometry.components[i].clone())]);
+ }
+ } else {
+ {{ module }}.layers.vector.addFeatures([admin_geom]);
+ }
+ // Zooming to the bounds.
+ {{ module }}.map.zoomToExtent(admin_geom.geometry.getBounds());
+ if ({{ module }}.is_point){
+ {{ module }}.map.zoomTo({{ point_zoom }});
+ }
+ } else {
+ {{ module }}.map.setCenter(new OpenLayers.LonLat({{ default_lon }}, {{ default_lat }}), {{ default_zoom }});
+ }
+ // This allows editing of the geographic fields -- the modified WKT is
+ // written back to the content field (as EWKT, so that the ORM will know
+ // to transform back to original SRID).
+ {{ module }}.layers.vector.events.on({"featuremodified" : {{ module }}.modify_wkt});
+ {{ module }}.layers.vector.events.on({"featureadded" : {{ module }}.add_wkt});
+ {% block controls %}
+ // Map controls:
+ // Add geometry specific panel of toolbar controls
+ {{ module }}.getControls({{ module }}.layers.vector);
+ {{ module }}.panel.addControls({{ module }}.controls);
+ {{ module }}.map.addControl({{ module }}.panel);
+ {{ module }}.addSelectControl();
+ // Then add optional visual controls
+ {% if mouse_position %}{{ module }}.map.addControl(new OpenLayers.Control.MousePosition());{% endif %}
+ {% if scale_text %}{{ module }}.map.addControl(new OpenLayers.Control.Scale());{% endif %}
+ {% if layerswitcher %}{{ module }}.map.addControl(new OpenLayers.Control.LayerSwitcher());{% endif %}
+ // Then add optional behavior controls
+ {% if not scrollable %}{{ module }}.map.getControlsByClass('OpenLayers.Control.Navigation')[0].disableZoomWheel();{% endif %}
+ {% endblock %}
+ if (wkt){
+ if ({{ module }}.modifiable){
+ {{ module }}.enableEditing();
+ }
+ } else {
+ {{ module }}.enableDrawing();
+ }
+}
diff --git a/parts/django/django/contrib/gis/templates/gis/admin/osm.html b/parts/django/django/contrib/gis/templates/gis/admin/osm.html
new file mode 100644
index 0000000..b74b41f
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/admin/osm.html
@@ -0,0 +1,2 @@
+{% extends "gis/admin/openlayers.html" %}
+{% block openlayers %}{% include "gis/admin/osm.js" %}{% endblock %} \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/templates/gis/admin/osm.js b/parts/django/django/contrib/gis/templates/gis/admin/osm.js
new file mode 100644
index 0000000..2a1f59e
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/admin/osm.js
@@ -0,0 +1,2 @@
+{% extends "gis/admin/openlayers.js" %}
+{% block base_layer %}new OpenLayers.Layer.OSM.Mapnik("OpenStreetMap (Mapnik)");{% endblock %}
diff --git a/parts/django/django/contrib/gis/templates/gis/google/google-map.html b/parts/django/django/contrib/gis/templates/gis/google/google-map.html
new file mode 100644
index 0000000..fb60e44
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/google/google-map.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" {{ gmap.xmlns }}>
+<head>
+ <title>{% block title %}Google Maps via GeoDjango{% endblock %}</title>
+ {{ gmap.style }}
+ {{ gmap.scripts }}
+</head>
+<body {{ gmap.onload }} {{ gmap.onunload }}>
+{% if gmap.dom_ids %}{% for dom_id in gmap.dom_ids %}<div id="{{ dom_id }}" style="width:600px;height:400px;"></div>{% endfor %}
+{% else %}<div id="{{ gmap.dom_id }}" style="width:600px;height:400px;"></div>{% endif %}
+</body>
+</html>
diff --git a/parts/django/django/contrib/gis/templates/gis/google/google-map.js b/parts/django/django/contrib/gis/templates/gis/google/google-map.js
new file mode 100644
index 0000000..06f11e3
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/google/google-map.js
@@ -0,0 +1,35 @@
+{% autoescape off %}
+{% block vars %}var geodjango = {};{% for icon in icons %}
+var {{ icon.varname }} = new GIcon(G_DEFAULT_ICON);
+{% if icon.image %}{{ icon.varname }}.image = "{{ icon.image }}";{% endif %}
+{% if icon.shadow %}{{ icon.varname }}.shadow = "{{ icon.shadow }}";{% endif %} {% if icon.shadowsize %}{{ icon.varname }}.shadowSize = new GSize({{ icon.shadowsize.0 }}, {{ icon.shadowsize.1 }});{% endif %}
+{% if icon.iconanchor %}{{ icon.varname }}.iconAnchor = new GPoint({{ icon.iconanchor.0 }}, {{ icon.iconanchor.1 }});{% endif %} {% if icon.iconsize %}{{ icon.varname }}.iconSize = new GSize({{ icon.iconsize.0 }}, {{ icon.iconsize.1 }});{% endif %}
+{% if icon.infowindowanchor %}{{ icon.varname }}.infoWindowAnchor = new GPoint({{ icon.infowindowanchor.0 }}, {{ icon.infowindowanchor.1 }});{% endif %}{% endfor %}
+{% endblock vars %}{% block functions %}
+{% block load %}{{ js_module }}.{{ dom_id }}_load = function(){
+ if (GBrowserIsCompatible()) {
+ {{ js_module }}.{{ dom_id }} = new GMap2(document.getElementById("{{ dom_id }}"));
+ {{ js_module }}.{{ dom_id }}.setCenter(new GLatLng({{ center.1 }}, {{ center.0 }}), {{ zoom }});
+ {% block controls %}{{ js_module }}.{{ dom_id }}.setUIToDefault();{% endblock %}
+ {% if calc_zoom %}var bounds = new GLatLngBounds(); var tmp_bounds = new GLatLngBounds();{% endif %}
+ {% for kml_url in kml_urls %}{{ js_module }}.{{ dom_id }}_kml{{ forloop.counter }} = new GGeoXml("{{ kml_url }}");
+ {{ js_module }}.{{ dom_id }}.addOverlay({{ js_module }}.{{ dom_id }}_kml{{ forloop.counter }});{% endfor %}
+ {% for polygon in polygons %}{{ js_module }}.{{ dom_id }}_poly{{ forloop.counter }} = new {{ polygon }};
+ {{ js_module }}.{{ dom_id }}.addOverlay({{ js_module }}.{{ dom_id }}_poly{{ forloop.counter }});
+ {% for event in polygon.events %}GEvent.addListener({{ js_module }}.{{ dom_id }}_poly{{ forloop.parentloop.counter }}, {{ event }});{% endfor %}
+ {% if calc_zoom %}tmp_bounds = {{ js_module }}.{{ dom_id }}_poly{{ forloop.counter }}.getBounds(); bounds.extend(tmp_bounds.getSouthWest()); bounds.extend(tmp_bounds.getNorthEast());{% endif %}{% endfor %}
+ {% for polyline in polylines %}{{ js_module }}.{{ dom_id }}_polyline{{ forloop.counter }} = new {{ polyline }};
+ {{ js_module }}.{{ dom_id }}.addOverlay({{ js_module }}.{{ dom_id }}_polyline{{ forloop.counter }});
+ {% for event in polyline.events %}GEvent.addListener({{ js_module }}.{{ dom_id }}_polyline{{ forloop.parentloop.counter }}, {{ event }}); {% endfor %}
+ {% if calc_zoom %}tmp_bounds = {{ js_module }}.{{ dom_id }}_polyline{{ forloop.counter }}.getBounds(); bounds.extend(tmp_bounds.getSouthWest()); bounds.extend(tmp_bounds.getNorthEast());{% endif %}{% endfor %}
+ {% for marker in markers %}{{ js_module }}.{{ dom_id }}_marker{{ forloop.counter }} = new {{ marker }};
+ {{ js_module }}.{{ dom_id }}.addOverlay({{ js_module }}.{{ dom_id }}_marker{{ forloop.counter }});
+ {% for event in marker.events %}GEvent.addListener({{ js_module }}.{{ dom_id }}_marker{{ forloop.parentloop.counter }}, {{ event }}); {% endfor %}
+ {% if calc_zoom %}bounds.extend({{ js_module }}.{{ dom_id }}_marker{{ forloop.counter }}.getLatLng()); {% endif %}{% endfor %}
+ {% if calc_zoom %}{{ js_module }}.{{ dom_id }}.setCenter(bounds.getCenter(), {{ js_module }}.{{ dom_id }}.getBoundsZoomLevel(bounds));{% endif %}
+ {% block load_extra %}{% endblock %}
+ }else {
+ alert("Sorry, the Google Maps API is not compatible with this browser.");
+ }
+}
+{% endblock load %}{% endblock functions %}{% endautoescape %}
diff --git a/parts/django/django/contrib/gis/templates/gis/google/google-multi.js b/parts/django/django/contrib/gis/templates/gis/google/google-multi.js
new file mode 100644
index 0000000..e3c7e8f
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/google/google-multi.js
@@ -0,0 +1,8 @@
+{% extends "gis/google/google-map.js" %}
+{% block functions %}
+{{ load_map_js }}
+{{ js_module }}.load = function(){
+ {% for dom_id in dom_ids %}{{ js_module }}.{{ dom_id }}_load();
+ {% endfor %}
+}
+{% endblock %} \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/templates/gis/google/google-single.js b/parts/django/django/contrib/gis/templates/gis/google/google-single.js
new file mode 100644
index 0000000..b930e45
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/google/google-single.js
@@ -0,0 +1,2 @@
+{% extends "gis/google/google-map.js" %}
+{% block vars %}{# No vars here because used within GoogleMapSet #}{% endblock %} \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/templates/gis/kml/base.kml b/parts/django/django/contrib/gis/templates/gis/kml/base.kml
new file mode 100644
index 0000000..374404c
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/kml/base.kml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://earth.google.com/kml/{% block kml_version %}2.1{% endblock %}">
+<Document>{% block name %}{% endblock %}
+{% block placemarks %}{% endblock %}
+</Document>
+</kml>
diff --git a/parts/django/django/contrib/gis/templates/gis/kml/placemarks.kml b/parts/django/django/contrib/gis/templates/gis/kml/placemarks.kml
new file mode 100644
index 0000000..ea2ac19
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/kml/placemarks.kml
@@ -0,0 +1,8 @@
+{% extends "gis/kml/base.kml" %}
+{% block placemarks %}{% for place in places %}
+ <Placemark>
+ <name>{% if place.name %}{{ place.name }}{% else %}{{ place }}{% endif %}</name>
+ <description>{% if place.description %}{{ place.description }}{% else %}{{ place }}{% endif %}</description>
+ {{ place.kml|safe }}
+ </Placemark>{% endfor %}{% endblock %}
+
diff --git a/parts/django/django/contrib/gis/templates/gis/sitemaps/geo_sitemap.xml b/parts/django/django/contrib/gis/templates/gis/sitemaps/geo_sitemap.xml
new file mode 100644
index 0000000..dbf858e
--- /dev/null
+++ b/parts/django/django/contrib/gis/templates/gis/sitemaps/geo_sitemap.xml
@@ -0,0 +1,17 @@
+{% autoescape off %}<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:geo="http://www.google.com/geo/schemas/sitemap/1.0">
+{% spaceless %}
+{% for url in urlset %}
+ <url>
+ <loc>{{ url.location|escape }}</loc>
+ {% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
+ {% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
+ {% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
+ {% if url.geo_format %}<geo:geo>
+ <geo:format>{{ url.geo_format }}</geo:format>
+ </geo:geo>{% endif %}
+ </url>
+{% endfor %}
+{% endspaceless %}
+</urlset>
+{% endautoescape %}
diff --git a/parts/django/django/contrib/gis/tests/__init__.py b/parts/django/django/contrib/gis/tests/__init__.py
new file mode 100644
index 0000000..138c291
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/__init__.py
@@ -0,0 +1,141 @@
+import unittest
+
+from django.conf import settings
+from django.test.simple import build_suite, DjangoTestSuiteRunner
+
+
+def run_tests(*args, **kwargs):
+ from django.test.simple import run_tests as base_run_tests
+ return base_run_tests(*args, **kwargs)
+
+
+def run_gis_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=None):
+ import warnings
+ warnings.warn(
+ 'The run_gis_tests() test runner has been deprecated in favor of GeoDjangoTestSuiteRunner.',
+ PendingDeprecationWarning
+ )
+ test_runner = GeoDjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
+ return test_runner.run_tests(test_labels, extra_tests=extra_tests)
+
+
+def geo_apps(namespace=True, runtests=False):
+ """
+ Returns a list of GeoDjango test applications that reside in
+ `django.contrib.gis.tests` that can be used with the current
+ database and the spatial libraries that are installed.
+ """
+ from django.db import connection
+ from django.contrib.gis.geos import GEOS_PREPARE
+ from django.contrib.gis.gdal import HAS_GDAL
+
+ apps = ['geoapp', 'relatedapp']
+
+ # No distance queries on MySQL.
+ if not connection.ops.mysql:
+ apps.append('distapp')
+
+ # Test geography support with PostGIS 1.5+.
+ if connection.ops.postgis and connection.ops.geography:
+ apps.append('geogapp')
+
+ # The following GeoDjango test apps depend on GDAL support.
+ if HAS_GDAL:
+ # 3D apps use LayerMapping, which uses GDAL.
+ if connection.ops.postgis and GEOS_PREPARE:
+ apps.append('geo3d')
+
+ apps.append('layermap')
+
+ if runtests:
+ return [('django.contrib.gis.tests', app) for app in apps]
+ elif namespace:
+ return ['django.contrib.gis.tests.%s' % app
+ for app in apps]
+ else:
+ return apps
+
+
+def geodjango_suite(apps=True):
+ """
+ Returns a TestSuite consisting only of GeoDjango tests that can be run.
+ """
+ import sys
+ from django.db.models import get_app
+
+ suite = unittest.TestSuite()
+
+ # Adding the GEOS tests.
+ from django.contrib.gis.geos import tests as geos_tests
+ suite.addTest(geos_tests.suite())
+
+ # Adding the measurment tests.
+ from django.contrib.gis.tests import test_measure
+ suite.addTest(test_measure.suite())
+
+ # Adding GDAL tests, and any test suite that depends on GDAL, to the
+ # suite if GDAL is available.
+ from django.contrib.gis.gdal import HAS_GDAL
+ if HAS_GDAL:
+ from django.contrib.gis.gdal import tests as gdal_tests
+ suite.addTest(gdal_tests.suite())
+
+ from django.contrib.gis.tests import test_spatialrefsys, test_geoforms
+ suite.addTest(test_spatialrefsys.suite())
+ suite.addTest(test_geoforms.suite())
+ else:
+ sys.stderr.write('GDAL not available - no tests requiring GDAL will be run.\n')
+
+ # Add GeoIP tests to the suite, if the library and data is available.
+ from django.contrib.gis.utils import HAS_GEOIP
+ if HAS_GEOIP and hasattr(settings, 'GEOIP_PATH'):
+ from django.contrib.gis.tests import test_geoip
+ suite.addTest(test_geoip.suite())
+
+ # Finally, adding the suites for each of the GeoDjango test apps.
+ if apps:
+ for app_name in geo_apps(namespace=False):
+ suite.addTest(build_suite(get_app(app_name)))
+
+ return suite
+
+
+class GeoDjangoTestSuiteRunner(DjangoTestSuiteRunner):
+
+ def setup_test_environment(self, **kwargs):
+ super(GeoDjangoTestSuiteRunner, self).setup_test_environment(**kwargs)
+
+ # Saving original values of INSTALLED_APPS, ROOT_URLCONF, and SITE_ID.
+ self.old_installed = getattr(settings, 'INSTALLED_APPS', None)
+ self.old_root_urlconf = getattr(settings, 'ROOT_URLCONF', '')
+ self.old_site_id = getattr(settings, 'SITE_ID', None)
+
+ # Constructing the new INSTALLED_APPS, and including applications
+ # within the GeoDjango test namespace.
+ new_installed = ['django.contrib.sites',
+ 'django.contrib.sitemaps',
+ 'django.contrib.gis',
+ ]
+
+ # Calling out to `geo_apps` to get GeoDjango applications supported
+ # for testing.
+ new_installed.extend(geo_apps())
+ settings.INSTALLED_APPS = new_installed
+
+ # SITE_ID needs to be set
+ settings.SITE_ID = 1
+
+ # ROOT_URLCONF needs to be set, else `AttributeErrors` are raised
+ # when TestCases are torn down that have `urls` defined.
+ settings.ROOT_URLCONF = ''
+
+
+ def teardown_test_environment(self, **kwargs):
+ super(GeoDjangoTestSuiteRunner, self).teardown_test_environment(**kwargs)
+ settings.INSTALLED_APPS = self.old_installed
+ settings.ROOT_URLCONF = self.old_root_urlconf
+ settings.SITE_ID = self.old_site_id
+
+
+ def build_suite(self, test_labels, extra_tests=None, **kwargs):
+ return geodjango_suite()
diff --git a/parts/django/django/contrib/gis/tests/data/cities/cities.dbf b/parts/django/django/contrib/gis/tests/data/cities/cities.dbf
new file mode 100644
index 0000000..8b27633
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/cities/cities.dbf
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/cities/cities.prj b/parts/django/django/contrib/gis/tests/data/cities/cities.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/cities/cities.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/tests/data/cities/cities.shp b/parts/django/django/contrib/gis/tests/data/cities/cities.shp
new file mode 100644
index 0000000..1c46ccc
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/cities/cities.shp
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/cities/cities.shx b/parts/django/django/contrib/gis/tests/data/cities/cities.shx
new file mode 100644
index 0000000..6be3fd6
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/cities/cities.shx
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/counties/counties.dbf b/parts/django/django/contrib/gis/tests/data/counties/counties.dbf
new file mode 100644
index 0000000..ccdbb26
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/counties/counties.dbf
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/counties/counties.shp b/parts/django/django/contrib/gis/tests/data/counties/counties.shp
new file mode 100644
index 0000000..2e7dca5
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/counties/counties.shp
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/counties/counties.shx b/parts/django/django/contrib/gis/tests/data/counties/counties.shx
new file mode 100644
index 0000000..46ed3bb
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/counties/counties.shx
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/geometries.json.gz b/parts/django/django/contrib/gis/tests/data/geometries.json.gz
new file mode 100644
index 0000000..683dc83
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/geometries.json.gz
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/interstates/interstates.dbf b/parts/django/django/contrib/gis/tests/data/interstates/interstates.dbf
new file mode 100644
index 0000000..a88d171
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/interstates/interstates.dbf
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/interstates/interstates.prj b/parts/django/django/contrib/gis/tests/data/interstates/interstates.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/interstates/interstates.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/tests/data/interstates/interstates.shp b/parts/django/django/contrib/gis/tests/data/interstates/interstates.shp
new file mode 100644
index 0000000..6d93de7
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/interstates/interstates.shp
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/interstates/interstates.shx b/parts/django/django/contrib/gis/tests/data/interstates/interstates.shx
new file mode 100644
index 0000000..7b9088a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/interstates/interstates.shx
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_point/test_point.dbf b/parts/django/django/contrib/gis/tests/data/test_point/test_point.dbf
new file mode 100644
index 0000000..b2b4eca
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_point/test_point.dbf
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_point/test_point.prj b/parts/django/django/contrib/gis/tests/data/test_point/test_point.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_point/test_point.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/tests/data/test_point/test_point.shp b/parts/django/django/contrib/gis/tests/data/test_point/test_point.shp
new file mode 100644
index 0000000..95e8b0a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_point/test_point.shp
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_point/test_point.shx b/parts/django/django/contrib/gis/tests/data/test_point/test_point.shx
new file mode 100644
index 0000000..087f3da
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_point/test_point.shx
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.dbf b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.dbf
new file mode 100644
index 0000000..7965bd6
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.dbf
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.prj b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.prj
new file mode 100644
index 0000000..a30c00a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.prj
@@ -0,0 +1 @@
+GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shp b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shp
new file mode 100644
index 0000000..b22930b
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shp
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shx b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shx
new file mode 100644
index 0000000..c92f78b
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_poly/test_poly.shx
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.csv b/parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.csv
new file mode 100644
index 0000000..dff648f
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.csv
@@ -0,0 +1,4 @@
+POINT_X,POINT_Y,NUM
+1.0,2.0,5
+5.0,23.0,17
+100.0,523.5,23
diff --git a/parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt b/parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt
new file mode 100644
index 0000000..979c179
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt
@@ -0,0 +1,7 @@
+<OGRVRTDataSource>
+<OGRVRTLayer name="test_vrt">
+<SrcDataSource relativeToVRT="1">test_vrt.csv</SrcDataSource>
+<GeometryType>wkbPoint25D</GeometryType>
+<GeometryField encoding="PointFromColumns" x="POINT_X" y="POINT_Y" z="NUM"/>
+</OGRVRTLayer>
+</OGRVRTDataSource> \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/tests/distapp/__init__.py b/parts/django/django/contrib/gis/tests/distapp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/distapp/__init__.py
diff --git a/parts/django/django/contrib/gis/tests/distapp/models.py b/parts/django/django/contrib/gis/tests/distapp/models.py
new file mode 100644
index 0000000..76e7d3a
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/distapp/models.py
@@ -0,0 +1,50 @@
+from django.contrib.gis.db import models
+
+class SouthTexasCity(models.Model):
+ "City model on projected coordinate system for South Texas."
+ name = models.CharField(max_length=30)
+ point = models.PointField(srid=32140)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class SouthTexasCityFt(models.Model):
+ "Same City model as above, but U.S. survey feet are the units."
+ name = models.CharField(max_length=30)
+ point = models.PointField(srid=2278)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class AustraliaCity(models.Model):
+ "City model for Australia, using WGS84."
+ name = models.CharField(max_length=30)
+ point = models.PointField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class CensusZipcode(models.Model):
+ "Model for a few South Texas ZIP codes (in original Census NAD83)."
+ name = models.CharField(max_length=5)
+ poly = models.PolygonField(srid=4269)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class SouthTexasZipcode(models.Model):
+ "Model for a few South Texas ZIP codes."
+ name = models.CharField(max_length=5)
+ poly = models.PolygonField(srid=32140, null=True)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class Interstate(models.Model):
+ "Geodetic model for U.S. Interstates."
+ name = models.CharField(max_length=10)
+ path = models.LineStringField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class SouthTexasInterstate(models.Model):
+ "Projected model for South Texas Interstates."
+ name = models.CharField(max_length=10)
+ path = models.LineStringField(srid=32140)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
diff --git a/parts/django/django/contrib/gis/tests/distapp/tests.py b/parts/django/django/contrib/gis/tests/distapp/tests.py
new file mode 100644
index 0000000..4f81a91
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/distapp/tests.py
@@ -0,0 +1,358 @@
+import os
+from decimal import Decimal
+
+from django.db import connection
+from django.db.models import Q
+from django.contrib.gis.geos import GEOSGeometry, Point, LineString
+from django.contrib.gis.measure import D # alias for Distance
+from django.contrib.gis.tests.utils import oracle, postgis, spatialite, no_oracle, no_spatialite
+from django.test import TestCase
+
+from models import AustraliaCity, Interstate, SouthTexasInterstate, \
+ SouthTexasCity, SouthTexasCityFt, CensusZipcode, SouthTexasZipcode
+
+class DistanceTest(TestCase):
+
+ # A point we are testing distances with -- using a WGS84
+ # coordinate that'll be implicitly transormed to that to
+ # the coordinate system of the field, EPSG:32140 (Texas South Central
+ # w/units in meters)
+ stx_pnt = GEOSGeometry('POINT (-95.370401017314293 29.704867409475465)', 4326)
+ # Another one for Australia
+ au_pnt = GEOSGeometry('POINT (150.791 -34.4919)', 4326)
+
+ def get_names(self, qs):
+ cities = [c.name for c in qs]
+ cities.sort()
+ return cities
+
+ def test01_init(self):
+ "Test initialization of distance models."
+ self.assertEqual(9, SouthTexasCity.objects.count())
+ self.assertEqual(9, SouthTexasCityFt.objects.count())
+ self.assertEqual(11, AustraliaCity.objects.count())
+ self.assertEqual(4, SouthTexasZipcode.objects.count())
+ self.assertEqual(4, CensusZipcode.objects.count())
+ self.assertEqual(1, Interstate.objects.count())
+ self.assertEqual(1, SouthTexasInterstate.objects.count())
+
+ @no_spatialite
+ def test02_dwithin(self):
+ "Testing the `dwithin` lookup type."
+ # Distances -- all should be equal (except for the
+ # degree/meter pair in au_cities, that's somewhat
+ # approximate).
+ tx_dists = [(7000, 22965.83), D(km=7), D(mi=4.349)]
+ au_dists = [(0.5, 32000), D(km=32), D(mi=19.884)]
+
+ # Expected cities for Australia and Texas.
+ tx_cities = ['Downtown Houston', 'Southside Place']
+ au_cities = ['Mittagong', 'Shellharbour', 'Thirroul', 'Wollongong']
+
+ # Performing distance queries on two projected coordinate systems one
+ # with units in meters and the other in units of U.S. survey feet.
+ for dist in tx_dists:
+ if isinstance(dist, tuple): dist1, dist2 = dist
+ else: dist1 = dist2 = dist
+ qs1 = SouthTexasCity.objects.filter(point__dwithin=(self.stx_pnt, dist1))
+ qs2 = SouthTexasCityFt.objects.filter(point__dwithin=(self.stx_pnt, dist2))
+ for qs in qs1, qs2:
+ self.assertEqual(tx_cities, self.get_names(qs))
+
+ # Now performing the `dwithin` queries on a geodetic coordinate system.
+ for dist in au_dists:
+ if isinstance(dist, D) and not oracle: type_error = True
+ else: type_error = False
+
+ if isinstance(dist, tuple):
+ if oracle: dist = dist[1]
+ else: dist = dist[0]
+
+ # Creating the query set.
+ qs = AustraliaCity.objects.order_by('name')
+ if type_error:
+ # A ValueError should be raised on PostGIS when trying to pass
+ # Distance objects into a DWithin query using a geodetic field.
+ self.assertRaises(ValueError, AustraliaCity.objects.filter(point__dwithin=(self.au_pnt, dist)).count)
+ else:
+ self.assertEqual(au_cities, self.get_names(qs.filter(point__dwithin=(self.au_pnt, dist))))
+
+ def test03a_distance_method(self):
+ "Testing the `distance` GeoQuerySet method on projected coordinate systems."
+ # The point for La Grange, TX
+ lagrange = GEOSGeometry('POINT(-96.876369 29.905320)', 4326)
+ # Reference distances in feet and in meters. Got these values from
+ # using the provided raw SQL statements.
+ # SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 32140)) FROM distapp_southtexascity;
+ m_distances = [147075.069813, 139630.198056, 140888.552826,
+ 138809.684197, 158309.246259, 212183.594374,
+ 70870.188967, 165337.758878, 139196.085105]
+ # SELECT ST_Distance(point, ST_Transform(ST_GeomFromText('POINT(-96.876369 29.905320)', 4326), 2278)) FROM distapp_southtexascityft;
+ # Oracle 11 thinks this is not a projected coordinate system, so it's s
+ # not tested.
+ ft_distances = [482528.79154625, 458103.408123001, 462231.860397575,
+ 455411.438904354, 519386.252102563, 696139.009211594,
+ 232513.278304279, 542445.630586414, 456679.155883207]
+
+ # Testing using different variations of parameters and using models
+ # with different projected coordinate systems.
+ dist1 = SouthTexasCity.objects.distance(lagrange, field_name='point')
+ dist2 = SouthTexasCity.objects.distance(lagrange) # Using GEOSGeometry parameter
+ if spatialite or oracle:
+ dist_qs = [dist1, dist2]
+ else:
+ dist3 = SouthTexasCityFt.objects.distance(lagrange.ewkt) # Using EWKT string parameter.
+ dist4 = SouthTexasCityFt.objects.distance(lagrange)
+ dist_qs = [dist1, dist2, dist3, dist4]
+
+ # Original query done on PostGIS, have to adjust AlmostEqual tolerance
+ # for Oracle.
+ if oracle: tol = 2
+ else: tol = 5
+
+ # Ensuring expected distances are returned for each distance queryset.
+ for qs in dist_qs:
+ for i, c in enumerate(qs):
+ self.assertAlmostEqual(m_distances[i], c.distance.m, tol)
+ self.assertAlmostEqual(ft_distances[i], c.distance.survey_ft, tol)
+
+ @no_spatialite
+ def test03b_distance_method(self):
+ "Testing the `distance` GeoQuerySet method on geodetic coordnate systems."
+ if oracle: tol = 2
+ else: tol = 5
+
+ # Testing geodetic distance calculation with a non-point geometry
+ # (a LineString of Wollongong and Shellharbour coords).
+ ls = LineString( ( (150.902, -34.4245), (150.87, -34.5789) ) )
+ if oracle or connection.ops.geography:
+ # Reference query:
+ # SELECT ST_distance_sphere(point, ST_GeomFromText('LINESTRING(150.9020 -34.4245,150.8700 -34.5789)', 4326)) FROM distapp_australiacity ORDER BY name;
+ distances = [1120954.92533513, 140575.720018241, 640396.662906304,
+ 60580.9693849269, 972807.955955075, 568451.8357838,
+ 40435.4335201384, 0, 68272.3896586844, 12375.0643697706, 0]
+ qs = AustraliaCity.objects.distance(ls).order_by('name')
+ for city, distance in zip(qs, distances):
+ # Testing equivalence to within a meter.
+ self.assertAlmostEqual(distance, city.distance.m, 0)
+ else:
+ # PostGIS 1.4 and below is limited to disance queries only
+ # to/from point geometries, check for raising of ValueError.
+ self.assertRaises(ValueError, AustraliaCity.objects.distance, ls)
+ self.assertRaises(ValueError, AustraliaCity.objects.distance, ls.wkt)
+
+ # Got the reference distances using the raw SQL statements:
+ # SELECT ST_distance_spheroid(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326), 'SPHEROID["WGS 84",6378137.0,298.257223563]') FROM distapp_australiacity WHERE (NOT (id = 11));
+ # SELECT ST_distance_sphere(point, ST_GeomFromText('POINT(151.231341 -33.952685)', 4326)) FROM distapp_australiacity WHERE (NOT (id = 11)); st_distance_sphere
+ if connection.ops.postgis and connection.ops.proj_version_tuple() >= (4, 7, 0):
+ # PROJ.4 versions 4.7+ have updated datums, and thus different
+ # distance values.
+ spheroid_distances = [60504.0628957201, 77023.9489850262, 49154.8867574404,
+ 90847.4358768573, 217402.811919332, 709599.234564757,
+ 640011.483550888, 7772.00667991925, 1047861.78619339,
+ 1165126.55236034]
+ sphere_distances = [60580.9693849267, 77144.0435286473, 49199.4415344719,
+ 90804.7533823494, 217713.384600405, 709134.127242793,
+ 639828.157159169, 7786.82949717788, 1049204.06569028,
+ 1162623.7238134]
+
+ else:
+ spheroid_distances = [60504.0628825298, 77023.948962654, 49154.8867507115,
+ 90847.435881812, 217402.811862568, 709599.234619957,
+ 640011.483583758, 7772.00667666425, 1047861.7859506,
+ 1165126.55237647]
+ sphere_distances = [60580.7612632291, 77143.7785056615, 49199.2725132184,
+ 90804.4414289463, 217712.63666124, 709131.691061906,
+ 639825.959074112, 7786.80274606706, 1049200.46122281,
+ 1162619.7297006]
+
+ # Testing with spheroid distances first.
+ hillsdale = AustraliaCity.objects.get(name='Hillsdale')
+ qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point, spheroid=True)
+ for i, c in enumerate(qs):
+ self.assertAlmostEqual(spheroid_distances[i], c.distance.m, tol)
+ if postgis:
+ # PostGIS uses sphere-only distances by default, testing these as well.
+ qs = AustraliaCity.objects.exclude(id=hillsdale.id).distance(hillsdale.point)
+ for i, c in enumerate(qs):
+ self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol)
+
+ @no_oracle # Oracle already handles geographic distance calculation.
+ def test03c_distance_method(self):
+ "Testing the `distance` GeoQuerySet method used with `transform` on a geographic field."
+ # Normally you can't compute distances from a geometry field
+ # that is not a PointField (on PostGIS 1.4 and below).
+ if not connection.ops.geography:
+ self.assertRaises(ValueError, CensusZipcode.objects.distance, self.stx_pnt)
+
+ # We'll be using a Polygon (created by buffering the centroid
+ # of 77005 to 100m) -- which aren't allowed in geographic distance
+ # queries normally, however our field has been transformed to
+ # a non-geographic system.
+ z = SouthTexasZipcode.objects.get(name='77005')
+
+ # Reference query:
+ # SELECT ST_Distance(ST_Transform("distapp_censuszipcode"."poly", 32140), ST_GeomFromText('<buffer_wkt>', 32140)) FROM "distapp_censuszipcode";
+ dists_m = [3553.30384972258, 1243.18391525602, 2186.15439472242]
+
+ # Having our buffer in the SRID of the transformation and of the field
+ # -- should get the same results. The first buffer has no need for
+ # transformation SQL because it is the same SRID as what was given
+ # to `transform()`. The second buffer will need to be transformed,
+ # however.
+ buf1 = z.poly.centroid.buffer(100)
+ buf2 = buf1.transform(4269, clone=True)
+ ref_zips = ['77002', '77025', '77401']
+
+ for buf in [buf1, buf2]:
+ qs = CensusZipcode.objects.exclude(name='77005').transform(32140).distance(buf)
+ self.assertEqual(ref_zips, self.get_names(qs))
+ for i, z in enumerate(qs):
+ self.assertAlmostEqual(z.distance.m, dists_m[i], 5)
+
+ def test04_distance_lookups(self):
+ "Testing the `distance_lt`, `distance_gt`, `distance_lte`, and `distance_gte` lookup types."
+ # Retrieving the cities within a 20km 'donut' w/a 7km radius 'hole'
+ # (thus, Houston and Southside place will be excluded as tested in
+ # the `test02_dwithin` above).
+ qs1 = SouthTexasCity.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
+
+ # Can't determine the units on SpatiaLite from PROJ.4 string, and
+ # Oracle 11 incorrectly thinks it is not projected.
+ if spatialite or oracle:
+ dist_qs = (qs1,)
+ else:
+ qs2 = SouthTexasCityFt.objects.filter(point__distance_gte=(self.stx_pnt, D(km=7))).filter(point__distance_lte=(self.stx_pnt, D(km=20)))
+ dist_qs = (qs1, qs2)
+
+ for qs in dist_qs:
+ cities = self.get_names(qs)
+ self.assertEqual(cities, ['Bellaire', 'Pearland', 'West University Place'])
+
+ # Doing a distance query using Polygons instead of a Point.
+ z = SouthTexasZipcode.objects.get(name='77005')
+ qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=275)))
+ self.assertEqual(['77025', '77401'], self.get_names(qs))
+ # If we add a little more distance 77002 should be included.
+ qs = SouthTexasZipcode.objects.exclude(name='77005').filter(poly__distance_lte=(z.poly, D(m=300)))
+ self.assertEqual(['77002', '77025', '77401'], self.get_names(qs))
+
+ def test05_geodetic_distance_lookups(self):
+ "Testing distance lookups on geodetic coordinate systems."
+ # Line is from Canberra to Sydney. Query is for all other cities within
+ # a 100km of that line (which should exclude only Hobart & Adelaide).
+ line = GEOSGeometry('LINESTRING(144.9630 -37.8143,151.2607 -33.8870)', 4326)
+ dist_qs = AustraliaCity.objects.filter(point__distance_lte=(line, D(km=100)))
+
+ if oracle or connection.ops.geography:
+ # Oracle and PostGIS 1.5 can do distance lookups on arbitrary geometries.
+ self.assertEqual(9, dist_qs.count())
+ self.assertEqual(['Batemans Bay', 'Canberra', 'Hillsdale',
+ 'Melbourne', 'Mittagong', 'Shellharbour',
+ 'Sydney', 'Thirroul', 'Wollongong'],
+ self.get_names(dist_qs))
+ else:
+ # PostGIS 1.4 and below only allows geodetic distance queries (utilizing
+ # ST_Distance_Sphere/ST_Distance_Spheroid) from Points to PointFields
+ # on geometry columns.
+ self.assertRaises(ValueError, dist_qs.count)
+
+ # Ensured that a ValueError was raised, none of the rest of the test is
+ # support on this backend, so bail now.
+ if spatialite: return
+
+ # Too many params (4 in this case) should raise a ValueError.
+ self.assertRaises(ValueError, len,
+ AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)', D(km=100), 'spheroid', '4')))
+
+ # Not enough params should raise a ValueError.
+ self.assertRaises(ValueError, len,
+ AustraliaCity.objects.filter(point__distance_lte=('POINT(5 23)',)))
+
+ # Getting all cities w/in 550 miles of Hobart.
+ hobart = AustraliaCity.objects.get(name='Hobart')
+ qs = AustraliaCity.objects.exclude(name='Hobart').filter(point__distance_lte=(hobart.point, D(mi=550)))
+ cities = self.get_names(qs)
+ self.assertEqual(cities, ['Batemans Bay', 'Canberra', 'Melbourne'])
+
+ # Cities that are either really close or really far from Wollongong --
+ # and using different units of distance.
+ wollongong = AustraliaCity.objects.get(name='Wollongong')
+ d1, d2 = D(yd=19500), D(nm=400) # Yards (~17km) & Nautical miles.
+
+ # Normal geodetic distance lookup (uses `distance_sphere` on PostGIS.
+ gq1 = Q(point__distance_lte=(wollongong.point, d1))
+ gq2 = Q(point__distance_gte=(wollongong.point, d2))
+ qs1 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq1 | gq2)
+
+ # Geodetic distance lookup but telling GeoDjango to use `distance_spheroid`
+ # instead (we should get the same results b/c accuracy variance won't matter
+ # in this test case).
+ if postgis:
+ gq3 = Q(point__distance_lte=(wollongong.point, d1, 'spheroid'))
+ gq4 = Q(point__distance_gte=(wollongong.point, d2, 'spheroid'))
+ qs2 = AustraliaCity.objects.exclude(name='Wollongong').filter(gq3 | gq4)
+ querysets = [qs1, qs2]
+ else:
+ querysets = [qs1]
+
+ for qs in querysets:
+ cities = self.get_names(qs)
+ self.assertEqual(cities, ['Adelaide', 'Hobart', 'Shellharbour', 'Thirroul'])
+
+ def test06_area(self):
+ "Testing the `area` GeoQuerySet method."
+ # Reference queries:
+ # SELECT ST_Area(poly) FROM distapp_southtexaszipcode;
+ area_sq_m = [5437908.90234375, 10183031.4389648, 11254471.0073242, 9881708.91772461]
+ # Tolerance has to be lower for Oracle and differences
+ # with GEOS 3.0.0RC4
+ tol = 2
+ for i, z in enumerate(SouthTexasZipcode.objects.area()):
+ self.assertAlmostEqual(area_sq_m[i], z.area.sq_m, tol)
+
+ def test07_length(self):
+ "Testing the `length` GeoQuerySet method."
+ # Reference query (should use `length_spheroid`).
+ # SELECT ST_length_spheroid(ST_GeomFromText('<wkt>', 4326) 'SPHEROID["WGS 84",6378137,298.257223563, AUTHORITY["EPSG","7030"]]');
+ len_m1 = 473504.769553813
+ len_m2 = 4617.668
+
+ if spatialite:
+ # Does not support geodetic coordinate systems.
+ self.assertRaises(ValueError, Interstate.objects.length)
+ else:
+ qs = Interstate.objects.length()
+ if oracle: tol = 2
+ else: tol = 5
+ self.assertAlmostEqual(len_m1, qs[0].length.m, tol)
+
+ # Now doing length on a projected coordinate system.
+ i10 = SouthTexasInterstate.objects.length().get(name='I-10')
+ self.assertAlmostEqual(len_m2, i10.length.m, 2)
+
+ @no_spatialite
+ def test08_perimeter(self):
+ "Testing the `perimeter` GeoQuerySet method."
+ # Reference query:
+ # SELECT ST_Perimeter(distapp_southtexaszipcode.poly) FROM distapp_southtexaszipcode;
+ perim_m = [18404.3550889361, 15627.2108551001, 20632.5588368978, 17094.5996143697]
+ if oracle: tol = 2
+ else: tol = 7
+ for i, z in enumerate(SouthTexasZipcode.objects.perimeter()):
+ self.assertAlmostEqual(perim_m[i], z.perimeter.m, tol)
+
+ # Running on points; should return 0.
+ for i, c in enumerate(SouthTexasCity.objects.perimeter(model_att='perim')):
+ self.assertEqual(0, c.perim.m)
+
+ def test09_measurement_null_fields(self):
+ "Testing the measurement GeoQuerySet methods on fields with NULL values."
+ # Creating SouthTexasZipcode w/NULL value.
+ SouthTexasZipcode.objects.create(name='78212')
+ # Performing distance/area queries against the NULL PolygonField,
+ # and ensuring the result of the operations is None.
+ htown = SouthTexasCity.objects.get(name='Downtown Houston')
+ z = SouthTexasZipcode.objects.distance(htown.point).area().get(name='78212')
+ self.assertEqual(None, z.distance)
+ self.assertEqual(None, z.area)
diff --git a/parts/django/django/contrib/gis/tests/geo3d/__init__.py b/parts/django/django/contrib/gis/tests/geo3d/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geo3d/__init__.py
diff --git a/parts/django/django/contrib/gis/tests/geo3d/models.py b/parts/django/django/contrib/gis/tests/geo3d/models.py
new file mode 100644
index 0000000..3c4f77e
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geo3d/models.py
@@ -0,0 +1,69 @@
+from django.contrib.gis.db import models
+
+class City3D(models.Model):
+ name = models.CharField(max_length=30)
+ point = models.PointField(dim=3)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class Interstate2D(models.Model):
+ name = models.CharField(max_length=30)
+ line = models.LineStringField(srid=4269)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class Interstate3D(models.Model):
+ name = models.CharField(max_length=30)
+ line = models.LineStringField(dim=3, srid=4269)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class InterstateProj2D(models.Model):
+ name = models.CharField(max_length=30)
+ line = models.LineStringField(srid=32140)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class InterstateProj3D(models.Model):
+ name = models.CharField(max_length=30)
+ line = models.LineStringField(dim=3, srid=32140)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class Polygon2D(models.Model):
+ name = models.CharField(max_length=30)
+ poly = models.PolygonField(srid=32140)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class Polygon3D(models.Model):
+ name = models.CharField(max_length=30)
+ poly = models.PolygonField(dim=3, srid=32140)
+ objects = models.GeoManager()
+
+ def __unicode__(self):
+ return self.name
+
+class Point2D(models.Model):
+ point = models.PointField()
+ objects = models.GeoManager()
+
+class Point3D(models.Model):
+ point = models.PointField(dim=3)
+ objects = models.GeoManager()
+
+class MultiPoint3D(models.Model):
+ mpoint = models.MultiPointField(dim=3)
+ objects = models.GeoManager()
diff --git a/parts/django/django/contrib/gis/tests/geo3d/tests.py b/parts/django/django/contrib/gis/tests/geo3d/tests.py
new file mode 100644
index 0000000..f57445c
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geo3d/tests.py
@@ -0,0 +1,231 @@
+import os
+import re
+from django.utils.unittest import TestCase
+from django.contrib.gis.db.models import Union, Extent3D
+from django.contrib.gis.geos import GEOSGeometry, Point, Polygon
+from django.contrib.gis.utils import LayerMapping, LayerMapError
+
+from models import City3D, Interstate2D, Interstate3D, \
+ InterstateProj2D, InterstateProj3D, \
+ Point2D, Point3D, MultiPoint3D, Polygon2D, Polygon3D
+
+data_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
+city_file = os.path.join(data_path, 'cities', 'cities.shp')
+vrt_file = os.path.join(data_path, 'test_vrt', 'test_vrt.vrt')
+
+# The coordinates of each city, with Z values corresponding to their
+# altitude in meters.
+city_data = (
+ ('Houston', (-95.363151, 29.763374, 18)),
+ ('Dallas', (-96.801611, 32.782057, 147)),
+ ('Oklahoma City', (-97.521157, 34.464642, 380)),
+ ('Wellington', (174.783117, -41.315268, 14)),
+ ('Pueblo', (-104.609252, 38.255001, 1433)),
+ ('Lawrence', (-95.235060, 38.971823, 251)),
+ ('Chicago', (-87.650175, 41.850385, 181)),
+ ('Victoria', (-123.305196, 48.462611, 15)),
+)
+
+# Reference mapping of city name to its altitude (Z value).
+city_dict = dict((name, coords) for name, coords in city_data)
+
+# 3D freeway data derived from the National Elevation Dataset:
+# http://seamless.usgs.gov/products/9arc.php
+interstate_data = (
+ ('I-45',
+ 'LINESTRING(-95.3708481 29.7765870 11.339,-95.3694580 29.7787980 4.536,-95.3690305 29.7797359 9.762,-95.3691886 29.7812450 12.448,-95.3696447 29.7850144 10.457,-95.3702511 29.7868518 9.418,-95.3706724 29.7881286 14.858,-95.3711632 29.7896157 15.386,-95.3714525 29.7936267 13.168,-95.3717848 29.7955007 15.104,-95.3717719 29.7969804 16.516,-95.3717305 29.7982117 13.923,-95.3717254 29.8000778 14.385,-95.3719875 29.8013539 15.160,-95.3720575 29.8026785 15.544,-95.3721321 29.8040912 14.975,-95.3722074 29.8050998 15.688,-95.3722779 29.8060430 16.099,-95.3733818 29.8076750 15.197,-95.3741563 29.8103686 17.268,-95.3749458 29.8129927 19.857,-95.3763564 29.8144557 15.435)',
+ ( 11.339, 4.536, 9.762, 12.448, 10.457, 9.418, 14.858,
+ 15.386, 13.168, 15.104, 16.516, 13.923, 14.385, 15.16 ,
+ 15.544, 14.975, 15.688, 16.099, 15.197, 17.268, 19.857,
+ 15.435),
+ ),
+ )
+
+# Bounding box polygon for inner-loop of Houston (in projected coordinate
+# system 32140), with elevation values from the National Elevation Dataset
+# (see above).
+bbox_wkt = 'POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,942051.75 4208366.38,941527.97 4225693.20))'
+bbox_z = (21.71, 13.21, 9.12, 16.40, 21.71)
+def gen_bbox():
+ bbox_2d = GEOSGeometry(bbox_wkt, srid=32140)
+ bbox_3d = Polygon(tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140)
+ return bbox_2d, bbox_3d
+
+class Geo3DTest(TestCase):
+ """
+ Only a subset of the PostGIS routines are 3D-enabled, and this TestCase
+ tries to test the features that can handle 3D and that are also
+ available within GeoDjango. For more information, see the PostGIS docs
+ on the routines that support 3D:
+
+ http://postgis.refractions.net/documentation/manual-1.4/ch08.html#PostGIS_3D_Functions
+ """
+
+ def test01_3d(self):
+ "Test the creation of 3D models."
+ # 3D models for the rest of the tests will be populated in here.
+ # For each 3D data set create model (and 2D version if necessary),
+ # retrieve, and assert geometry is in 3D and contains the expected
+ # 3D values.
+ for name, pnt_data in city_data:
+ x, y, z = pnt_data
+ pnt = Point(x, y, z, srid=4326)
+ City3D.objects.create(name=name, point=pnt)
+ city = City3D.objects.get(name=name)
+ self.failUnless(city.point.hasz)
+ self.assertEqual(z, city.point.z)
+
+ # Interstate (2D / 3D and Geographic/Projected variants)
+ for name, line, exp_z in interstate_data:
+ line_3d = GEOSGeometry(line, srid=4269)
+ # Using `hex` attribute because it omits 3D.
+ line_2d = GEOSGeometry(line_3d.hex, srid=4269)
+
+ # Creating a geographic and projected version of the
+ # interstate in both 2D and 3D.
+ Interstate3D.objects.create(name=name, line=line_3d)
+ InterstateProj3D.objects.create(name=name, line=line_3d)
+ Interstate2D.objects.create(name=name, line=line_2d)
+ InterstateProj2D.objects.create(name=name, line=line_2d)
+
+ # Retrieving and making sure it's 3D and has expected
+ # Z values -- shouldn't change because of coordinate system.
+ interstate = Interstate3D.objects.get(name=name)
+ interstate_proj = InterstateProj3D.objects.get(name=name)
+ for i in [interstate, interstate_proj]:
+ self.failUnless(i.line.hasz)
+ self.assertEqual(exp_z, tuple(i.line.z))
+
+ # Creating 3D Polygon.
+ bbox2d, bbox3d = gen_bbox()
+ Polygon2D.objects.create(name='2D BBox', poly=bbox2d)
+ Polygon3D.objects.create(name='3D BBox', poly=bbox3d)
+ p3d = Polygon3D.objects.get(name='3D BBox')
+ self.failUnless(p3d.poly.hasz)
+ self.assertEqual(bbox3d, p3d.poly)
+
+ def test01a_3d_layermapping(self):
+ "Testing LayerMapping on 3D models."
+ from models import Point2D, Point3D
+
+ point_mapping = {'point' : 'POINT'}
+ mpoint_mapping = {'mpoint' : 'MULTIPOINT'}
+
+ # The VRT is 3D, but should still be able to map sans the Z.
+ lm = LayerMapping(Point2D, vrt_file, point_mapping, transform=False)
+ lm.save()
+ self.assertEqual(3, Point2D.objects.count())
+
+ # The city shapefile is 2D, and won't be able to fill the coordinates
+ # in the 3D model -- thus, a LayerMapError is raised.
+ self.assertRaises(LayerMapError, LayerMapping,
+ Point3D, city_file, point_mapping, transform=False)
+
+ # 3D model should take 3D data just fine.
+ lm = LayerMapping(Point3D, vrt_file, point_mapping, transform=False)
+ lm.save()
+ self.assertEqual(3, Point3D.objects.count())
+
+ # Making sure LayerMapping.make_multi works right, by converting
+ # a Point25D into a MultiPoint25D.
+ lm = LayerMapping(MultiPoint3D, vrt_file, mpoint_mapping, transform=False)
+ lm.save()
+ self.assertEqual(3, MultiPoint3D.objects.count())
+
+ def test02a_kml(self):
+ "Test GeoQuerySet.kml() with Z values."
+ h = City3D.objects.kml(precision=6).get(name='Houston')
+ # KML should be 3D.
+ # `SELECT ST_AsKML(point, 6) FROM geo3d_city3d WHERE name = 'Houston';`
+ ref_kml_regex = re.compile(r'^<Point><coordinates>-95.363\d+,29.763\d+,18</coordinates></Point>$')
+ self.failUnless(ref_kml_regex.match(h.kml))
+
+ def test02b_geojson(self):
+ "Test GeoQuerySet.geojson() with Z values."
+ h = City3D.objects.geojson(precision=6).get(name='Houston')
+ # GeoJSON should be 3D
+ # `SELECT ST_AsGeoJSON(point, 6) FROM geo3d_city3d WHERE name='Houston';`
+ ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
+ self.failUnless(ref_json_regex.match(h.geojson))
+
+ def test03a_union(self):
+ "Testing the Union aggregate of 3D models."
+ # PostGIS query that returned the reference EWKT for this test:
+ # `SELECT ST_AsText(ST_Union(point)) FROM geo3d_city3d;`
+ ref_ewkt = 'SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)'
+ ref_union = GEOSGeometry(ref_ewkt)
+ union = City3D.objects.aggregate(Union('point'))['point__union']
+ self.failUnless(union.hasz)
+ self.assertEqual(ref_union, union)
+
+ def test03b_extent(self):
+ "Testing the Extent3D aggregate for 3D models."
+ # `SELECT ST_Extent3D(point) FROM geo3d_city3d;`
+ ref_extent3d = (-123.305196, -41.315268, 14,174.783117, 48.462611, 1433)
+ extent1 = City3D.objects.aggregate(Extent3D('point'))['point__extent3d']
+ extent2 = City3D.objects.extent3d()
+
+ def check_extent3d(extent3d, tol=6):
+ for ref_val, ext_val in zip(ref_extent3d, extent3d):
+ self.assertAlmostEqual(ref_val, ext_val, tol)
+
+ for e3d in [extent1, extent2]:
+ check_extent3d(e3d)
+
+ def test04_perimeter(self):
+ "Testing GeoQuerySet.perimeter() on 3D fields."
+ # Reference query for values below:
+ # `SELECT ST_Perimeter3D(poly), ST_Perimeter2D(poly) FROM geo3d_polygon3d;`
+ ref_perim_3d = 76859.2620451
+ ref_perim_2d = 76859.2577803
+ tol = 6
+ self.assertAlmostEqual(ref_perim_2d,
+ Polygon2D.objects.perimeter().get(name='2D BBox').perimeter.m,
+ tol)
+ self.assertAlmostEqual(ref_perim_3d,
+ Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m,
+ tol)
+
+ def test05_length(self):
+ "Testing GeoQuerySet.length() on 3D fields."
+ # ST_Length_Spheroid Z-aware, and thus does not need to use
+ # a separate function internally.
+ # `SELECT ST_Length_Spheroid(line, 'SPHEROID["GRS 1980",6378137,298.257222101]')
+ # FROM geo3d_interstate[2d|3d];`
+ tol = 3
+ ref_length_2d = 4368.1721949481
+ ref_length_3d = 4368.62547052088
+ self.assertAlmostEqual(ref_length_2d,
+ Interstate2D.objects.length().get(name='I-45').length.m,
+ tol)
+ self.assertAlmostEqual(ref_length_3d,
+ Interstate3D.objects.length().get(name='I-45').length.m,
+ tol)
+
+ # Making sure `ST_Length3D` is used on for a projected
+ # and 3D model rather than `ST_Length`.
+ # `SELECT ST_Length(line) FROM geo3d_interstateproj2d;`
+ ref_length_2d = 4367.71564892392
+ # `SELECT ST_Length3D(line) FROM geo3d_interstateproj3d;`
+ ref_length_3d = 4368.16897234101
+ self.assertAlmostEqual(ref_length_2d,
+ InterstateProj2D.objects.length().get(name='I-45').length.m,
+ tol)
+ self.assertAlmostEqual(ref_length_3d,
+ InterstateProj3D.objects.length().get(name='I-45').length.m,
+ tol)
+
+ def test06_scale(self):
+ "Testing GeoQuerySet.scale() on Z values."
+ # Mapping of City name to reference Z values.
+ zscales = (-3, 4, 23)
+ for zscale in zscales:
+ for city in City3D.objects.scale(1.0, 1.0, zscale):
+ self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
+
+ def test07_translate(self):
+ "Testing GeoQuerySet.translate() on Z values."
+ ztranslations = (5.23, 23, -17)
+ for ztrans in ztranslations:
+ for city in City3D.objects.translate(0, 0, ztrans):
+ self.assertEqual(city_dict[city.name][2] + ztrans, city.translate.z)
diff --git a/parts/django/django/contrib/gis/tests/geo3d/views.py b/parts/django/django/contrib/gis/tests/geo3d/views.py
new file mode 100644
index 0000000..60f00ef
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geo3d/views.py
@@ -0,0 +1 @@
+# Create your views here.
diff --git a/parts/django/django/contrib/gis/tests/geoapp/__init__.py b/parts/django/django/contrib/gis/tests/geoapp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/__init__.py
diff --git a/parts/django/django/contrib/gis/tests/geoapp/feeds.py b/parts/django/django/contrib/gis/tests/geoapp/feeds.py
new file mode 100644
index 0000000..942b140
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/feeds.py
@@ -0,0 +1,63 @@
+from django.contrib.gis import feeds
+from django.contrib.gis.tests.utils import mysql
+from models import City, Country
+
+class TestGeoRSS1(feeds.Feed):
+ link = '/city/'
+ title = 'Test GeoDjango Cities'
+
+ def items(self):
+ return City.objects.all()
+
+ def item_link(self, item):
+ return '/city/%s/' % item.pk
+
+ def item_geometry(self, item):
+ return item.point
+
+class TestGeoRSS2(TestGeoRSS1):
+ def geometry(self, obj):
+ # This should attach a <georss:box> element for the extent of
+ # of the cities in the database. This tuple came from
+ # calling `City.objects.extent()` -- we can't do that call here
+ # because `extent` is not implemented for MySQL/Oracle.
+ return (-123.30, -41.32, 174.78, 48.46)
+
+ def item_geometry(self, item):
+ # Returning a simple tuple for the geometry.
+ return item.point.x, item.point.y
+
+class TestGeoAtom1(TestGeoRSS1):
+ feed_type = feeds.GeoAtom1Feed
+
+class TestGeoAtom2(TestGeoRSS2):
+ feed_type = feeds.GeoAtom1Feed
+
+ def geometry(self, obj):
+ # This time we'll use a 2-tuple of coordinates for the box.
+ return ((-123.30, -41.32), (174.78, 48.46))
+
+class TestW3CGeo1(TestGeoRSS1):
+ feed_type = feeds.W3CGeoFeed
+
+# The following feeds are invalid, and will raise exceptions.
+class TestW3CGeo2(TestGeoRSS2):
+ feed_type = feeds.W3CGeoFeed
+
+class TestW3CGeo3(TestGeoRSS1):
+ feed_type = feeds.W3CGeoFeed
+
+ def item_geometry(self, item):
+ from django.contrib.gis.geos import Polygon
+ return Polygon(((0, 0), (0, 1), (1, 1), (1, 0), (0, 0)))
+
+# The feed dictionary to use for URLs.
+feed_dict = {
+ 'rss1' : TestGeoRSS1,
+ 'rss2' : TestGeoRSS2,
+ 'atom1' : TestGeoAtom1,
+ 'atom2' : TestGeoAtom2,
+ 'w3cgeo1' : TestW3CGeo1,
+ 'w3cgeo2' : TestW3CGeo2,
+ 'w3cgeo3' : TestW3CGeo3,
+}
diff --git a/parts/django/django/contrib/gis/tests/geoapp/fixtures/initial_data.json.gz b/parts/django/django/contrib/gis/tests/geoapp/fixtures/initial_data.json.gz
new file mode 100644
index 0000000..c695082
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/fixtures/initial_data.json.gz
Binary files differ
diff --git a/parts/django/django/contrib/gis/tests/geoapp/models.py b/parts/django/django/contrib/gis/tests/geoapp/models.py
new file mode 100644
index 0000000..89027ee
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/models.py
@@ -0,0 +1,45 @@
+from django.contrib.gis.db import models
+from django.contrib.gis.tests.utils import mysql, spatialite
+
+# MySQL spatial indices can't handle NULL geometries.
+null_flag = not mysql
+
+class Country(models.Model):
+ name = models.CharField(max_length=30)
+ mpoly = models.MultiPolygonField() # SRID, by default, is 4326
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class City(models.Model):
+ name = models.CharField(max_length=30)
+ point = models.PointField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+# This is an inherited model from City
+class PennsylvaniaCity(City):
+ county = models.CharField(max_length=30)
+ objects = models.GeoManager() # TODO: This should be implicitly inherited.
+
+class State(models.Model):
+ name = models.CharField(max_length=30)
+ poly = models.PolygonField(null=null_flag) # Allowing NULL geometries here.
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class Track(models.Model):
+ name = models.CharField(max_length=30)
+ line = models.LineStringField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+if not spatialite:
+ class Feature(models.Model):
+ name = models.CharField(max_length=20)
+ geom = models.GeometryField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+ class MinusOneSRID(models.Model):
+ geom = models.PointField(srid=-1) # Minus one SRID.
+ objects = models.GeoManager()
diff --git a/parts/django/django/contrib/gis/tests/geoapp/sitemaps.py b/parts/django/django/contrib/gis/tests/geoapp/sitemaps.py
new file mode 100644
index 0000000..ca785f2
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/sitemaps.py
@@ -0,0 +1,8 @@
+from django.contrib.gis.sitemaps import GeoRSSSitemap, KMLSitemap, KMZSitemap
+from models import City, Country
+from feeds import feed_dict
+
+sitemaps = {'kml' : KMLSitemap([City, Country]),
+ 'kmz' : KMZSitemap([City, Country]),
+ 'georss' : GeoRSSSitemap(feed_dict),
+ }
diff --git a/parts/django/django/contrib/gis/tests/geoapp/test_feeds.py b/parts/django/django/contrib/gis/tests/geoapp/test_feeds.py
new file mode 100644
index 0000000..7ec9a3c
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/test_feeds.py
@@ -0,0 +1,78 @@
+from xml.dom import minidom
+from django.test import TestCase
+
+from models import City
+
+
+class GeoFeedTest(TestCase):
+
+ urls = 'django.contrib.gis.tests.geoapp.urls'
+
+ def assertChildNodes(self, elem, expected):
+ "Taken from regressiontests/syndication/tests.py."
+ actual = set([n.nodeName for n in elem.childNodes])
+ expected = set(expected)
+ self.assertEqual(actual, expected)
+
+ def test_geofeed_rss(self):
+ "Tests geographic feeds using GeoRSS over RSSv2."
+ # Uses `GEOSGeometry` in `item_geometry`
+ doc1 = minidom.parseString(self.client.get('/feeds/rss1/').content)
+ # Uses a 2-tuple in `item_geometry`
+ doc2 = minidom.parseString(self.client.get('/feeds/rss2/').content)
+ feed1, feed2 = doc1.firstChild, doc2.firstChild
+
+ # Making sure the box got added to the second GeoRSS feed.
+ self.assertChildNodes(feed2.getElementsByTagName('channel')[0],
+ ['title', 'link', 'description', 'language',
+ 'lastBuildDate', 'item', 'georss:box', 'atom:link']
+ )
+
+ # Incrementing through the feeds.
+ for feed in [feed1, feed2]:
+ # Ensuring the georss namespace was added to the <rss> element.
+ self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss')
+ chan = feed.getElementsByTagName('channel')[0]
+ items = chan.getElementsByTagName('item')
+ self.assertEqual(len(items), City.objects.count())
+
+ # Ensuring the georss element was added to each item in the feed.
+ for item in items:
+ self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'georss:point'])
+
+ def test_geofeed_atom(self):
+ "Testing geographic feeds using GeoRSS over Atom."
+ doc1 = minidom.parseString(self.client.get('/feeds/atom1/').content)
+ doc2 = minidom.parseString(self.client.get('/feeds/atom2/').content)
+ feed1, feed2 = doc1.firstChild, doc2.firstChild
+
+ # Making sure the box got added to the second GeoRSS feed.
+ self.assertChildNodes(feed2, ['title', 'link', 'id', 'updated', 'entry', 'georss:box'])
+
+ for feed in [feed1, feed2]:
+ # Ensuring the georsss namespace was added to the <feed> element.
+ self.assertEqual(feed.getAttribute(u'xmlns:georss'), u'http://www.georss.org/georss')
+ entries = feed.getElementsByTagName('entry')
+ self.assertEqual(len(entries), City.objects.count())
+
+ # Ensuring the georss element was added to each entry in the feed.
+ for entry in entries:
+ self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'georss:point'])
+
+ def test_geofeed_w3c(self):
+ "Testing geographic feeds using W3C Geo."
+ doc = minidom.parseString(self.client.get('/feeds/w3cgeo1/').content)
+ feed = doc.firstChild
+ # Ensuring the geo namespace was added to the <feed> element.
+ self.assertEqual(feed.getAttribute(u'xmlns:geo'), u'http://www.w3.org/2003/01/geo/wgs84_pos#')
+ chan = feed.getElementsByTagName('channel')[0]
+ items = chan.getElementsByTagName('item')
+ self.assertEqual(len(items), City.objects.count())
+
+ # Ensuring the geo:lat and geo:lon element was added to each item in the feed.
+ for item in items:
+ self.assertChildNodes(item, ['title', 'link', 'description', 'guid', 'geo:lat', 'geo:lon'])
+
+ # Boxes and Polygons aren't allowed in W3C Geo feeds.
+ self.assertRaises(ValueError, self.client.get, '/feeds/w3cgeo2/') # Box in <channel>
+ self.assertRaises(ValueError, self.client.get, '/feeds/w3cgeo3/') # Polygons in <entry>
diff --git a/parts/django/django/contrib/gis/tests/geoapp/test_regress.py b/parts/django/django/contrib/gis/tests/geoapp/test_regress.py
new file mode 100644
index 0000000..0295526
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/test_regress.py
@@ -0,0 +1,37 @@
+import os, unittest
+from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite
+from django.contrib.gis.shortcuts import render_to_kmz
+from models import City
+
+class GeoRegressionTests(unittest.TestCase):
+
+ def test01_update(self):
+ "Testing GeoQuerySet.update(), see #10411."
+ pnt = City.objects.get(name='Pueblo').point
+ bak = pnt.clone()
+ pnt.y += 0.005
+ pnt.x += 0.005
+
+ City.objects.filter(name='Pueblo').update(point=pnt)
+ self.assertEqual(pnt, City.objects.get(name='Pueblo').point)
+ City.objects.filter(name='Pueblo').update(point=bak)
+ self.assertEqual(bak, City.objects.get(name='Pueblo').point)
+
+ def test02_kmz(self):
+ "Testing `render_to_kmz` with non-ASCII data, see #11624."
+ name = '\xc3\x85land Islands'.decode('iso-8859-1')
+ places = [{'name' : name,
+ 'description' : name,
+ 'kml' : '<Point><coordinates>5.0,23.0</coordinates></Point>'
+ }]
+ kmz = render_to_kmz('gis/kml/placemarks.kml', {'places' : places})
+
+ @no_spatialite
+ @no_mysql
+ def test03_extent(self):
+ "Testing `extent` on a table with a single point, see #11827."
+ pnt = City.objects.get(name='Pueblo').point
+ ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y)
+ extent = City.objects.filter(name='Pueblo').extent()
+ for ref_val, val in zip(ref_ext, extent):
+ self.assertAlmostEqual(ref_val, val, 4)
diff --git a/parts/django/django/contrib/gis/tests/geoapp/test_sitemaps.py b/parts/django/django/contrib/gis/tests/geoapp/test_sitemaps.py
new file mode 100644
index 0000000..c8c43f4
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/test_sitemaps.py
@@ -0,0 +1,86 @@
+import cStringIO
+from xml.dom import minidom
+import zipfile
+from django.test import TestCase
+
+from models import City, Country
+
+
+class GeoSitemapTest(TestCase):
+
+ urls = 'django.contrib.gis.tests.geoapp.urls'
+
+ def assertChildNodes(self, elem, expected):
+ "Taken from regressiontests/syndication/tests.py."
+ actual = set([n.nodeName for n in elem.childNodes])
+ expected = set(expected)
+ self.assertEqual(actual, expected)
+
+ def test_geositemap_index(self):
+ "Tests geographic sitemap index."
+ # Getting the geo index.
+ doc = minidom.parseString(self.client.get('/sitemap.xml').content)
+ index = doc.firstChild
+ self.assertEqual(index.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9')
+ self.assertEqual(3, len(index.getElementsByTagName('sitemap')))
+
+ def test_geositemap_kml(self):
+ "Tests KML/KMZ geographic sitemaps."
+ for kml_type in ('kml', 'kmz'):
+ doc = minidom.parseString(self.client.get('/sitemaps/%s.xml' % kml_type).content)
+
+ # Ensuring the right sitemaps namespaces are present.
+ urlset = doc.firstChild
+ self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9')
+ self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0')
+
+ urls = urlset.getElementsByTagName('url')
+ self.assertEqual(2, len(urls)) # Should only be 2 sitemaps.
+ for url in urls:
+ self.assertChildNodes(url, ['loc', 'geo:geo'])
+ # Making sure the 'geo:format' element was properly set.
+ geo_elem = url.getElementsByTagName('geo:geo')[0]
+ geo_format = geo_elem.getElementsByTagName('geo:format')[0]
+ self.assertEqual(kml_type, geo_format.childNodes[0].data)
+
+ # Getting the relative URL since we don't have a real site.
+ kml_url = url.getElementsByTagName('loc')[0].childNodes[0].data.split('http://example.com')[1]
+
+ if kml_type == 'kml':
+ kml_doc = minidom.parseString(self.client.get(kml_url).content)
+ elif kml_type == 'kmz':
+ # Have to decompress KMZ before parsing.
+ buf = cStringIO.StringIO(self.client.get(kml_url).content)
+ zf = zipfile.ZipFile(buf)
+ self.assertEqual(1, len(zf.filelist))
+ self.assertEqual('doc.kml', zf.filelist[0].filename)
+ kml_doc = minidom.parseString(zf.read('doc.kml'))
+
+ # Ensuring the correct number of placemarks are in the KML doc.
+ if 'city' in kml_url:
+ model = City
+ elif 'country' in kml_url:
+ model = Country
+ self.assertEqual(model.objects.count(), len(kml_doc.getElementsByTagName('Placemark')))
+
+ def test_geositemap_georss(self):
+ "Tests GeoRSS geographic sitemaps."
+ from feeds import feed_dict
+
+ doc = minidom.parseString(self.client.get('/sitemaps/georss.xml').content)
+
+ # Ensuring the right sitemaps namespaces are present.
+ urlset = doc.firstChild
+ self.assertEqual(urlset.getAttribute(u'xmlns'), u'http://www.sitemaps.org/schemas/sitemap/0.9')
+ self.assertEqual(urlset.getAttribute(u'xmlns:geo'), u'http://www.google.com/geo/schemas/sitemap/1.0')
+
+ # Making sure the correct number of feed URLs were included.
+ urls = urlset.getElementsByTagName('url')
+ self.assertEqual(len(feed_dict), len(urls))
+
+ for url in urls:
+ self.assertChildNodes(url, ['loc', 'geo:geo'])
+ # Making sure the 'geo:format' element was properly set to 'georss'.
+ geo_elem = url.getElementsByTagName('geo:geo')[0]
+ geo_format = geo_elem.getElementsByTagName('geo:format')[0]
+ self.assertEqual('georss', geo_format.childNodes[0].data)
diff --git a/parts/django/django/contrib/gis/tests/geoapp/tests.py b/parts/django/django/contrib/gis/tests/geoapp/tests.py
new file mode 100644
index 0000000..a2b81c6
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/tests.py
@@ -0,0 +1,735 @@
+import re
+from django.db import connection
+from django.contrib.gis import gdal
+from django.contrib.gis.geos import fromstr, GEOSGeometry, \
+ Point, LineString, LinearRing, Polygon, GeometryCollection
+from django.contrib.gis.measure import Distance
+from django.contrib.gis.tests.utils import \
+ no_mysql, no_oracle, no_spatialite, \
+ mysql, oracle, postgis, spatialite
+from django.test import TestCase
+
+from models import Country, City, PennsylvaniaCity, State, Track
+
+if not spatialite:
+ from models import Feature, MinusOneSRID
+
+class GeoModelTest(TestCase):
+
+ def test01_fixtures(self):
+ "Testing geographic model initialization from fixtures."
+ # Ensuring that data was loaded from initial data fixtures.
+ self.assertEqual(2, Country.objects.count())
+ self.assertEqual(8, City.objects.count())
+ self.assertEqual(2, State.objects.count())
+
+ def test02_proxy(self):
+ "Testing Lazy-Geometry support (using the GeometryProxy)."
+ ## Testing on a Point
+ pnt = Point(0, 0)
+ nullcity = City(name='NullCity', point=pnt)
+ nullcity.save()
+
+ # Making sure TypeError is thrown when trying to set with an
+ # incompatible type.
+ for bad in [5, 2.0, LineString((0, 0), (1, 1))]:
+ try:
+ nullcity.point = bad
+ except TypeError:
+ pass
+ else:
+ self.fail('Should throw a TypeError')
+
+ # Now setting with a compatible GEOS Geometry, saving, and ensuring
+ # the save took, notice no SRID is explicitly set.
+ new = Point(5, 23)
+ nullcity.point = new
+
+ # Ensuring that the SRID is automatically set to that of the
+ # field after assignment, but before saving.
+ self.assertEqual(4326, nullcity.point.srid)
+ nullcity.save()
+
+ # Ensuring the point was saved correctly after saving
+ self.assertEqual(new, City.objects.get(name='NullCity').point)
+
+ # Setting the X and Y of the Point
+ nullcity.point.x = 23
+ nullcity.point.y = 5
+ # Checking assignments pre & post-save.
+ self.assertNotEqual(Point(23, 5), City.objects.get(name='NullCity').point)
+ nullcity.save()
+ self.assertEqual(Point(23, 5), City.objects.get(name='NullCity').point)
+ nullcity.delete()
+
+ ## Testing on a Polygon
+ shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0))
+ inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40))
+
+ # Creating a State object using a built Polygon
+ ply = Polygon(shell, inner)
+ nullstate = State(name='NullState', poly=ply)
+ self.assertEqual(4326, nullstate.poly.srid) # SRID auto-set from None
+ nullstate.save()
+
+ ns = State.objects.get(name='NullState')
+ self.assertEqual(ply, ns.poly)
+
+ # Testing the `ogr` and `srs` lazy-geometry properties.
+ if gdal.HAS_GDAL:
+ self.assertEqual(True, isinstance(ns.poly.ogr, gdal.OGRGeometry))
+ self.assertEqual(ns.poly.wkb, ns.poly.ogr.wkb)
+ self.assertEqual(True, isinstance(ns.poly.srs, gdal.SpatialReference))
+ self.assertEqual('WGS 84', ns.poly.srs.name)
+
+ # Changing the interior ring on the poly attribute.
+ new_inner = LinearRing((30, 30), (30, 70), (70, 70), (70, 30), (30, 30))
+ ns.poly[1] = new_inner
+ ply[1] = new_inner
+ self.assertEqual(4326, ns.poly.srid)
+ ns.save()
+ self.assertEqual(ply, State.objects.get(name='NullState').poly)
+ ns.delete()
+
+ def test03a_kml(self):
+ "Testing KML output from the database using GeoQuerySet.kml()."
+ # Only PostGIS supports KML serialization
+ if not postgis:
+ self.assertRaises(NotImplementedError, State.objects.all().kml, field_name='poly')
+ return
+
+ # Should throw a TypeError when trying to obtain KML from a
+ # non-geometry field.
+ qs = City.objects.all()
+ self.assertRaises(TypeError, qs.kml, 'name')
+
+ # The reference KML depends on the version of PostGIS used
+ # (the output stopped including altitude in 1.3.3).
+ if connection.ops.spatial_version >= (1, 3, 3):
+ ref_kml = '<Point><coordinates>-104.609252,38.255001</coordinates></Point>'
+ else:
+ ref_kml = '<Point><coordinates>-104.609252,38.255001,0</coordinates></Point>'
+
+ # Ensuring the KML is as expected.
+ ptown1 = City.objects.kml(field_name='point', precision=9).get(name='Pueblo')
+ ptown2 = City.objects.kml(precision=9).get(name='Pueblo')
+ for ptown in [ptown1, ptown2]:
+ self.assertEqual(ref_kml, ptown.kml)
+
+ def test03b_gml(self):
+ "Testing GML output from the database using GeoQuerySet.gml()."
+ if mysql or spatialite:
+ self.assertRaises(NotImplementedError, Country.objects.all().gml, field_name='mpoly')
+ return
+
+ # Should throw a TypeError when tyring to obtain GML from a
+ # non-geometry field.
+ qs = City.objects.all()
+ self.assertRaises(TypeError, qs.gml, field_name='name')
+ ptown1 = City.objects.gml(field_name='point', precision=9).get(name='Pueblo')
+ ptown2 = City.objects.gml(precision=9).get(name='Pueblo')
+
+ if oracle:
+ # No precision parameter for Oracle :-/
+ gml_regex = re.compile(r'^<gml:Point srsName="SDO:4326" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="\." cs="," ts=" ">-104.60925\d+,38.25500\d+ </gml:coordinates></gml:Point>')
+ for ptown in [ptown1, ptown2]:
+ self.failUnless(gml_regex.match(ptown.gml))
+ else:
+ gml_regex = re.compile(r'^<gml:Point srsName="EPSG:4326"><gml:coordinates>-104\.60925\d+,38\.255001</gml:coordinates></gml:Point>')
+ for ptown in [ptown1, ptown2]:
+ self.failUnless(gml_regex.match(ptown.gml))
+
+ def test03c_geojson(self):
+ "Testing GeoJSON output from the database using GeoQuerySet.geojson()."
+ # Only PostGIS 1.3.4+ supports GeoJSON.
+ if not connection.ops.geojson:
+ self.assertRaises(NotImplementedError, Country.objects.all().geojson, field_name='mpoly')
+ return
+
+ if connection.ops.spatial_version >= (1, 4, 0):
+ pueblo_json = '{"type":"Point","coordinates":[-104.609252,38.255001]}'
+ houston_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"coordinates":[-95.363151,29.763374]}'
+ victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.305196,48.462611]}'
+ chicago_json = '{"type":"Point","crs":{"type":"name","properties":{"name":"EPSG:4326"}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
+ else:
+ pueblo_json = '{"type":"Point","coordinates":[-104.60925200,38.25500100]}'
+ houston_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"coordinates":[-95.36315100,29.76337400]}'
+ victoria_json = '{"type":"Point","bbox":[-123.30519600,48.46261100,-123.30519600,48.46261100],"coordinates":[-123.30519600,48.46261100]}'
+ chicago_json = '{"type":"Point","crs":{"type":"EPSG","properties":{"EPSG":4326}},"bbox":[-87.65018,41.85039,-87.65018,41.85039],"coordinates":[-87.65018,41.85039]}'
+
+ # Precision argument should only be an integer
+ self.assertRaises(TypeError, City.objects.geojson, precision='foo')
+
+ # Reference queries and values.
+ # SELECT ST_AsGeoJson("geoapp_city"."point", 8, 0) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Pueblo';
+ self.assertEqual(pueblo_json, City.objects.geojson().get(name='Pueblo').geojson)
+
+ # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
+ # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
+ # This time we want to include the CRS by using the `crs` keyword.
+ self.assertEqual(houston_json, City.objects.geojson(crs=True, model_att='json').get(name='Houston').json)
+
+ # 1.3.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 2) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Victoria';
+ # 1.4.x: SELECT ST_AsGeoJson("geoapp_city"."point", 8, 1) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Houston';
+ # This time we include the bounding box by using the `bbox` keyword.
+ self.assertEqual(victoria_json, City.objects.geojson(bbox=True).get(name='Victoria').geojson)
+
+ # 1.(3|4).x: SELECT ST_AsGeoJson("geoapp_city"."point", 5, 3) FROM "geoapp_city" WHERE "geoapp_city"."name" = 'Chicago';
+ # Finally, we set every available keyword.
+ self.assertEqual(chicago_json, City.objects.geojson(bbox=True, crs=True, precision=5).get(name='Chicago').geojson)
+
+ def test03d_svg(self):
+ "Testing SVG output using GeoQuerySet.svg()."
+ if mysql or oracle:
+ self.assertRaises(NotImplementedError, City.objects.svg)
+ return
+
+ self.assertRaises(TypeError, City.objects.svg, precision='foo')
+ # SELECT AsSVG(geoapp_city.point, 0, 8) FROM geoapp_city WHERE name = 'Pueblo';
+ svg1 = 'cx="-104.609252" cy="-38.255001"'
+ # Even though relative, only one point so it's practically the same except for
+ # the 'c' letter prefix on the x,y values.
+ svg2 = svg1.replace('c', '')
+ self.assertEqual(svg1, City.objects.svg().get(name='Pueblo').svg)
+ self.assertEqual(svg2, City.objects.svg(relative=5).get(name='Pueblo').svg)
+
+ @no_mysql
+ def test04_transform(self):
+ "Testing the transform() GeoManager method."
+ # Pre-transformed points for Houston and Pueblo.
+ htown = fromstr('POINT(1947516.83115183 6322297.06040572)', srid=3084)
+ ptown = fromstr('POINT(992363.390841912 481455.395105533)', srid=2774)
+ prec = 3 # Precision is low due to version variations in PROJ and GDAL.
+
+ # Asserting the result of the transform operation with the values in
+ # the pre-transformed points. Oracle does not have the 3084 SRID.
+ if not oracle:
+ h = City.objects.transform(htown.srid).get(name='Houston')
+ self.assertEqual(3084, h.point.srid)
+ self.assertAlmostEqual(htown.x, h.point.x, prec)
+ self.assertAlmostEqual(htown.y, h.point.y, prec)
+
+ p1 = City.objects.transform(ptown.srid, field_name='point').get(name='Pueblo')
+ p2 = City.objects.transform(srid=ptown.srid).get(name='Pueblo')
+ for p in [p1, p2]:
+ self.assertEqual(2774, p.point.srid)
+ self.assertAlmostEqual(ptown.x, p.point.x, prec)
+ self.assertAlmostEqual(ptown.y, p.point.y, prec)
+
+ @no_mysql
+ @no_spatialite # SpatiaLite does not have an Extent function
+ def test05_extent(self):
+ "Testing the `extent` GeoQuerySet method."
+ # Reference query:
+ # `SELECT ST_extent(point) FROM geoapp_city WHERE (name='Houston' or name='Dallas');`
+ # => BOX(-96.8016128540039 29.7633724212646,-95.3631439208984 32.7820587158203)
+ expected = (-96.8016128540039, 29.7633724212646, -95.3631439208984, 32.782058715820)
+
+ qs = City.objects.filter(name__in=('Houston', 'Dallas'))
+ extent = qs.extent()
+
+ for val, exp in zip(extent, expected):
+ self.assertAlmostEqual(exp, val, 4)
+
+ # Only PostGIS has support for the MakeLine aggregate.
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test06_make_line(self):
+ "Testing the `make_line` GeoQuerySet method."
+ # Ensuring that a `TypeError` is raised on models without PointFields.
+ self.assertRaises(TypeError, State.objects.make_line)
+ self.assertRaises(TypeError, Country.objects.make_line)
+ # Reference query:
+ # SELECT AsText(ST_MakeLine(geoapp_city.point)) FROM geoapp_city;
+ ref_line = GEOSGeometry('LINESTRING(-95.363151 29.763374,-96.801611 32.782057,-97.521157 34.464642,174.783117 -41.315268,-104.609252 38.255001,-95.23506 38.971823,-87.650175 41.850385,-123.305196 48.462611)', srid=4326)
+ self.assertEqual(ref_line, City.objects.make_line())
+
+ @no_mysql
+ def test09_disjoint(self):
+ "Testing the `disjoint` lookup type."
+ ptown = City.objects.get(name='Pueblo')
+ qs1 = City.objects.filter(point__disjoint=ptown.point)
+ self.assertEqual(7, qs1.count())
+
+ qs2 = State.objects.filter(poly__disjoint=ptown.point)
+ self.assertEqual(1, qs2.count())
+ self.assertEqual('Kansas', qs2[0].name)
+
+ def test10_contains_contained(self):
+ "Testing the 'contained', 'contains', and 'bbcontains' lookup types."
+ # Getting Texas, yes we were a country -- once ;)
+ texas = Country.objects.get(name='Texas')
+
+ # Seeing what cities are in Texas, should get Houston and Dallas,
+ # and Oklahoma City because 'contained' only checks on the
+ # _bounding box_ of the Geometries.
+ if not oracle:
+ qs = City.objects.filter(point__contained=texas.mpoly)
+ self.assertEqual(3, qs.count())
+ cities = ['Houston', 'Dallas', 'Oklahoma City']
+ for c in qs: self.assertEqual(True, c.name in cities)
+
+ # Pulling out some cities.
+ houston = City.objects.get(name='Houston')
+ wellington = City.objects.get(name='Wellington')
+ pueblo = City.objects.get(name='Pueblo')
+ okcity = City.objects.get(name='Oklahoma City')
+ lawrence = City.objects.get(name='Lawrence')
+
+ # Now testing contains on the countries using the points for
+ # Houston and Wellington.
+ tx = Country.objects.get(mpoly__contains=houston.point) # Query w/GEOSGeometry
+ nz = Country.objects.get(mpoly__contains=wellington.point.hex) # Query w/EWKBHEX
+ self.assertEqual('Texas', tx.name)
+ self.assertEqual('New Zealand', nz.name)
+
+ # Spatialite 2.3 thinks that Lawrence is in Puerto Rico (a NULL geometry).
+ if not spatialite:
+ ks = State.objects.get(poly__contains=lawrence.point)
+ self.assertEqual('Kansas', ks.name)
+
+ # Pueblo and Oklahoma City (even though OK City is within the bounding box of Texas)
+ # are not contained in Texas or New Zealand.
+ self.assertEqual(0, len(Country.objects.filter(mpoly__contains=pueblo.point))) # Query w/GEOSGeometry object
+ self.assertEqual((mysql and 1) or 0,
+ len(Country.objects.filter(mpoly__contains=okcity.point.wkt))) # Qeury w/WKT
+
+ # OK City is contained w/in bounding box of Texas.
+ if not oracle:
+ qs = Country.objects.filter(mpoly__bbcontains=okcity.point)
+ self.assertEqual(1, len(qs))
+ self.assertEqual('Texas', qs[0].name)
+
+ @no_mysql
+ def test11_lookup_insert_transform(self):
+ "Testing automatic transform for lookups and inserts."
+ # San Antonio in 'WGS84' (SRID 4326)
+ sa_4326 = 'POINT (-98.493183 29.424170)'
+ wgs_pnt = fromstr(sa_4326, srid=4326) # Our reference point in WGS84
+
+ # Oracle doesn't have SRID 3084, using 41157.
+ if oracle:
+ # San Antonio in 'Texas 4205, Southern Zone (1983, meters)' (SRID 41157)
+ # Used the following Oracle SQL to get this value:
+ # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_CS.TRANSFORM(SDO_GEOMETRY('POINT (-98.493183 29.424170)', 4326), 41157)) FROM DUAL;
+ nad_wkt = 'POINT (300662.034646583 5416427.45974934)'
+ nad_srid = 41157
+ else:
+ # San Antonio in 'NAD83(HARN) / Texas Centric Lambert Conformal' (SRID 3084)
+ nad_wkt = 'POINT (1645978.362408288754523 6276356.025927528738976)' # Used ogr.py in gdal 1.4.1 for this transform
+ nad_srid = 3084
+
+ # Constructing & querying with a point from a different SRID. Oracle
+ # `SDO_OVERLAPBDYINTERSECT` operates differently from
+ # `ST_Intersects`, so contains is used instead.
+ nad_pnt = fromstr(nad_wkt, srid=nad_srid)
+ if oracle:
+ tx = Country.objects.get(mpoly__contains=nad_pnt)
+ else:
+ tx = Country.objects.get(mpoly__intersects=nad_pnt)
+ self.assertEqual('Texas', tx.name)
+
+ # Creating San Antonio. Remember the Alamo.
+ sa = City.objects.create(name='San Antonio', point=nad_pnt)
+
+ # Now verifying that San Antonio was transformed correctly
+ sa = City.objects.get(name='San Antonio')
+ self.assertAlmostEqual(wgs_pnt.x, sa.point.x, 6)
+ self.assertAlmostEqual(wgs_pnt.y, sa.point.y, 6)
+
+ # If the GeometryField SRID is -1, then we shouldn't perform any
+ # transformation if the SRID of the input geometry is different.
+ # SpatiaLite does not support missing SRID values.
+ if not spatialite:
+ m1 = MinusOneSRID(geom=Point(17, 23, srid=4326))
+ m1.save()
+ self.assertEqual(-1, m1.geom.srid)
+
+ @no_mysql
+ def test12_null_geometries(self):
+ "Testing NULL geometry support, and the `isnull` lookup type."
+ # Creating a state with a NULL boundary.
+ State.objects.create(name='Puerto Rico')
+
+ # Querying for both NULL and Non-NULL values.
+ nullqs = State.objects.filter(poly__isnull=True)
+ validqs = State.objects.filter(poly__isnull=False)
+
+ # Puerto Rico should be NULL (it's a commonwealth unincorporated territory)
+ self.assertEqual(1, len(nullqs))
+ self.assertEqual('Puerto Rico', nullqs[0].name)
+
+ # The valid states should be Colorado & Kansas
+ self.assertEqual(2, len(validqs))
+ state_names = [s.name for s in validqs]
+ self.assertEqual(True, 'Colorado' in state_names)
+ self.assertEqual(True, 'Kansas' in state_names)
+
+ # Saving another commonwealth w/a NULL geometry.
+ nmi = State.objects.create(name='Northern Mariana Islands', poly=None)
+ self.assertEqual(nmi.poly, None)
+
+ # Assigning a geomery and saving -- then UPDATE back to NULL.
+ nmi.poly = 'POLYGON((0 0,1 0,1 1,1 0,0 0))'
+ nmi.save()
+ State.objects.filter(name='Northern Mariana Islands').update(poly=None)
+ self.assertEqual(None, State.objects.get(name='Northern Mariana Islands').poly)
+
+ # Only PostGIS has `left` and `right` lookup types.
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test13_left_right(self):
+ "Testing the 'left' and 'right' lookup types."
+ # Left: A << B => true if xmax(A) < xmin(B)
+ # Right: A >> B => true if xmin(A) > xmax(B)
+ # See: BOX2D_left() and BOX2D_right() in lwgeom_box2dfloat4.c in PostGIS source.
+
+ # Getting the borders for Colorado & Kansas
+ co_border = State.objects.get(name='Colorado').poly
+ ks_border = State.objects.get(name='Kansas').poly
+
+ # Note: Wellington has an 'X' value of 174, so it will not be considered
+ # to the left of CO.
+
+ # These cities should be strictly to the right of the CO border.
+ cities = ['Houston', 'Dallas', 'Oklahoma City',
+ 'Lawrence', 'Chicago', 'Wellington']
+ qs = City.objects.filter(point__right=co_border)
+ self.assertEqual(6, len(qs))
+ for c in qs: self.assertEqual(True, c.name in cities)
+
+ # These cities should be strictly to the right of the KS border.
+ cities = ['Chicago', 'Wellington']
+ qs = City.objects.filter(point__right=ks_border)
+ self.assertEqual(2, len(qs))
+ for c in qs: self.assertEqual(True, c.name in cities)
+
+ # Note: Wellington has an 'X' value of 174, so it will not be considered
+ # to the left of CO.
+ vic = City.objects.get(point__left=co_border)
+ self.assertEqual('Victoria', vic.name)
+
+ cities = ['Pueblo', 'Victoria']
+ qs = City.objects.filter(point__left=ks_border)
+ self.assertEqual(2, len(qs))
+ for c in qs: self.assertEqual(True, c.name in cities)
+
+ def test14_equals(self):
+ "Testing the 'same_as' and 'equals' lookup types."
+ pnt = fromstr('POINT (-95.363151 29.763374)', srid=4326)
+ c1 = City.objects.get(point=pnt)
+ c2 = City.objects.get(point__same_as=pnt)
+ c3 = City.objects.get(point__equals=pnt)
+ for c in [c1, c2, c3]: self.assertEqual('Houston', c.name)
+
+ @no_mysql
+ def test15_relate(self):
+ "Testing the 'relate' lookup type."
+ # To make things more interesting, we will have our Texas reference point in
+ # different SRIDs.
+ pnt1 = fromstr('POINT (649287.0363174 4177429.4494686)', srid=2847)
+ pnt2 = fromstr('POINT(-98.4919715741052 29.4333344025053)', srid=4326)
+
+ # Not passing in a geometry as first param shoud
+ # raise a type error when initializing the GeoQuerySet
+ self.assertRaises(ValueError, Country.objects.filter, mpoly__relate=(23, 'foo'))
+
+ # Making sure the right exception is raised for the given
+ # bad arguments.
+ for bad_args, e in [((pnt1, 0), ValueError), ((pnt2, 'T*T***FF*', 0), ValueError)]:
+ qs = Country.objects.filter(mpoly__relate=bad_args)
+ self.assertRaises(e, qs.count)
+
+ # Relate works differently for the different backends.
+ if postgis or spatialite:
+ contains_mask = 'T*T***FF*'
+ within_mask = 'T*F**F***'
+ intersects_mask = 'T********'
+ elif oracle:
+ contains_mask = 'contains'
+ within_mask = 'inside'
+ # TODO: This is not quite the same as the PostGIS mask above
+ intersects_mask = 'overlapbdyintersect'
+
+ # Testing contains relation mask.
+ self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, contains_mask)).name)
+ self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, contains_mask)).name)
+
+ # Testing within relation mask.
+ ks = State.objects.get(name='Kansas')
+ self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, within_mask)).name)
+
+ # Testing intersection relation mask.
+ if not oracle:
+ self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt1, intersects_mask)).name)
+ self.assertEqual('Texas', Country.objects.get(mpoly__relate=(pnt2, intersects_mask)).name)
+ self.assertEqual('Lawrence', City.objects.get(point__relate=(ks.poly, intersects_mask)).name)
+
+ def test16_createnull(self):
+ "Testing creating a model instance and the geometry being None"
+ c = City()
+ self.assertEqual(c.point, None)
+
+ @no_mysql
+ def test17_unionagg(self):
+ "Testing the `unionagg` (aggregate union) GeoManager method."
+ tx = Country.objects.get(name='Texas').mpoly
+ # Houston, Dallas -- Oracle has different order.
+ union1 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
+ union2 = fromstr('MULTIPOINT(-96.801611 32.782057,-95.363151 29.763374)')
+ qs = City.objects.filter(point__within=tx)
+ self.assertRaises(TypeError, qs.unionagg, 'name')
+ # Using `field_name` keyword argument in one query and specifying an
+ # order in the other (which should not be used because this is
+ # an aggregate method on a spatial column)
+ u1 = qs.unionagg(field_name='point')
+ u2 = qs.order_by('name').unionagg()
+ tol = 0.00001
+ if oracle:
+ union = union2
+ else:
+ union = union1
+ self.assertEqual(True, union.equals_exact(u1, tol))
+ self.assertEqual(True, union.equals_exact(u2, tol))
+ qs = City.objects.filter(name='NotACity')
+ self.assertEqual(None, qs.unionagg(field_name='point'))
+
+ @no_spatialite # SpatiaLite does not support abstract geometry columns
+ def test18_geometryfield(self):
+ "Testing the general GeometryField."
+ Feature(name='Point', geom=Point(1, 1)).save()
+ Feature(name='LineString', geom=LineString((0, 0), (1, 1), (5, 5))).save()
+ Feature(name='Polygon', geom=Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0)))).save()
+ Feature(name='GeometryCollection',
+ geom=GeometryCollection(Point(2, 2), LineString((0, 0), (2, 2)),
+ Polygon(LinearRing((0, 0), (0, 5), (5, 5), (5, 0), (0, 0))))).save()
+
+ f_1 = Feature.objects.get(name='Point')
+ self.assertEqual(True, isinstance(f_1.geom, Point))
+ self.assertEqual((1.0, 1.0), f_1.geom.tuple)
+ f_2 = Feature.objects.get(name='LineString')
+ self.assertEqual(True, isinstance(f_2.geom, LineString))
+ self.assertEqual(((0.0, 0.0), (1.0, 1.0), (5.0, 5.0)), f_2.geom.tuple)
+
+ f_3 = Feature.objects.get(name='Polygon')
+ self.assertEqual(True, isinstance(f_3.geom, Polygon))
+ f_4 = Feature.objects.get(name='GeometryCollection')
+ self.assertEqual(True, isinstance(f_4.geom, GeometryCollection))
+ self.assertEqual(f_3.geom, f_4.geom[2])
+
+ @no_mysql
+ def test19_centroid(self):
+ "Testing the `centroid` GeoQuerySet method."
+ qs = State.objects.exclude(poly__isnull=True).centroid()
+ if oracle:
+ tol = 0.1
+ elif spatialite:
+ tol = 0.000001
+ else:
+ tol = 0.000000001
+ for s in qs:
+ self.assertEqual(True, s.poly.centroid.equals_exact(s.centroid, tol))
+
+ @no_mysql
+ def test20_pointonsurface(self):
+ "Testing the `point_on_surface` GeoQuerySet method."
+ # Reference values.
+ if oracle:
+ # SELECT SDO_UTIL.TO_WKTGEOMETRY(SDO_GEOM.SDO_POINTONSURFACE(GEOAPP_COUNTRY.MPOLY, 0.05)) FROM GEOAPP_COUNTRY;
+ ref = {'New Zealand' : fromstr('POINT (174.616364 -36.100861)', srid=4326),
+ 'Texas' : fromstr('POINT (-103.002434 36.500397)', srid=4326),
+ }
+
+ elif postgis or spatialite:
+ # Using GEOSGeometry to compute the reference point on surface values
+ # -- since PostGIS also uses GEOS these should be the same.
+ ref = {'New Zealand' : Country.objects.get(name='New Zealand').mpoly.point_on_surface,
+ 'Texas' : Country.objects.get(name='Texas').mpoly.point_on_surface
+ }
+
+ for c in Country.objects.point_on_surface():
+ if spatialite:
+ # XXX This seems to be a WKT-translation-related precision issue?
+ tol = 0.00001
+ else:
+ tol = 0.000000001
+ self.assertEqual(True, ref[c.name].equals_exact(c.point_on_surface, tol))
+
+ @no_mysql
+ @no_oracle
+ def test21_scale(self):
+ "Testing the `scale` GeoQuerySet method."
+ xfac, yfac = 2, 3
+ tol = 5 # XXX The low precision tolerance is for SpatiaLite
+ qs = Country.objects.scale(xfac, yfac, model_att='scaled')
+ for c in qs:
+ for p1, p2 in zip(c.mpoly, c.scaled):
+ for r1, r2 in zip(p1, p2):
+ for c1, c2 in zip(r1.coords, r2.coords):
+ self.assertAlmostEqual(c1[0] * xfac, c2[0], tol)
+ self.assertAlmostEqual(c1[1] * yfac, c2[1], tol)
+
+ @no_mysql
+ @no_oracle
+ def test22_translate(self):
+ "Testing the `translate` GeoQuerySet method."
+ xfac, yfac = 5, -23
+ qs = Country.objects.translate(xfac, yfac, model_att='translated')
+ for c in qs:
+ for p1, p2 in zip(c.mpoly, c.translated):
+ for r1, r2 in zip(p1, p2):
+ for c1, c2 in zip(r1.coords, r2.coords):
+ # XXX The low precision is for SpatiaLite
+ self.assertAlmostEqual(c1[0] + xfac, c2[0], 5)
+ self.assertAlmostEqual(c1[1] + yfac, c2[1], 5)
+
+ @no_mysql
+ def test23_numgeom(self):
+ "Testing the `num_geom` GeoQuerySet method."
+ # Both 'countries' only have two geometries.
+ for c in Country.objects.num_geom(): self.assertEqual(2, c.num_geom)
+ for c in City.objects.filter(point__isnull=False).num_geom():
+ # Oracle will return 1 for the number of geometries on non-collections,
+ # whereas PostGIS will return None.
+ if postgis:
+ self.assertEqual(None, c.num_geom)
+ else:
+ self.assertEqual(1, c.num_geom)
+
+ @no_mysql
+ @no_spatialite # SpatiaLite can only count vertices in LineStrings
+ def test24_numpoints(self):
+ "Testing the `num_points` GeoQuerySet method."
+ for c in Country.objects.num_points():
+ self.assertEqual(c.mpoly.num_points, c.num_points)
+
+ if not oracle:
+ # Oracle cannot count vertices in Point geometries.
+ for c in City.objects.num_points(): self.assertEqual(1, c.num_points)
+
+ @no_mysql
+ def test25_geoset(self):
+ "Testing the `difference`, `intersection`, `sym_difference`, and `union` GeoQuerySet methods."
+ geom = Point(5, 23)
+ tol = 1
+ qs = Country.objects.all().difference(geom).sym_difference(geom).union(geom)
+
+ # XXX For some reason SpatiaLite does something screwey with the Texas geometry here. Also,
+ # XXX it doesn't like the null intersection.
+ if spatialite:
+ qs = qs.exclude(name='Texas')
+ else:
+ qs = qs.intersection(geom)
+
+ for c in qs:
+ if oracle:
+ # Should be able to execute the queries; however, they won't be the same
+ # as GEOS (because Oracle doesn't use GEOS internally like PostGIS or
+ # SpatiaLite).
+ pass
+ else:
+ self.assertEqual(c.mpoly.difference(geom), c.difference)
+ if not spatialite:
+ self.assertEqual(c.mpoly.intersection(geom), c.intersection)
+ self.assertEqual(c.mpoly.sym_difference(geom), c.sym_difference)
+ self.assertEqual(c.mpoly.union(geom), c.union)
+
+ @no_mysql
+ def test26_inherited_geofields(self):
+ "Test GeoQuerySet methods on inherited Geometry fields."
+ # Creating a Pennsylvanian city.
+ mansfield = PennsylvaniaCity.objects.create(name='Mansfield', county='Tioga', point='POINT(-77.071445 41.823881)')
+
+ # All transformation SQL will need to be performed on the
+ # _parent_ table.
+ qs = PennsylvaniaCity.objects.transform(32128)
+
+ self.assertEqual(1, qs.count())
+ for pc in qs: self.assertEqual(32128, pc.point.srid)
+
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test27_snap_to_grid(self):
+ "Testing GeoQuerySet.snap_to_grid()."
+ # Let's try and break snap_to_grid() with bad combinations of arguments.
+ for bad_args in ((), range(3), range(5)):
+ self.assertRaises(ValueError, Country.objects.snap_to_grid, *bad_args)
+ for bad_args in (('1.0',), (1.0, None), tuple(map(unicode, range(4)))):
+ self.assertRaises(TypeError, Country.objects.snap_to_grid, *bad_args)
+
+ # Boundary for San Marino, courtesy of Bjorn Sandvik of thematicmapping.org
+ # from the world borders dataset he provides.
+ wkt = ('MULTIPOLYGON(((12.41580 43.95795,12.45055 43.97972,12.45389 43.98167,'
+ '12.46250 43.98472,12.47167 43.98694,12.49278 43.98917,'
+ '12.50555 43.98861,12.51000 43.98694,12.51028 43.98277,'
+ '12.51167 43.94333,12.51056 43.93916,12.49639 43.92333,'
+ '12.49500 43.91472,12.48778 43.90583,12.47444 43.89722,'
+ '12.46472 43.89555,12.45917 43.89611,12.41639 43.90472,'
+ '12.41222 43.90610,12.40782 43.91366,12.40389 43.92667,'
+ '12.40500 43.94833,12.40889 43.95499,12.41580 43.95795)))')
+ sm = Country.objects.create(name='San Marino', mpoly=fromstr(wkt))
+
+ # Because floating-point arithmitic isn't exact, we set a tolerance
+ # to pass into GEOS `equals_exact`.
+ tol = 0.000000001
+
+ # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.1)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
+ ref = fromstr('MULTIPOLYGON(((12.4 44,12.5 44,12.5 43.9,12.4 43.9,12.4 44)))')
+ self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.1).get(name='San Marino').snap_to_grid, tol))
+
+ # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
+ ref = fromstr('MULTIPOLYGON(((12.4 43.93,12.45 43.93,12.5 43.93,12.45 43.93,12.4 43.93)))')
+ self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23).get(name='San Marino').snap_to_grid, tol))
+
+ # SELECT AsText(ST_SnapToGrid("geoapp_country"."mpoly", 0.5, 0.17, 0.05, 0.23)) FROM "geoapp_country" WHERE "geoapp_country"."name" = 'San Marino';
+ ref = fromstr('MULTIPOLYGON(((12.4 43.87,12.45 43.87,12.45 44.1,12.5 44.1,12.5 43.87,12.45 43.87,12.4 43.87)))')
+ self.failUnless(ref.equals_exact(Country.objects.snap_to_grid(0.05, 0.23, 0.5, 0.17).get(name='San Marino').snap_to_grid, tol))
+
+ @no_mysql
+ @no_spatialite
+ def test28_reverse(self):
+ "Testing GeoQuerySet.reverse_geom()."
+ coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ]
+ Track.objects.create(name='Foo', line=LineString(coords))
+ t = Track.objects.reverse_geom().get(name='Foo')
+ coords.reverse()
+ self.assertEqual(tuple(coords), t.reverse_geom.coords)
+ if oracle:
+ self.assertRaises(TypeError, State.objects.reverse_geom)
+
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test29_force_rhr(self):
+ "Testing GeoQuerySet.force_rhr()."
+ rings = ( ( (0, 0), (5, 0), (0, 5), (0, 0) ),
+ ( (1, 1), (1, 3), (3, 1), (1, 1) ),
+ )
+ rhr_rings = ( ( (0, 0), (0, 5), (5, 0), (0, 0) ),
+ ( (1, 1), (3, 1), (1, 3), (1, 1) ),
+ )
+ State.objects.create(name='Foo', poly=Polygon(*rings))
+ s = State.objects.force_rhr().get(name='Foo')
+ self.assertEqual(rhr_rings, s.force_rhr.coords)
+
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test29_force_rhr(self):
+ "Testing GeoQuerySet.geohash()."
+ if not connection.ops.geohash: return
+ # Reference query:
+ # SELECT ST_GeoHash(point) FROM geoapp_city WHERE name='Houston';
+ # SELECT ST_GeoHash(point, 5) FROM geoapp_city WHERE name='Houston';
+ ref_hash = '9vk1mfq8jx0c8e0386z6'
+ h1 = City.objects.geohash().get(name='Houston')
+ h2 = City.objects.geohash(precision=5).get(name='Houston')
+ self.assertEqual(ref_hash, h1.geohash)
+ self.assertEqual(ref_hash[:5], h2.geohash)
+
+from test_feeds import GeoFeedTest
+from test_regress import GeoRegressionTests
+from test_sitemaps import GeoSitemapTest
diff --git a/parts/django/django/contrib/gis/tests/geoapp/urls.py b/parts/django/django/contrib/gis/tests/geoapp/urls.py
new file mode 100644
index 0000000..edaf280
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geoapp/urls.py
@@ -0,0 +1,14 @@
+from django.conf.urls.defaults import *
+from feeds import feed_dict
+
+urlpatterns = patterns('',
+ (r'^feeds/(?P<url>.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict': feed_dict}),
+)
+
+from sitemaps import sitemaps
+urlpatterns += patterns('django.contrib.gis.sitemaps.views',
+ (r'^sitemap.xml$', 'index', {'sitemaps' : sitemaps}),
+ (r'^sitemaps/(?P<section>\w+)\.xml$', 'sitemap', {'sitemaps' : sitemaps}),
+ (r'^sitemaps/kml/(?P<label>\w+)/(?P<model>\w+)/(?P<field_name>\w+)\.kml$', 'kml'),
+ (r'^sitemaps/kml/(?P<label>\w+)/(?P<model>\w+)/(?P<field_name>\w+)\.kmz$', 'kmz'),
+)
diff --git a/parts/django/django/contrib/gis/tests/geogapp/__init__.py b/parts/django/django/contrib/gis/tests/geogapp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geogapp/__init__.py
diff --git a/parts/django/django/contrib/gis/tests/geogapp/fixtures/initial_data.json b/parts/django/django/contrib/gis/tests/geogapp/fixtures/initial_data.json
new file mode 100644
index 0000000..0664411
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geogapp/fixtures/initial_data.json
@@ -0,0 +1,98 @@
+[
+ {
+ "pk": 1,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Houston",
+ "point": "POINT (-95.363151 29.763374)"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Dallas",
+ "point": "POINT (-96.801611 32.782057)"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Oklahoma City",
+ "point": "POINT (-97.521157 34.464642)"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Wellington",
+ "point": "POINT (174.783117 -41.315268)"
+ }
+ },
+ {
+ "pk": 5,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Pueblo",
+ "point": "POINT (-104.609252 38.255001)"
+ }
+ },
+ {
+ "pk": 6,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Lawrence",
+ "point": "POINT (-95.235060 38.971823)"
+ }
+ },
+ {
+ "pk": 7,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Chicago",
+ "point": "POINT (-87.650175 41.850385)"
+ }
+ },
+ {
+ "pk": 8,
+ "model": "geogapp.city",
+ "fields": {
+ "name": "Victoria",
+ "point": "POINT (-123.305196 48.462611)"
+ }
+ },
+ {
+ "pk": 1,
+ "model": "geogapp.zipcode",
+ "fields" : {
+ "code" : "77002",
+ "poly" : "SRID=4269;POLYGON ((-95.365015 29.772327, -95.362415 29.772327, -95.360915 29.771827, -95.354615 29.771827, -95.351515 29.772527, -95.350915 29.765327, -95.351015 29.762436, -95.350115 29.760328, -95.347515 29.758528, -95.352315 29.753928, -95.356415 29.756328, -95.358215 29.754028, -95.360215 29.756328, -95.363415 29.757128, -95.364014 29.75638, -95.363415 29.753928, -95.360015 29.751828, -95.361815 29.749528, -95.362715 29.750028, -95.367516 29.744128, -95.369316 29.745128, -95.373916 29.744128, -95.380116 29.738028, -95.387916 29.727929, -95.388516 29.729629, -95.387916 29.732129, -95.382916 29.737428, -95.376616 29.742228, -95.372616 29.747228, -95.378601 29.750846, -95.378616 29.752028, -95.378616 29.754428, -95.376016 29.754528, -95.374616 29.759828, -95.373616 29.761128, -95.371916 29.763928, -95.372316 29.768727, -95.365884 29.76791, -95.366015 29.767127, -95.358715 29.765327, -95.358615 29.766327, -95.359115 29.767227, -95.360215 29.767027, -95.362783 29.768267, -95.365315 29.770527, -95.365015 29.772327))"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "geogapp.zipcode",
+ "fields" : {
+ "code" : "77005",
+ "poly" : "SRID=4269;POLYGON ((-95.447918 29.727275, -95.428017 29.728729, -95.421117 29.729029, -95.418617 29.727629, -95.418517 29.726429, -95.402117 29.726629, -95.402117 29.725729, -95.395316 29.725729, -95.391916 29.726229, -95.389716 29.725829, -95.396517 29.715429, -95.397517 29.715929, -95.400917 29.711429, -95.411417 29.715029, -95.418417 29.714729, -95.418317 29.70623, -95.440818 29.70593, -95.445018 29.70683, -95.446618 29.70763, -95.447418 29.71003, -95.447918 29.727275))"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "geogapp.zipcode",
+ "fields" : {
+ "code" : "77025",
+ "poly" : "SRID=4269;POLYGON ((-95.418317 29.70623, -95.414717 29.706129, -95.414617 29.70533, -95.418217 29.70533, -95.419817 29.69533, -95.419484 29.694196, -95.417166 29.690901, -95.414517 29.69433, -95.413317 29.69263, -95.412617 29.68973, -95.412817 29.68753, -95.414087 29.685055, -95.419165 29.685428, -95.421617 29.68513, -95.425717 29.67983, -95.425017 29.67923, -95.424517 29.67763, -95.427418 29.67763, -95.438018 29.664631, -95.436713 29.664411, -95.440118 29.662231, -95.439218 29.661031, -95.437718 29.660131, -95.435718 29.659731, -95.431818 29.660331, -95.441418 29.656631, -95.441318 29.656331, -95.441818 29.656131, -95.441718 29.659031, -95.441118 29.661031, -95.446718 29.656431, -95.446518 29.673431, -95.446918 29.69013, -95.447418 29.71003, -95.446618 29.70763, -95.445018 29.70683, -95.440818 29.70593, -95.418317 29.70623))"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "geogapp.zipcode",
+ "fields" : {
+ "code" : "77401",
+ "poly" : "SRID=4269;POLYGON ((-95.447918 29.727275, -95.447418 29.71003, -95.446918 29.69013, -95.454318 29.68893, -95.475819 29.68903, -95.475819 29.69113, -95.484419 29.69103, -95.484519 29.69903, -95.480419 29.70133, -95.480419 29.69833, -95.474119 29.69833, -95.474119 29.70453, -95.472719 29.71283, -95.468019 29.71293, -95.468219 29.720229, -95.464018 29.720229, -95.464118 29.724529, -95.463018 29.725929, -95.459818 29.726129, -95.459918 29.720329, -95.451418 29.720429, -95.451775 29.726303, -95.451318 29.727029, -95.447918 29.727275))"
+ }
+ }
+] \ No newline at end of file
diff --git a/parts/django/django/contrib/gis/tests/geogapp/models.py b/parts/django/django/contrib/gis/tests/geogapp/models.py
new file mode 100644
index 0000000..3696ba2
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geogapp/models.py
@@ -0,0 +1,20 @@
+from django.contrib.gis.db import models
+
+class City(models.Model):
+ name = models.CharField(max_length=30)
+ point = models.PointField(geography=True)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class Zipcode(models.Model):
+ code = models.CharField(max_length=10)
+ poly = models.PolygonField(geography=True)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.code
+
+class County(models.Model):
+ name = models.CharField(max_length=25)
+ state = models.CharField(max_length=20)
+ mpoly = models.MultiPolygonField(geography=True)
+ objects = models.GeoManager()
+ def __unicode__(self): return ' County, '.join([self.name, self.state])
diff --git a/parts/django/django/contrib/gis/tests/geogapp/tests.py b/parts/django/django/contrib/gis/tests/geogapp/tests.py
new file mode 100644
index 0000000..cb69ce9
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/geogapp/tests.py
@@ -0,0 +1,87 @@
+"""
+Tests for geography support in PostGIS 1.5+
+"""
+import os
+from django.contrib.gis import gdal
+from django.contrib.gis.measure import D
+from django.test import TestCase
+from models import City, County, Zipcode
+
+class GeographyTest(TestCase):
+
+ def test01_fixture_load(self):
+ "Ensure geography features loaded properly."
+ self.assertEqual(8, City.objects.count())
+
+ def test02_distance_lookup(self):
+ "Testing GeoQuerySet distance lookup support on non-point geography fields."
+ z = Zipcode.objects.get(code='77002')
+ cities1 = list(City.objects
+ .filter(point__distance_lte=(z.poly, D(mi=500)))
+ .order_by('name')
+ .values_list('name', flat=True))
+ cities2 = list(City.objects
+ .filter(point__dwithin=(z.poly, D(mi=500)))
+ .order_by('name')
+ .values_list('name', flat=True))
+ for cities in [cities1, cities2]:
+ self.assertEqual(['Dallas', 'Houston', 'Oklahoma City'], cities)
+
+ def test03_distance_method(self):
+ "Testing GeoQuerySet.distance() support on non-point geography fields."
+ # `GeoQuerySet.distance` is not allowed geometry fields.
+ htown = City.objects.get(name='Houston')
+ qs = Zipcode.objects.distance(htown.point)
+
+ def test04_invalid_operators_functions(self):
+ "Ensuring exceptions are raised for operators & functions invalid on geography fields."
+ # Only a subset of the geometry functions & operator are available
+ # to PostGIS geography types. For more information, visit:
+ # http://postgis.refractions.net/documentation/manual-1.5/ch08.html#PostGIS_GeographyFunctions
+ z = Zipcode.objects.get(code='77002')
+ # ST_Within not available.
+ self.assertRaises(ValueError, City.objects.filter(point__within=z.poly).count)
+ # `@` operator not available.
+ self.assertRaises(ValueError, City.objects.filter(point__contained=z.poly).count)
+
+ # Regression test for #14060, `~=` was never really implemented for PostGIS.
+ htown = City.objects.get(name='Houston')
+ self.assertRaises(ValueError, City.objects.get, point__exact=htown.point)
+
+ def test05_geography_layermapping(self):
+ "Testing LayerMapping support on models with geography fields."
+ # There is a similar test in `layermap` that uses the same data set,
+ # but the County model here is a bit different.
+ if not gdal.HAS_GDAL: return
+ from django.contrib.gis.utils import LayerMapping
+
+ # Getting the shapefile and mapping dictionary.
+ shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
+ co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
+ co_mapping = {'name' : 'Name',
+ 'state' : 'State',
+ 'mpoly' : 'MULTIPOLYGON',
+ }
+
+ # Reference county names, number of polygons, and state names.
+ names = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
+ num_polys = [1, 2, 1, 19, 1] # Number of polygons for each.
+ st_names = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
+
+ lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269, unique='name')
+ lm.save(silent=True, strict=True)
+
+ for c, name, num_poly, state in zip(County.objects.order_by('name'), names, num_polys, st_names):
+ self.assertEqual(4326, c.mpoly.srid)
+ self.assertEqual(num_poly, len(c.mpoly))
+ self.assertEqual(name, c.name)
+ self.assertEqual(state, c.state)
+
+ def test06_geography_area(self):
+ "Testing that Area calculations work on geography columns."
+ from django.contrib.gis.measure import A
+ # SELECT ST_Area(poly) FROM geogapp_zipcode WHERE code='77002';
+ ref_area = 5439084.70637573
+ tol = 5
+ z = Zipcode.objects.area().get(code='77002')
+ self.assertAlmostEqual(z.area.sq_m, ref_area, tol)
diff --git a/parts/django/django/contrib/gis/tests/layermap/__init__.py b/parts/django/django/contrib/gis/tests/layermap/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/layermap/__init__.py
diff --git a/parts/django/django/contrib/gis/tests/layermap/models.py b/parts/django/django/contrib/gis/tests/layermap/models.py
new file mode 100644
index 0000000..3a34d16
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/layermap/models.py
@@ -0,0 +1,66 @@
+from django.contrib.gis.db import models
+
+class State(models.Model):
+ name = models.CharField(max_length=20)
+ objects = models.GeoManager()
+
+class County(models.Model):
+ name = models.CharField(max_length=25)
+ state = models.ForeignKey(State)
+ mpoly = models.MultiPolygonField(srid=4269) # Multipolygon in NAD83
+ objects = models.GeoManager()
+
+class CountyFeat(models.Model):
+ name = models.CharField(max_length=25)
+ poly = models.PolygonField(srid=4269)
+ objects = models.GeoManager()
+
+class City(models.Model):
+ name = models.CharField(max_length=25)
+ population = models.IntegerField()
+ density = models.DecimalField(max_digits=7, decimal_places=1)
+ dt = models.DateField()
+ point = models.PointField()
+ objects = models.GeoManager()
+
+class Interstate(models.Model):
+ name = models.CharField(max_length=20)
+ length = models.DecimalField(max_digits=6, decimal_places=2)
+ path = models.LineStringField()
+ objects = models.GeoManager()
+
+# Same as `City` above, but for testing model inheritance.
+class CityBase(models.Model):
+ name = models.CharField(max_length=25)
+ population = models.IntegerField()
+ density = models.DecimalField(max_digits=7, decimal_places=1)
+ point = models.PointField()
+ objects = models.GeoManager()
+
+class ICity1(CityBase):
+ dt = models.DateField()
+
+class ICity2(ICity1):
+ dt_time = models.DateTimeField(auto_now=True)
+
+# Mapping dictionaries for the models above.
+co_mapping = {'name' : 'Name',
+ 'state' : {'name' : 'State'}, # ForeignKey's use another mapping dictionary for the _related_ Model (State in this case).
+ 'mpoly' : 'MULTIPOLYGON', # Will convert POLYGON features into MULTIPOLYGONS.
+ }
+
+cofeat_mapping = {'name' : 'Name',
+ 'poly' : 'POLYGON',
+ }
+
+city_mapping = {'name' : 'Name',
+ 'population' : 'Population',
+ 'density' : 'Density',
+ 'dt' : 'Created',
+ 'point' : 'POINT',
+ }
+
+inter_mapping = {'name' : 'Name',
+ 'length' : 'Length',
+ 'path' : 'LINESTRING',
+ }
diff --git a/parts/django/django/contrib/gis/tests/layermap/tests.py b/parts/django/django/contrib/gis/tests/layermap/tests.py
new file mode 100644
index 0000000..6394b04
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/layermap/tests.py
@@ -0,0 +1,268 @@
+import os
+import unittest
+from decimal import Decimal
+
+from django.utils.copycompat import copy
+
+from django.contrib.gis.gdal import DataSource
+from django.contrib.gis.tests.utils import mysql
+from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey
+
+from models import City, County, CountyFeat, Interstate, ICity1, ICity2, State, city_mapping, co_mapping, cofeat_mapping, inter_mapping
+
+shp_path = os.path.realpath(os.path.join(os.path.dirname(__file__), os.pardir, 'data'))
+city_shp = os.path.join(shp_path, 'cities', 'cities.shp')
+co_shp = os.path.join(shp_path, 'counties', 'counties.shp')
+inter_shp = os.path.join(shp_path, 'interstates', 'interstates.shp')
+
+# Dictionaries to hold what's expected in the county shapefile.
+NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo']
+NUMS = [1, 2, 1, 19, 1] # Number of polygons for each.
+STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado']
+
+class LayerMapTest(unittest.TestCase):
+
+ def test01_init(self):
+ "Testing LayerMapping initialization."
+
+ # Model field that does not exist.
+ bad1 = copy(city_mapping)
+ bad1['foobar'] = 'FooField'
+
+ # Shapefile field that does not exist.
+ bad2 = copy(city_mapping)
+ bad2['name'] = 'Nombre'
+
+ # Nonexistent geographic field type.
+ bad3 = copy(city_mapping)
+ bad3['point'] = 'CURVE'
+
+ # Incrementing through the bad mapping dictionaries and
+ # ensuring that a LayerMapError is raised.
+ for bad_map in (bad1, bad2, bad3):
+ try:
+ lm = LayerMapping(City, city_shp, bad_map)
+ except LayerMapError:
+ pass
+ else:
+ self.fail('Expected a LayerMapError.')
+
+ # A LookupError should be thrown for bogus encodings.
+ try:
+ lm = LayerMapping(City, city_shp, city_mapping, encoding='foobar')
+ except LookupError:
+ pass
+ else:
+ self.fail('Expected a LookupError')
+
+ def test02_simple_layermap(self):
+ "Test LayerMapping import of a simple point shapefile."
+ # Setting up for the LayerMapping.
+ lm = LayerMapping(City, city_shp, city_mapping)
+ lm.save()
+
+ # There should be three cities in the shape file.
+ self.assertEqual(3, City.objects.count())
+
+ # Opening up the shapefile, and verifying the values in each
+ # of the features made it to the model.
+ ds = DataSource(city_shp)
+ layer = ds[0]
+ for feat in layer:
+ city = City.objects.get(name=feat['Name'].value)
+ self.assertEqual(feat['Population'].value, city.population)
+ self.assertEqual(Decimal(str(feat['Density'])), city.density)
+ self.assertEqual(feat['Created'].value, city.dt)
+
+ # Comparing the geometries.
+ pnt1, pnt2 = feat.geom, city.point
+ self.assertAlmostEqual(pnt1.x, pnt2.x, 6)
+ self.assertAlmostEqual(pnt1.y, pnt2.y, 6)
+
+ def test03_layermap_strict(self):
+ "Testing the `strict` keyword, and import of a LineString shapefile."
+ # When the `strict` keyword is set an error encountered will force
+ # the importation to stop.
+ try:
+ lm = LayerMapping(Interstate, inter_shp, inter_mapping)
+ lm.save(silent=True, strict=True)
+ except InvalidDecimal:
+ # No transactions for geoms on MySQL; delete added features.
+ if mysql: Interstate.objects.all().delete()
+ else:
+ self.fail('Should have failed on strict import with invalid decimal values.')
+
+ # This LayerMapping should work b/c `strict` is not set.
+ lm = LayerMapping(Interstate, inter_shp, inter_mapping)
+ lm.save(silent=True)
+
+ # Two interstate should have imported correctly.
+ self.assertEqual(2, Interstate.objects.count())
+
+ # Verifying the values in the layer w/the model.
+ ds = DataSource(inter_shp)
+
+ # Only the first two features of this shapefile are valid.
+ valid_feats = ds[0][:2]
+ for feat in valid_feats:
+ istate = Interstate.objects.get(name=feat['Name'].value)
+
+ if feat.fid == 0:
+ self.assertEqual(Decimal(str(feat['Length'])), istate.length)
+ elif feat.fid == 1:
+ # Everything but the first two decimal digits were truncated,
+ # because the Interstate model's `length` field has decimal_places=2.
+ self.assertAlmostEqual(feat.get('Length'), float(istate.length), 2)
+
+ for p1, p2 in zip(feat.geom, istate.path):
+ self.assertAlmostEqual(p1[0], p2[0], 6)
+ self.assertAlmostEqual(p1[1], p2[1], 6)
+
+ def county_helper(self, county_feat=True):
+ "Helper function for ensuring the integrity of the mapped County models."
+ for name, n, st in zip(NAMES, NUMS, STATES):
+ # Should only be one record b/c of `unique` keyword.
+ c = County.objects.get(name=name)
+ self.assertEqual(n, len(c.mpoly))
+ self.assertEqual(st, c.state.name) # Checking ForeignKey mapping.
+
+ # Multiple records because `unique` was not set.
+ if county_feat:
+ qs = CountyFeat.objects.filter(name=name)
+ self.assertEqual(n, qs.count())
+
+ def test04_layermap_unique_multigeometry_fk(self):
+ "Testing the `unique`, and `transform`, geometry collection conversion, and ForeignKey mappings."
+ # All the following should work.
+ try:
+ # Telling LayerMapping that we want no transformations performed on the data.
+ lm = LayerMapping(County, co_shp, co_mapping, transform=False)
+
+ # Specifying the source spatial reference system via the `source_srs` keyword.
+ lm = LayerMapping(County, co_shp, co_mapping, source_srs=4269)
+ lm = LayerMapping(County, co_shp, co_mapping, source_srs='NAD83')
+
+ # Unique may take tuple or string parameters.
+ for arg in ('name', ('name', 'mpoly')):
+ lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique=arg)
+ except:
+ self.fail('No exception should be raised for proper use of keywords.')
+
+ # Testing invalid params for the `unique` keyword.
+ for e, arg in ((TypeError, 5.0), (ValueError, 'foobar'), (ValueError, ('name', 'mpolygon'))):
+ self.assertRaises(e, LayerMapping, County, co_shp, co_mapping, transform=False, unique=arg)
+
+ # No source reference system defined in the shapefile, should raise an error.
+ if not mysql:
+ self.assertRaises(LayerMapError, LayerMapping, County, co_shp, co_mapping)
+
+ # Passing in invalid ForeignKey mapping parameters -- must be a dictionary
+ # mapping for the model the ForeignKey points to.
+ bad_fk_map1 = copy(co_mapping); bad_fk_map1['state'] = 'name'
+ bad_fk_map2 = copy(co_mapping); bad_fk_map2['state'] = {'nombre' : 'State'}
+ self.assertRaises(TypeError, LayerMapping, County, co_shp, bad_fk_map1, transform=False)
+ self.assertRaises(LayerMapError, LayerMapping, County, co_shp, bad_fk_map2, transform=False)
+
+ # There exist no State models for the ForeignKey mapping to work -- should raise
+ # a MissingForeignKey exception (this error would be ignored if the `strict`
+ # keyword is not set).
+ lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
+ self.assertRaises(MissingForeignKey, lm.save, silent=True, strict=True)
+
+ # Now creating the state models so the ForeignKey mapping may work.
+ co, hi, tx = State(name='Colorado'), State(name='Hawaii'), State(name='Texas')
+ co.save(), hi.save(), tx.save()
+
+ # If a mapping is specified as a collection, all OGR fields that
+ # are not collections will be converted into them. For example,
+ # a Point column would be converted to MultiPoint. Other things being done
+ # w/the keyword args:
+ # `transform=False`: Specifies that no transform is to be done; this
+ # has the effect of ignoring the spatial reference check (because the
+ # county shapefile does not have implicit spatial reference info).
+ #
+ # `unique='name'`: Creates models on the condition that they have
+ # unique county names; geometries from each feature however will be
+ # appended to the geometry collection of the unique model. Thus,
+ # all of the various islands in Honolulu county will be in in one
+ # database record with a MULTIPOLYGON type.
+ lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
+ lm.save(silent=True, strict=True)
+
+ # A reference that doesn't use the unique keyword; a new database record will
+ # created for each polygon.
+ lm = LayerMapping(CountyFeat, co_shp, cofeat_mapping, transform=False)
+ lm.save(silent=True, strict=True)
+
+ # The county helper is called to ensure integrity of County models.
+ self.county_helper()
+
+ def test05_test_fid_range_step(self):
+ "Tests the `fid_range` keyword and the `step` keyword of .save()."
+ # Function for clearing out all the counties before testing.
+ def clear_counties(): County.objects.all().delete()
+
+ # Initializing the LayerMapping object to use in these tests.
+ lm = LayerMapping(County, co_shp, co_mapping, transform=False, unique='name')
+
+ # Bad feature id ranges should raise a type error.
+ clear_counties()
+ bad_ranges = (5.0, 'foo', co_shp)
+ for bad in bad_ranges:
+ self.assertRaises(TypeError, lm.save, fid_range=bad)
+
+ # Step keyword should not be allowed w/`fid_range`.
+ fr = (3, 5) # layer[3:5]
+ self.assertRaises(LayerMapError, lm.save, fid_range=fr, step=10)
+ lm.save(fid_range=fr)
+
+ # Features IDs 3 & 4 are for Galveston County, Texas -- only
+ # one model is returned because the `unique` keyword was set.
+ qs = County.objects.all()
+ self.assertEqual(1, qs.count())
+ self.assertEqual('Galveston', qs[0].name)
+
+ # Features IDs 5 and beyond for Honolulu County, Hawaii, and
+ # FID 0 is for Pueblo County, Colorado.
+ clear_counties()
+ lm.save(fid_range=slice(5, None), silent=True, strict=True) # layer[5:]
+ lm.save(fid_range=slice(None, 1), silent=True, strict=True) # layer[:1]
+
+ # Only Pueblo & Honolulu counties should be present because of
+ # the `unique` keyword. Have to set `order_by` on this QuerySet
+ # or else MySQL will return a different ordering than the other dbs.
+ qs = County.objects.order_by('name')
+ self.assertEqual(2, qs.count())
+ hi, co = tuple(qs)
+ hi_idx, co_idx = tuple(map(NAMES.index, ('Honolulu', 'Pueblo')))
+ self.assertEqual('Pueblo', co.name); self.assertEqual(NUMS[co_idx], len(co.mpoly))
+ self.assertEqual('Honolulu', hi.name); self.assertEqual(NUMS[hi_idx], len(hi.mpoly))
+
+ # Testing the `step` keyword -- should get the same counties
+ # regardless of we use a step that divides equally, that is odd,
+ # or that is larger than the dataset.
+ for st in (4,7,1000):
+ clear_counties()
+ lm.save(step=st, strict=True)
+ self.county_helper(county_feat=False)
+
+ def test06_model_inheritance(self):
+ "Tests LayerMapping on inherited models. See #12093."
+ icity_mapping = {'name' : 'Name',
+ 'population' : 'Population',
+ 'density' : 'Density',
+ 'point' : 'POINT',
+ 'dt' : 'Created',
+ }
+
+ # Parent model has geometry field.
+ lm1 = LayerMapping(ICity1, city_shp, icity_mapping)
+ lm1.save()
+
+ # Grandparent has geometry field.
+ lm2 = LayerMapping(ICity2, city_shp, icity_mapping)
+ lm2.save()
+
+ self.assertEqual(6, ICity1.objects.count())
+ self.assertEqual(3, ICity2.objects.count())
+
diff --git a/parts/django/django/contrib/gis/tests/relatedapp/__init__.py b/parts/django/django/contrib/gis/tests/relatedapp/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/relatedapp/__init__.py
diff --git a/parts/django/django/contrib/gis/tests/relatedapp/models.py b/parts/django/django/contrib/gis/tests/relatedapp/models.py
new file mode 100644
index 0000000..2e9a62b
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/relatedapp/models.py
@@ -0,0 +1,49 @@
+from django.contrib.gis.db import models
+from django.contrib.localflavor.us.models import USStateField
+
+class Location(models.Model):
+ point = models.PointField()
+ objects = models.GeoManager()
+ def __unicode__(self): return self.point.wkt
+
+class City(models.Model):
+ name = models.CharField(max_length=50)
+ state = USStateField()
+ location = models.ForeignKey(Location)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+class AugmentedLocation(Location):
+ extra_text = models.TextField(blank=True)
+ objects = models.GeoManager()
+
+class DirectoryEntry(models.Model):
+ listing_text = models.CharField(max_length=50)
+ location = models.ForeignKey(AugmentedLocation)
+ objects = models.GeoManager()
+
+class Parcel(models.Model):
+ name = models.CharField(max_length=30)
+ city = models.ForeignKey(City)
+ center1 = models.PointField()
+ # Throwing a curveball w/`db_column` here.
+ center2 = models.PointField(srid=2276, db_column='mycenter')
+ border1 = models.PolygonField()
+ border2 = models.PolygonField(srid=2276)
+ objects = models.GeoManager()
+ def __unicode__(self): return self.name
+
+# These use the GeoManager but do not have any geographic fields.
+class Author(models.Model):
+ name = models.CharField(max_length=100)
+ objects = models.GeoManager()
+
+class Article(models.Model):
+ title = models.CharField(max_length=100)
+ author = models.ForeignKey(Author, unique=True)
+ objects = models.GeoManager()
+
+class Book(models.Model):
+ title = models.CharField(max_length=100)
+ author = models.ForeignKey(Author, related_name='books', null=True)
+ objects = models.GeoManager()
diff --git a/parts/django/django/contrib/gis/tests/relatedapp/tests.py b/parts/django/django/contrib/gis/tests/relatedapp/tests.py
new file mode 100644
index 0000000..c8aeb28
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/relatedapp/tests.py
@@ -0,0 +1,284 @@
+from django.test import TestCase
+
+from django.contrib.gis.geos import GEOSGeometry, Point, MultiPoint
+from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
+from django.contrib.gis.geometry.backend import Geometry
+from django.contrib.gis.tests.utils import mysql, oracle, no_mysql, no_oracle, no_spatialite
+
+from models import City, Location, DirectoryEntry, Parcel, Book, Author, Article
+
+class RelatedGeoModelTest(TestCase):
+
+ def test02_select_related(self):
+ "Testing `select_related` on geographic models (see #7126)."
+ qs1 = City.objects.all()
+ qs2 = City.objects.select_related()
+ qs3 = City.objects.select_related('location')
+
+ # Reference data for what's in the fixtures.
+ cities = (
+ ('Aurora', 'TX', -97.516111, 33.058333),
+ ('Roswell', 'NM', -104.528056, 33.387222),
+ ('Kecksburg', 'PA', -79.460734, 40.18476),
+ )
+
+ for qs in (qs1, qs2, qs3):
+ for ref, c in zip(cities, qs):
+ nm, st, lon, lat = ref
+ self.assertEqual(nm, c.name)
+ self.assertEqual(st, c.state)
+ self.assertEqual(Point(lon, lat), c.location.point)
+
+ @no_mysql
+ def test03_transform_related(self):
+ "Testing the `transform` GeoQuerySet method on related geographic models."
+ # All the transformations are to state plane coordinate systems using
+ # US Survey Feet (thus a tolerance of 0 implies error w/in 1 survey foot).
+ tol = 0
+
+ def check_pnt(ref, pnt):
+ self.assertAlmostEqual(ref.x, pnt.x, tol)
+ self.assertAlmostEqual(ref.y, pnt.y, tol)
+ self.assertEqual(ref.srid, pnt.srid)
+
+ # Each city transformed to the SRID of their state plane coordinate system.
+ transformed = (('Kecksburg', 2272, 'POINT(1490553.98959621 314792.131023984)'),
+ ('Roswell', 2257, 'POINT(481902.189077221 868477.766629735)'),
+ ('Aurora', 2276, 'POINT(2269923.2484839 7069381.28722222)'),
+ )
+
+ for name, srid, wkt in transformed:
+ # Doing this implicitly sets `select_related` select the location.
+ # TODO: Fix why this breaks on Oracle.
+ qs = list(City.objects.filter(name=name).transform(srid, field_name='location__point'))
+ check_pnt(GEOSGeometry(wkt, srid), qs[0].location.point)
+
+ @no_mysql
+ @no_spatialite
+ def test04a_related_extent_aggregate(self):
+ "Testing the `extent` GeoQuerySet aggregates on related geographic models."
+ # This combines the Extent and Union aggregates into one query
+ aggs = City.objects.aggregate(Extent('location__point'))
+
+ # One for all locations, one that excludes New Mexico (Roswell).
+ all_extent = (-104.528056, 29.763374, -79.460734, 40.18476)
+ txpa_extent = (-97.516111, 29.763374, -79.460734, 40.18476)
+ e1 = City.objects.extent(field_name='location__point')
+ e2 = City.objects.exclude(state='NM').extent(field_name='location__point')
+ e3 = aggs['location__point__extent']
+
+ # The tolerance value is to four decimal places because of differences
+ # between the Oracle and PostGIS spatial backends on the extent calculation.
+ tol = 4
+ for ref, e in [(all_extent, e1), (txpa_extent, e2), (all_extent, e3)]:
+ for ref_val, e_val in zip(ref, e): self.assertAlmostEqual(ref_val, e_val, tol)
+
+ @no_mysql
+ def test04b_related_union_aggregate(self):
+ "Testing the `unionagg` GeoQuerySet aggregates on related geographic models."
+ # This combines the Extent and Union aggregates into one query
+ aggs = City.objects.aggregate(Union('location__point'))
+
+ # These are the points that are components of the aggregate geographic
+ # union that is returned. Each point # corresponds to City PK.
+ p1 = Point(-104.528056, 33.387222)
+ p2 = Point(-97.516111, 33.058333)
+ p3 = Point(-79.460734, 40.18476)
+ p4 = Point(-96.801611, 32.782057)
+ p5 = Point(-95.363151, 29.763374)
+
+ # Creating the reference union geometry depending on the spatial backend,
+ # as Oracle will have a different internal ordering of the component
+ # geometries than PostGIS. The second union aggregate is for a union
+ # query that includes limiting information in the WHERE clause (in other
+ # words a `.filter()` precedes the call to `.unionagg()`).
+ if oracle:
+ ref_u1 = MultiPoint(p4, p5, p3, p1, p2, srid=4326)
+ ref_u2 = MultiPoint(p3, p2, srid=4326)
+ else:
+ # Looks like PostGIS points by longitude value.
+ ref_u1 = MultiPoint(p1, p2, p4, p5, p3, srid=4326)
+ ref_u2 = MultiPoint(p2, p3, srid=4326)
+
+ u1 = City.objects.unionagg(field_name='location__point')
+ u2 = City.objects.exclude(name__in=('Roswell', 'Houston', 'Dallas', 'Fort Worth')).unionagg(field_name='location__point')
+ u3 = aggs['location__point__union']
+
+ self.assertEqual(ref_u1, u1)
+ self.assertEqual(ref_u2, u2)
+ self.assertEqual(ref_u1, u3)
+
+ def test05_select_related_fk_to_subclass(self):
+ "Testing that calling select_related on a query over a model with an FK to a model subclass works"
+ # Regression test for #9752.
+ l = list(DirectoryEntry.objects.all().select_related())
+
+ def test06_f_expressions(self):
+ "Testing F() expressions on GeometryFields."
+ # Constructing a dummy parcel border and getting the City instance for
+ # assigning the FK.
+ b1 = GEOSGeometry('POLYGON((-97.501205 33.052520,-97.501205 33.052576,-97.501150 33.052576,-97.501150 33.052520,-97.501205 33.052520))', srid=4326)
+ pcity = City.objects.get(name='Aurora')
+
+ # First parcel has incorrect center point that is equal to the City;
+ # it also has a second border that is different from the first as a
+ # 100ft buffer around the City.
+ c1 = pcity.location.point
+ c2 = c1.transform(2276, clone=True)
+ b2 = c2.buffer(100)
+ p1 = Parcel.objects.create(name='P1', city=pcity, center1=c1, center2=c2, border1=b1, border2=b2)
+
+ # Now creating a second Parcel where the borders are the same, just
+ # in different coordinate systems. The center points are also the
+ # the same (but in different coordinate systems), and this time they
+ # actually correspond to the centroid of the border.
+ c1 = b1.centroid
+ c2 = c1.transform(2276, clone=True)
+ p2 = Parcel.objects.create(name='P2', city=pcity, center1=c1, center2=c2, border1=b1, border2=b1)
+
+ # Should return the second Parcel, which has the center within the
+ # border.
+ qs = Parcel.objects.filter(center1__within=F('border1'))
+ self.assertEqual(1, len(qs))
+ self.assertEqual('P2', qs[0].name)
+
+ if not mysql:
+ # This time center2 is in a different coordinate system and needs
+ # to be wrapped in transformation SQL.
+ qs = Parcel.objects.filter(center2__within=F('border1'))
+ self.assertEqual(1, len(qs))
+ self.assertEqual('P2', qs[0].name)
+
+ # Should return the first Parcel, which has the center point equal
+ # to the point in the City ForeignKey.
+ qs = Parcel.objects.filter(center1=F('city__location__point'))
+ self.assertEqual(1, len(qs))
+ self.assertEqual('P1', qs[0].name)
+
+ if not mysql:
+ # This time the city column should be wrapped in transformation SQL.
+ qs = Parcel.objects.filter(border2__contains=F('city__location__point'))
+ self.assertEqual(1, len(qs))
+ self.assertEqual('P1', qs[0].name)
+
+ def test07_values(self):
+ "Testing values() and values_list() and GeoQuerySets."
+ # GeoQuerySet and GeoValuesQuerySet, and GeoValuesListQuerySet respectively.
+ gqs = Location.objects.all()
+ gvqs = Location.objects.values()
+ gvlqs = Location.objects.values_list()
+
+ # Incrementing through each of the models, dictionaries, and tuples
+ # returned by the different types of GeoQuerySets.
+ for m, d, t in zip(gqs, gvqs, gvlqs):
+ # The values should be Geometry objects and not raw strings returned
+ # by the spatial database.
+ self.failUnless(isinstance(d['point'], Geometry))
+ self.failUnless(isinstance(t[1], Geometry))
+ self.assertEqual(m.point, d['point'])
+ self.assertEqual(m.point, t[1])
+
+ def test08_defer_only(self):
+ "Testing defer() and only() on Geographic models."
+ qs = Location.objects.all()
+ def_qs = Location.objects.defer('point')
+ for loc, def_loc in zip(qs, def_qs):
+ self.assertEqual(loc.point, def_loc.point)
+
+ def test09_pk_relations(self):
+ "Ensuring correct primary key column is selected across relations. See #10757."
+ # The expected ID values -- notice the last two location IDs
+ # are out of order. Dallas and Houston have location IDs that differ
+ # from their PKs -- this is done to ensure that the related location
+ # ID column is selected instead of ID column for the city.
+ city_ids = (1, 2, 3, 4, 5)
+ loc_ids = (1, 2, 3, 5, 4)
+ ids_qs = City.objects.order_by('id').values('id', 'location__id')
+ for val_dict, c_id, l_id in zip(ids_qs, city_ids, loc_ids):
+ self.assertEqual(val_dict['id'], c_id)
+ self.assertEqual(val_dict['location__id'], l_id)
+
+ def test10_combine(self):
+ "Testing the combination of two GeoQuerySets. See #10807."
+ buf1 = City.objects.get(name='Aurora').location.point.buffer(0.1)
+ buf2 = City.objects.get(name='Kecksburg').location.point.buffer(0.1)
+ qs1 = City.objects.filter(location__point__within=buf1)
+ qs2 = City.objects.filter(location__point__within=buf2)
+ combined = qs1 | qs2
+ names = [c.name for c in combined]
+ self.assertEqual(2, len(names))
+ self.failUnless('Aurora' in names)
+ self.failUnless('Kecksburg' in names)
+
+ def test11_geoquery_pickle(self):
+ "Ensuring GeoQuery objects are unpickled correctly. See #10839."
+ import pickle
+ from django.contrib.gis.db.models.sql import GeoQuery
+ qs = City.objects.all()
+ q_str = pickle.dumps(qs.query)
+ q = pickle.loads(q_str)
+ self.assertEqual(GeoQuery, q.__class__)
+
+ # TODO: fix on Oracle -- get the following error because the SQL is ordered
+ # by a geometry object, which Oracle apparently doesn't like:
+ # ORA-22901: cannot compare nested table or VARRAY or LOB attributes of an object type
+ @no_oracle
+ def test12a_count(self):
+ "Testing `Count` aggregate use with the `GeoManager` on geo-fields."
+ # The City, 'Fort Worth' uses the same location as Dallas.
+ dallas = City.objects.get(name='Dallas')
+
+ # Count annotation should be 2 for the Dallas location now.
+ loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id)
+ self.assertEqual(2, loc.num_cities)
+
+ def test12b_count(self):
+ "Testing `Count` aggregate use with the `GeoManager` on non geo-fields. See #11087."
+ # Should only be one author (Trevor Paglen) returned by this query, and
+ # the annotation should have 3 for the number of books, see #11087.
+ # Also testing with a `GeoValuesQuerySet`, see #11489.
+ qs = Author.objects.annotate(num_books=Count('books')).filter(num_books__gt=1)
+ vqs = Author.objects.values('name').annotate(num_books=Count('books')).filter(num_books__gt=1)
+ self.assertEqual(1, len(qs))
+ self.assertEqual(3, qs[0].num_books)
+ self.assertEqual(1, len(vqs))
+ self.assertEqual(3, vqs[0]['num_books'])
+
+ # TODO: The phantom model does appear on Oracle.
+ @no_oracle
+ def test13_select_related_null_fk(self):
+ "Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381."
+ no_author = Book.objects.create(title='Without Author')
+ b = Book.objects.select_related('author').get(title='Without Author')
+ # Should be `None`, and not a 'dummy' model.
+ self.assertEqual(None, b.author)
+
+ @no_mysql
+ @no_oracle
+ @no_spatialite
+ def test14_collect(self):
+ "Testing the `collect` GeoQuerySet method and `Collect` aggregate."
+ # Reference query:
+ # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
+ # "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
+ # WHERE "relatedapp_city"."state" = 'TX';
+ ref_geom = GEOSGeometry('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
+
+ c1 = City.objects.filter(state='TX').collect(field_name='location__point')
+ c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
+
+ for coll in (c1, c2):
+ # Even though Dallas and Ft. Worth share same point, Collect doesn't
+ # consolidate -- that's why 4 points in MultiPoint.
+ self.assertEqual(4, len(coll))
+ self.assertEqual(ref_geom, coll)
+
+ def test15_invalid_select_related(self):
+ "Testing doing select_related on the related name manager of a unique FK. See #13934."
+ qs = Article.objects.select_related('author__article')
+ # This triggers TypeError when `get_default_columns` has no `local_only`
+ # keyword. The TypeError is swallowed if QuerySet is actually
+ # evaluated as list generation swallows TypeError in CPython.
+ sql = str(qs.query)
+
+ # TODO: Related tests for KML, GML, and distance lookups.
diff --git a/parts/django/django/contrib/gis/tests/test_geoforms.py b/parts/django/django/contrib/gis/tests/test_geoforms.py
new file mode 100644
index 0000000..aa6b25e
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/test_geoforms.py
@@ -0,0 +1,65 @@
+import unittest
+
+from django.forms import ValidationError
+from django.contrib.gis import forms
+from django.contrib.gis.geos import GEOSGeometry
+
+class GeometryFieldTest(unittest.TestCase):
+
+ def test00_init(self):
+ "Testing GeometryField initialization with defaults."
+ fld = forms.GeometryField()
+ for bad_default in ('blah', 3, 'FoO', None, 0):
+ self.assertRaises(ValidationError, fld.clean, bad_default)
+
+ def test01_srid(self):
+ "Testing GeometryField with a SRID set."
+ # Input that doesn't specify the SRID is assumed to be in the SRID
+ # of the input field.
+ fld = forms.GeometryField(srid=4326)
+ geom = fld.clean('POINT(5 23)')
+ self.assertEqual(4326, geom.srid)
+ # Making the field in a different SRID from that of the geometry, and
+ # asserting it transforms.
+ fld = forms.GeometryField(srid=32140)
+ tol = 0.0000001
+ xform_geom = GEOSGeometry('POINT (951640.547328465 4219369.26171664)', srid=32140)
+ # The cleaned geometry should be transformed to 32140.
+ cleaned_geom = fld.clean('SRID=4326;POINT (-95.363151 29.763374)')
+ self.failUnless(xform_geom.equals_exact(cleaned_geom, tol))
+
+ def test02_null(self):
+ "Testing GeometryField's handling of null (None) geometries."
+ # Form fields, by default, are required (`required=True`)
+ fld = forms.GeometryField()
+ self.assertRaises(forms.ValidationError, fld.clean, None)
+
+ # Still not allowed if `null=False`.
+ fld = forms.GeometryField(required=False, null=False)
+ self.assertRaises(forms.ValidationError, fld.clean, None)
+
+ # This will clean None as a geometry (See #10660).
+ fld = forms.GeometryField(required=False)
+ self.assertEqual(None, fld.clean(None))
+
+ def test03_geom_type(self):
+ "Testing GeometryField's handling of different geometry types."
+ # By default, all geometry types are allowed.
+ fld = forms.GeometryField()
+ for wkt in ('POINT(5 23)', 'MULTIPOLYGON(((0 0, 0 1, 1 1, 1 0, 0 0)))', 'LINESTRING(0 0, 1 1)'):
+ self.assertEqual(GEOSGeometry(wkt), fld.clean(wkt))
+
+ pnt_fld = forms.GeometryField(geom_type='POINT')
+ self.assertEqual(GEOSGeometry('POINT(5 23)'), pnt_fld.clean('POINT(5 23)'))
+ self.assertRaises(forms.ValidationError, pnt_fld.clean, 'LINESTRING(0 0, 1 1)')
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(GeometryFieldTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
+
+if __name__=="__main__":
+ run()
diff --git a/parts/django/django/contrib/gis/tests/test_geoip.py b/parts/django/django/contrib/gis/tests/test_geoip.py
new file mode 100644
index 0000000..a9ab6a6
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/test_geoip.py
@@ -0,0 +1,103 @@
+import os, unittest
+from django.db import settings
+from django.contrib.gis.geos import GEOSGeometry
+from django.contrib.gis.utils import GeoIP, GeoIPException
+
+# Note: Requires use of both the GeoIP country and city datasets.
+# The GEOIP_DATA path should be the only setting set (the directory
+# should contain links or the actual database files 'GeoIP.dat' and
+# 'GeoLiteCity.dat'.
+class GeoIPTest(unittest.TestCase):
+
+ def test01_init(self):
+ "Testing GeoIP initialization."
+ g1 = GeoIP() # Everything inferred from GeoIP path
+ path = settings.GEOIP_PATH
+ g2 = GeoIP(path, 0) # Passing in data path explicitly.
+ g3 = GeoIP.open(path, 0) # MaxMind Python API syntax.
+
+ for g in (g1, g2, g3):
+ self.assertEqual(True, bool(g._country))
+ self.assertEqual(True, bool(g._city))
+
+ # Only passing in the location of one database.
+ city = os.path.join(path, 'GeoLiteCity.dat')
+ cntry = os.path.join(path, 'GeoIP.dat')
+ g4 = GeoIP(city, country='')
+ self.assertEqual(None, g4._country)
+ g5 = GeoIP(cntry, city='')
+ self.assertEqual(None, g5._city)
+
+ # Improper parameters.
+ bad_params = (23, 'foo', 15.23)
+ for bad in bad_params:
+ self.assertRaises(GeoIPException, GeoIP, cache=bad)
+ if isinstance(bad, basestring):
+ e = GeoIPException
+ else:
+ e = TypeError
+ self.assertRaises(e, GeoIP, bad, 0)
+
+ def test02_bad_query(self):
+ "Testing GeoIP query parameter checking."
+ cntry_g = GeoIP(city='<foo>')
+ # No city database available, these calls should fail.
+ self.assertRaises(GeoIPException, cntry_g.city, 'google.com')
+ self.assertRaises(GeoIPException, cntry_g.coords, 'yahoo.com')
+
+ # Non-string query should raise TypeError
+ self.assertRaises(TypeError, cntry_g.country_code, 17)
+ self.assertRaises(TypeError, cntry_g.country_name, GeoIP)
+
+ def test03_country(self):
+ "Testing GeoIP country querying methods."
+ g = GeoIP(city='<foo>')
+
+ fqdn = 'www.google.com'
+ addr = '12.215.42.19'
+
+ for query in (fqdn, addr):
+ for func in (g.country_code, g.country_code_by_addr, g.country_code_by_name):
+ self.assertEqual('US', func(query))
+ for func in (g.country_name, g.country_name_by_addr, g.country_name_by_name):
+ self.assertEqual('United States', func(query))
+ self.assertEqual({'country_code' : 'US', 'country_name' : 'United States'},
+ g.country(query))
+
+ def test04_city(self):
+ "Testing GeoIP city querying methods."
+ g = GeoIP(country='<foo>')
+
+ addr = '130.80.29.3'
+ fqdn = 'chron.com'
+ for query in (fqdn, addr):
+ # Country queries should still work.
+ for func in (g.country_code, g.country_code_by_addr, g.country_code_by_name):
+ self.assertEqual('US', func(query))
+ for func in (g.country_name, g.country_name_by_addr, g.country_name_by_name):
+ self.assertEqual('United States', func(query))
+ self.assertEqual({'country_code' : 'US', 'country_name' : 'United States'},
+ g.country(query))
+
+ # City information dictionary.
+ d = g.city(query)
+ self.assertEqual('USA', d['country_code3'])
+ self.assertEqual('Houston', d['city'])
+ self.assertEqual('TX', d['region'])
+ self.assertEqual(713, d['area_code'])
+ geom = g.geos(query)
+ self.failIf(not isinstance(geom, GEOSGeometry))
+ lon, lat = (-95.3670, 29.7523)
+ lat_lon = g.lat_lon(query)
+ lat_lon = (lat_lon[1], lat_lon[0])
+ for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon):
+ self.assertAlmostEqual(lon, tup[0], 4)
+ self.assertAlmostEqual(lat, tup[1], 4)
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(GeoIPTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/tests/test_measure.py b/parts/django/django/contrib/gis/tests/test_measure.py
new file mode 100644
index 0000000..28d5048
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/test_measure.py
@@ -0,0 +1,336 @@
+"""
+Distance and Area objects to allow for sensible and convienient calculation
+and conversions. Here are some tests.
+"""
+
+import unittest
+from django.contrib.gis.measure import Distance, Area, D, A
+
+class DistanceTest(unittest.TestCase):
+ "Testing the Distance object"
+
+ def testInit(self):
+ "Testing initialisation from valid units"
+ d = Distance(m=100)
+ self.assertEqual(d.m, 100)
+
+ d1, d2, d3 = D(m=100), D(meter=100), D(metre=100)
+ for d in (d1, d2, d3):
+ self.assertEqual(d.m, 100)
+
+ d = D(nm=100)
+ self.assertEqual(d.m, 185200)
+
+ y1, y2, y3 = D(yd=100), D(yard=100), D(Yard=100)
+ for d in (y1, y2, y3):
+ self.assertEqual(d.yd, 100)
+
+ mm1, mm2 = D(millimeter=1000), D(MiLLiMeTeR=1000)
+ for d in (mm1, mm2):
+ self.assertEqual(d.m, 1.0)
+ self.assertEqual(d.mm, 1000.0)
+
+
+ def testInitInvalid(self):
+ "Testing initialisation from invalid units"
+ self.assertRaises(AttributeError, D, banana=100)
+
+ def testAccess(self):
+ "Testing access in different units"
+ d = D(m=100)
+ self.assertEqual(d.km, 0.1)
+ self.assertAlmostEqual(d.ft, 328.084, 3)
+
+ def testAccessInvalid(self):
+ "Testing access in invalid units"
+ d = D(m=100)
+ self.failIf(hasattr(d, 'banana'))
+
+ def testAddition(self):
+ "Test addition & subtraction"
+ d1 = D(m=100)
+ d2 = D(m=200)
+
+ d3 = d1 + d2
+ self.assertEqual(d3.m, 300)
+ d3 += d1
+ self.assertEqual(d3.m, 400)
+
+ d4 = d1 - d2
+ self.assertEqual(d4.m, -100)
+ d4 -= d1
+ self.assertEqual(d4.m, -200)
+
+ try:
+ d5 = d1 + 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance + number should raise TypeError')
+
+ try:
+ d5 = d1 - 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance - number should raise TypeError')
+
+ try:
+ d1 += 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance += number should raise TypeError')
+
+ try:
+ d1 -= 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance -= number should raise TypeError')
+
+ def testMultiplication(self):
+ "Test multiplication & division"
+ d1 = D(m=100)
+
+ d3 = d1 * 2
+ self.assertEqual(d3.m, 200)
+ d3 = 2 * d1
+ self.assertEqual(d3.m, 200)
+ d3 *= 5
+ self.assertEqual(d3.m, 1000)
+
+ d4 = d1 / 2
+ self.assertEqual(d4.m, 50)
+ d4 /= 5
+ self.assertEqual(d4.m, 10)
+
+ a5 = d1 * D(m=10)
+ self.assert_(isinstance(a5, Area))
+ self.assertEqual(a5.sq_m, 100*10)
+
+ try:
+ d1 *= D(m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance *= Distance should raise TypeError')
+
+ try:
+ d5 = d1 / D(m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance / Distance should raise TypeError')
+
+ try:
+ d1 /= D(m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Distance /= Distance should raise TypeError')
+
+ def testUnitConversions(self):
+ "Testing default units during maths"
+ d1 = D(m=100)
+ d2 = D(km=1)
+
+ d3 = d1 + d2
+ self.assertEqual(d3._default_unit, 'm')
+ d4 = d2 + d1
+ self.assertEqual(d4._default_unit, 'km')
+ d5 = d1 * 2
+ self.assertEqual(d5._default_unit, 'm')
+ d6 = d1 / 2
+ self.assertEqual(d6._default_unit, 'm')
+
+ def testComparisons(self):
+ "Testing comparisons"
+ d1 = D(m=100)
+ d2 = D(km=1)
+ d3 = D(km=0)
+
+ self.assert_(d2 > d1)
+ self.assert_(d1 == d1)
+ self.assert_(d1 < d2)
+ self.failIf(d3)
+
+ def testUnitsStr(self):
+ "Testing conversion to strings"
+ d1 = D(m=100)
+ d2 = D(km=3.5)
+
+ self.assertEqual(str(d1), '100.0 m')
+ self.assertEqual(str(d2), '3.5 km')
+ self.assertEqual(repr(d1), 'Distance(m=100.0)')
+ self.assertEqual(repr(d2), 'Distance(km=3.5)')
+
+ def testUnitAttName(self):
+ "Testing the `unit_attname` class method"
+ unit_tuple = [('Yard', 'yd'), ('Nautical Mile', 'nm'), ('German legal metre', 'german_m'),
+ ('Indian yard', 'indian_yd'), ('Chain (Sears)', 'chain_sears'), ('Chain', 'chain')]
+ for nm, att in unit_tuple:
+ self.assertEqual(att, D.unit_attname(nm))
+
+class AreaTest(unittest.TestCase):
+ "Testing the Area object"
+
+ def testInit(self):
+ "Testing initialisation from valid units"
+ a = Area(sq_m=100)
+ self.assertEqual(a.sq_m, 100)
+
+ a = A(sq_m=100)
+ self.assertEqual(a.sq_m, 100)
+
+ a = A(sq_mi=100)
+ self.assertEqual(a.sq_m, 258998811.0336)
+
+ def testInitInvaliA(self):
+ "Testing initialisation from invalid units"
+ self.assertRaises(AttributeError, A, banana=100)
+
+ def testAccess(self):
+ "Testing access in different units"
+ a = A(sq_m=100)
+ self.assertEqual(a.sq_km, 0.0001)
+ self.assertAlmostEqual(a.sq_ft, 1076.391, 3)
+
+ def testAccessInvaliA(self):
+ "Testing access in invalid units"
+ a = A(sq_m=100)
+ self.failIf(hasattr(a, 'banana'))
+
+ def testAddition(self):
+ "Test addition & subtraction"
+ a1 = A(sq_m=100)
+ a2 = A(sq_m=200)
+
+ a3 = a1 + a2
+ self.assertEqual(a3.sq_m, 300)
+ a3 += a1
+ self.assertEqual(a3.sq_m, 400)
+
+ a4 = a1 - a2
+ self.assertEqual(a4.sq_m, -100)
+ a4 -= a1
+ self.assertEqual(a4.sq_m, -200)
+
+ try:
+ a5 = a1 + 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area + number should raise TypeError')
+
+ try:
+ a5 = a1 - 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area - number should raise TypeError')
+
+ try:
+ a1 += 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area += number should raise TypeError')
+
+ try:
+ a1 -= 1
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area -= number should raise TypeError')
+
+ def testMultiplication(self):
+ "Test multiplication & division"
+ a1 = A(sq_m=100)
+
+ a3 = a1 * 2
+ self.assertEqual(a3.sq_m, 200)
+ a3 = 2 * a1
+ self.assertEqual(a3.sq_m, 200)
+ a3 *= 5
+ self.assertEqual(a3.sq_m, 1000)
+
+ a4 = a1 / 2
+ self.assertEqual(a4.sq_m, 50)
+ a4 /= 5
+ self.assertEqual(a4.sq_m, 10)
+
+ try:
+ a5 = a1 * A(sq_m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area * Area should raise TypeError')
+
+ try:
+ a1 *= A(sq_m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area *= Area should raise TypeError')
+
+ try:
+ a5 = a1 / A(sq_m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area / Area should raise TypeError')
+
+ try:
+ a1 /= A(sq_m=1)
+ except TypeError, e:
+ pass
+ else:
+ self.fail('Area /= Area should raise TypeError')
+
+ def testUnitConversions(self):
+ "Testing default units during maths"
+ a1 = A(sq_m=100)
+ a2 = A(sq_km=1)
+
+ a3 = a1 + a2
+ self.assertEqual(a3._default_unit, 'sq_m')
+ a4 = a2 + a1
+ self.assertEqual(a4._default_unit, 'sq_km')
+ a5 = a1 * 2
+ self.assertEqual(a5._default_unit, 'sq_m')
+ a6 = a1 / 2
+ self.assertEqual(a6._default_unit, 'sq_m')
+
+ def testComparisons(self):
+ "Testing comparisons"
+ a1 = A(sq_m=100)
+ a2 = A(sq_km=1)
+ a3 = A(sq_km=0)
+
+ self.assert_(a2 > a1)
+ self.assert_(a1 == a1)
+ self.assert_(a1 < a2)
+ self.failIf(a3)
+
+ def testUnitsStr(self):
+ "Testing conversion to strings"
+ a1 = A(sq_m=100)
+ a2 = A(sq_km=3.5)
+
+ self.assertEqual(str(a1), '100.0 sq_m')
+ self.assertEqual(str(a2), '3.5 sq_km')
+ self.assertEqual(repr(a1), 'Area(sq_m=100.0)')
+ self.assertEqual(repr(a2), 'Area(sq_km=3.5)')
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(DistanceTest))
+ s.addTest(unittest.makeSuite(AreaTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
+
+if __name__=="__main__":
+ run()
diff --git a/parts/django/django/contrib/gis/tests/test_spatialrefsys.py b/parts/django/django/contrib/gis/tests/test_spatialrefsys.py
new file mode 100644
index 0000000..a9fcbff
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/test_spatialrefsys.py
@@ -0,0 +1,113 @@
+import unittest
+
+from django.db import connection
+from django.contrib.gis.tests.utils import mysql, no_mysql, oracle, postgis, spatialite
+
+test_srs = ({'srid' : 4326,
+ 'auth_name' : ('EPSG', True),
+ 'auth_srid' : 4326,
+ 'srtext' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]',
+ 'srtext14' : 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]',
+ 'proj4' : '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ',
+ 'spheroid' : 'WGS 84', 'name' : 'WGS 84',
+ 'geographic' : True, 'projected' : False, 'spatialite' : True,
+ 'ellipsoid' : (6378137.0, 6356752.3, 298.257223563), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
+ 'eprec' : (1, 1, 9),
+ },
+ {'srid' : 32140,
+ 'auth_name' : ('EPSG', False),
+ 'auth_srid' : 32140,
+ 'srtext' : 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AUTHORITY["EPSG","32140"]]',
+ 'srtext14': 'PROJCS["NAD83 / Texas South Central",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",30.28333333333333],PARAMETER["standard_parallel_2",28.38333333333333],PARAMETER["latitude_of_origin",27.83333333333333],PARAMETER["central_meridian",-99],PARAMETER["false_easting",600000],PARAMETER["false_northing",4000000],AUTHORITY["EPSG","32140"],AXIS["X",EAST],AXIS["Y",NORTH]]',
+ 'proj4' : '+proj=lcc +lat_1=30.28333333333333 +lat_2=28.38333333333333 +lat_0=27.83333333333333 +lon_0=-99 +x_0=600000 +y_0=4000000 +ellps=GRS80 +datum=NAD83 +units=m +no_defs ',
+ 'spheroid' : 'GRS 1980', 'name' : 'NAD83 / Texas South Central',
+ 'geographic' : False, 'projected' : True, 'spatialite' : False,
+ 'ellipsoid' : (6378137.0, 6356752.31414, 298.257222101), # From proj's "cs2cs -le" and Wikipedia (semi-minor only)
+ 'eprec' : (1, 5, 10),
+ },
+ )
+
+if oracle:
+ from django.contrib.gis.db.backends.oracle.models import SpatialRefSys
+elif postgis:
+ from django.contrib.gis.db.backends.postgis.models import SpatialRefSys
+elif spatialite:
+ from django.contrib.gis.db.backends.spatialite.models import SpatialRefSys
+
+class SpatialRefSysTest(unittest.TestCase):
+
+ @no_mysql
+ def test01_retrieve(self):
+ "Testing retrieval of SpatialRefSys model objects."
+ for sd in test_srs:
+ srs = SpatialRefSys.objects.get(srid=sd['srid'])
+ self.assertEqual(sd['srid'], srs.srid)
+
+ # Some of the authority names are borked on Oracle, e.g., SRID=32140.
+ # also, Oracle Spatial seems to add extraneous info to fields, hence the
+ # the testing with the 'startswith' flag.
+ auth_name, oracle_flag = sd['auth_name']
+ if postgis or (oracle and oracle_flag):
+ self.assertEqual(True, srs.auth_name.startswith(auth_name))
+
+ self.assertEqual(sd['auth_srid'], srs.auth_srid)
+
+ # No proj.4 and different srtext on oracle backends :(
+ if postgis:
+ if connection.ops.spatial_version >= (1, 4, 0):
+ srtext = sd['srtext14']
+ else:
+ srtext = sd['srtext']
+ self.assertEqual(srtext, srs.wkt)
+ self.assertEqual(sd['proj4'], srs.proj4text)
+
+ @no_mysql
+ def test02_osr(self):
+ "Testing getting OSR objects from SpatialRefSys model objects."
+ for sd in test_srs:
+ sr = SpatialRefSys.objects.get(srid=sd['srid'])
+ self.assertEqual(True, sr.spheroid.startswith(sd['spheroid']))
+ self.assertEqual(sd['geographic'], sr.geographic)
+ self.assertEqual(sd['projected'], sr.projected)
+
+ if not (spatialite and not sd['spatialite']):
+ # Can't get 'NAD83 / Texas South Central' from PROJ.4 string
+ # on SpatiaLite
+ self.assertEqual(True, sr.name.startswith(sd['name']))
+
+ # Testing the SpatialReference object directly.
+ if postgis or spatialite:
+ srs = sr.srs
+ self.assertEqual(sd['proj4'], srs.proj4)
+ # No `srtext` field in the `spatial_ref_sys` table in SpatiaLite
+ if not spatialite:
+ if connection.ops.spatial_version >= (1, 4, 0):
+ srtext = sd['srtext14']
+ else:
+ srtext = sd['srtext']
+ self.assertEqual(srtext, srs.wkt)
+
+ @no_mysql
+ def test03_ellipsoid(self):
+ "Testing the ellipsoid property."
+ for sd in test_srs:
+ # Getting the ellipsoid and precision parameters.
+ ellps1 = sd['ellipsoid']
+ prec = sd['eprec']
+
+ # Getting our spatial reference and its ellipsoid
+ srs = SpatialRefSys.objects.get(srid=sd['srid'])
+ ellps2 = srs.ellipsoid
+
+ for i in range(3):
+ param1 = ellps1[i]
+ param2 = ellps2[i]
+ self.assertAlmostEqual(ellps1[i], ellps2[i], prec[i])
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(SpatialRefSysTest))
+ return s
+
+def run(verbosity=2):
+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
diff --git a/parts/django/django/contrib/gis/tests/utils.py b/parts/django/django/contrib/gis/tests/utils.py
new file mode 100644
index 0000000..b758fd0
--- /dev/null
+++ b/parts/django/django/contrib/gis/tests/utils.py
@@ -0,0 +1,26 @@
+from django.conf import settings
+from django.db import DEFAULT_DB_ALIAS
+
+# function that will pass a test.
+def pass_test(*args): return
+
+def no_backend(test_func, backend):
+ "Use this decorator to disable test on specified backend."
+ if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1] == backend:
+ return pass_test
+ else:
+ return test_func
+
+# Decorators to disable entire test functions for specific
+# spatial backends.
+def no_oracle(func): return no_backend(func, 'oracle')
+def no_postgis(func): return no_backend(func, 'postgis')
+def no_mysql(func): return no_backend(func, 'mysql')
+def no_spatialite(func): return no_backend(func, 'spatialite')
+
+# Shortcut booleans to omit only portions of tests.
+_default_db = settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'].rsplit('.')[-1]
+oracle = _default_db == 'oracle'
+postgis = _default_db == 'postgis'
+mysql = _default_db == 'mysql'
+spatialite = _default_db == 'spatialite'
diff --git a/parts/django/django/contrib/gis/utils/__init__.py b/parts/django/django/contrib/gis/utils/__init__.py
new file mode 100644
index 0000000..1cff4d9
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/__init__.py
@@ -0,0 +1,25 @@
+"""
+ This module contains useful utilities for GeoDjango.
+"""
+# Importing the utilities that depend on GDAL, if available.
+from django.contrib.gis.gdal import HAS_GDAL
+if HAS_GDAL:
+ from django.contrib.gis.utils.ogrinfo import ogrinfo, sample
+ from django.contrib.gis.utils.ogrinspect import mapping, ogrinspect
+ from django.contrib.gis.utils.srs import add_postgis_srs, add_srs_entry
+ try:
+ # LayerMapping requires DJANGO_SETTINGS_MODULE to be set,
+ # so this needs to be in try/except.
+ from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError
+ except:
+ pass
+
+# Attempting to import the GeoIP class.
+try:
+ from django.contrib.gis.utils.geoip import GeoIP, GeoIPException
+ HAS_GEOIP = True
+except:
+ HAS_GEOIP = False
+
+from django.contrib.gis.utils.wkt import precision_wkt
+
diff --git a/parts/django/django/contrib/gis/utils/geoip.py b/parts/django/django/contrib/gis/utils/geoip.py
new file mode 100644
index 0000000..eedaef9
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/geoip.py
@@ -0,0 +1,361 @@
+"""
+ This module houses the GeoIP object, a ctypes wrapper for the MaxMind GeoIP(R)
+ C API (http://www.maxmind.com/app/c). This is an alternative to the GPL
+ licensed Python GeoIP interface provided by MaxMind.
+
+ GeoIP(R) is a registered trademark of MaxMind, LLC of Boston, Massachusetts.
+
+ For IP-based geolocation, this module requires the GeoLite Country and City
+ datasets, in binary format (CSV will not work!). The datasets may be
+ downloaded from MaxMind at http://www.maxmind.com/download/geoip/database/.
+ Grab GeoIP.dat.gz and GeoLiteCity.dat.gz, and unzip them in the directory
+ corresponding to settings.GEOIP_PATH. See the GeoIP docstring and examples
+ below for more details.
+
+ TODO: Verify compatibility with Windows.
+
+ Example:
+
+ >>> from django.contrib.gis.utils import GeoIP
+ >>> g = GeoIP()
+ >>> g.country('google.com')
+ {'country_code': 'US', 'country_name': 'United States'}
+ >>> g.city('72.14.207.99')
+ {'area_code': 650,
+ 'city': 'Mountain View',
+ 'country_code': 'US',
+ 'country_code3': 'USA',
+ 'country_name': 'United States',
+ 'dma_code': 807,
+ 'latitude': 37.419200897216797,
+ 'longitude': -122.05740356445312,
+ 'postal_code': '94043',
+ 'region': 'CA'}
+ >>> g.lat_lon('salon.com')
+ (37.789798736572266, -122.39420318603516)
+ >>> g.lon_lat('uh.edu')
+ (-95.415199279785156, 29.77549934387207)
+ >>> g.geos('24.124.1.80').wkt
+ 'POINT (-95.2087020874023438 39.0392990112304688)'
+"""
+import os, re
+from ctypes import c_char_p, c_float, c_int, Structure, CDLL, POINTER
+from ctypes.util import find_library
+from django.conf import settings
+if not settings.configured: settings.configure()
+
+# Creating the settings dictionary with any settings, if needed.
+GEOIP_SETTINGS = dict((key, getattr(settings, key))
+ for key in ('GEOIP_PATH', 'GEOIP_LIBRARY_PATH', 'GEOIP_COUNTRY', 'GEOIP_CITY')
+ if hasattr(settings, key))
+lib_path = GEOIP_SETTINGS.get('GEOIP_LIBRARY_PATH', None)
+
+# GeoIP Exception class.
+class GeoIPException(Exception): pass
+
+# The shared library for the GeoIP C API. May be downloaded
+# from http://www.maxmind.com/download/geoip/api/c/
+if lib_path:
+ lib_name = None
+else:
+ # TODO: Is this really the library name for Windows?
+ lib_name = 'GeoIP'
+
+# Getting the path to the GeoIP library.
+if lib_name: lib_path = find_library(lib_name)
+if lib_path is None: raise GeoIPException('Could not find the GeoIP library (tried "%s"). '
+ 'Try setting GEOIP_LIBRARY_PATH in your settings.' % lib_name)
+lgeoip = CDLL(lib_path)
+
+# Regular expressions for recognizing IP addresses and the GeoIP
+# free database editions.
+ipregex = re.compile(r'^(?P<w>\d\d?\d?)\.(?P<x>\d\d?\d?)\.(?P<y>\d\d?\d?)\.(?P<z>\d\d?\d?)$')
+free_regex = re.compile(r'^GEO-\d{3}FREE')
+lite_regex = re.compile(r'^GEO-\d{3}LITE')
+
+#### GeoIP C Structure definitions ####
+class GeoIPRecord(Structure):
+ _fields_ = [('country_code', c_char_p),
+ ('country_code3', c_char_p),
+ ('country_name', c_char_p),
+ ('region', c_char_p),
+ ('city', c_char_p),
+ ('postal_code', c_char_p),
+ ('latitude', c_float),
+ ('longitude', c_float),
+ # TODO: In 1.4.6 this changed from `int dma_code;` to
+ # `union {int metro_code; int dma_code;};`. Change
+ # to a `ctypes.Union` in to accomodate in future when
+ # pre-1.4.6 versions are no longer distributed.
+ ('dma_code', c_int),
+ ('area_code', c_int),
+ # TODO: The following structure fields were added in 1.4.3 --
+ # uncomment these fields when sure previous versions are no
+ # longer distributed by package maintainers.
+ #('charset', c_int),
+ #('continent_code', c_char_p),
+ ]
+class GeoIPTag(Structure): pass
+
+#### ctypes function prototypes ####
+RECTYPE = POINTER(GeoIPRecord)
+DBTYPE = POINTER(GeoIPTag)
+
+# For retrieving records by name or address.
+def record_output(func):
+ func.restype = RECTYPE
+ return func
+rec_by_addr = record_output(lgeoip.GeoIP_record_by_addr)
+rec_by_name = record_output(lgeoip.GeoIP_record_by_name)
+
+# For opening & closing GeoIP database files.
+geoip_open = lgeoip.GeoIP_open
+geoip_open.restype = DBTYPE
+geoip_close = lgeoip.GeoIP_delete
+geoip_close.argtypes = [DBTYPE]
+geoip_close.restype = None
+
+# String output routines.
+def string_output(func):
+ func.restype = c_char_p
+ return func
+geoip_dbinfo = string_output(lgeoip.GeoIP_database_info)
+cntry_code_by_addr = string_output(lgeoip.GeoIP_country_code_by_addr)
+cntry_code_by_name = string_output(lgeoip.GeoIP_country_code_by_name)
+cntry_name_by_addr = string_output(lgeoip.GeoIP_country_name_by_addr)
+cntry_name_by_name = string_output(lgeoip.GeoIP_country_name_by_name)
+
+#### GeoIP class ####
+class GeoIP(object):
+ # The flags for GeoIP memory caching.
+ # GEOIP_STANDARD - read database from filesystem, uses least memory.
+ #
+ # GEOIP_MEMORY_CACHE - load database into memory, faster performance
+ # but uses more memory
+ #
+ # GEOIP_CHECK_CACHE - check for updated database. If database has been updated,
+ # reload filehandle and/or memory cache.
+ #
+ # GEOIP_INDEX_CACHE - just cache
+ # the most frequently accessed index portion of the database, resulting
+ # in faster lookups than GEOIP_STANDARD, but less memory usage than
+ # GEOIP_MEMORY_CACHE - useful for larger databases such as
+ # GeoIP Organization and GeoIP City. Note, for GeoIP Country, Region
+ # and Netspeed databases, GEOIP_INDEX_CACHE is equivalent to GEOIP_MEMORY_CACHE
+ #
+ GEOIP_STANDARD = 0
+ GEOIP_MEMORY_CACHE = 1
+ GEOIP_CHECK_CACHE = 2
+ GEOIP_INDEX_CACHE = 4
+ cache_options = dict((opt, None) for opt in (0, 1, 2, 4))
+ _city_file = ''
+ _country_file = ''
+
+ # Initially, pointers to GeoIP file references are NULL.
+ _city = None
+ _country = None
+
+ def __init__(self, path=None, cache=0, country=None, city=None):
+ """
+ Initializes the GeoIP object, no parameters are required to use default
+ settings. Keyword arguments may be passed in to customize the locations
+ of the GeoIP data sets.
+
+ * path: Base directory to where GeoIP data is located or the full path
+ to where the city or country data files (*.dat) are located.
+ Assumes that both the city and country data sets are located in
+ this directory; overrides the GEOIP_PATH settings attribute.
+
+ * cache: The cache settings when opening up the GeoIP datasets,
+ and may be an integer in (0, 1, 2, 4) corresponding to
+ the GEOIP_STANDARD, GEOIP_MEMORY_CACHE, GEOIP_CHECK_CACHE,
+ and GEOIP_INDEX_CACHE `GeoIPOptions` C API settings,
+ respectively. Defaults to 0, meaning that the data is read
+ from the disk.
+
+ * country: The name of the GeoIP country data file. Defaults to
+ 'GeoIP.dat'; overrides the GEOIP_COUNTRY settings attribute.
+
+ * city: The name of the GeoIP city data file. Defaults to
+ 'GeoLiteCity.dat'; overrides the GEOIP_CITY settings attribute.
+ """
+ # Checking the given cache option.
+ if cache in self.cache_options:
+ self._cache = self.cache_options[cache]
+ else:
+ raise GeoIPException('Invalid caching option: %s' % cache)
+
+ # Getting the GeoIP data path.
+ if not path:
+ path = GEOIP_SETTINGS.get('GEOIP_PATH', None)
+ if not path: raise GeoIPException('GeoIP path must be provided via parameter or the GEOIP_PATH setting.')
+ if not isinstance(path, basestring):
+ raise TypeError('Invalid path type: %s' % type(path).__name__)
+
+ if os.path.isdir(path):
+ # Constructing the GeoIP database filenames using the settings
+ # dictionary. If the database files for the GeoLite country
+ # and/or city datasets exist, then try and open them.
+ country_db = os.path.join(path, country or GEOIP_SETTINGS.get('GEOIP_COUNTRY', 'GeoIP.dat'))
+ if os.path.isfile(country_db):
+ self._country = geoip_open(country_db, cache)
+ self._country_file = country_db
+
+ city_db = os.path.join(path, city or GEOIP_SETTINGS.get('GEOIP_CITY', 'GeoLiteCity.dat'))
+ if os.path.isfile(city_db):
+ self._city = geoip_open(city_db, cache)
+ self._city_file = city_db
+ elif os.path.isfile(path):
+ # Otherwise, some detective work will be needed to figure
+ # out whether the given database path is for the GeoIP country
+ # or city databases.
+ ptr = geoip_open(path, cache)
+ info = geoip_dbinfo(ptr)
+ if lite_regex.match(info):
+ # GeoLite City database detected.
+ self._city = ptr
+ self._city_file = path
+ elif free_regex.match(info):
+ # GeoIP Country database detected.
+ self._country = ptr
+ self._country_file = path
+ else:
+ raise GeoIPException('Unable to recognize database edition: %s' % info)
+ else:
+ raise GeoIPException('GeoIP path must be a valid file or directory.')
+
+ def __del__(self):
+ # Cleaning any GeoIP file handles lying around.
+ if self._country: geoip_close(self._country)
+ if self._city: geoip_close(self._city)
+
+ def _check_query(self, query, country=False, city=False, city_or_country=False):
+ "Helper routine for checking the query and database availability."
+ # Making sure a string was passed in for the query.
+ if not isinstance(query, basestring):
+ raise TypeError('GeoIP query must be a string, not type %s' % type(query).__name__)
+
+ # Extra checks for the existence of country and city databases.
+ if city_or_country and not (self._country or self._city):
+ raise GeoIPException('Invalid GeoIP country and city data files.')
+ elif country and not self._country:
+ raise GeoIPException('Invalid GeoIP country data file: %s' % self._country_file)
+ elif city and not self._city:
+ raise GeoIPException('Invalid GeoIP city data file: %s' % self._city_file)
+
+ def city(self, query):
+ """
+ Returns a dictionary of city information for the given IP address or
+ Fully Qualified Domain Name (FQDN). Some information in the dictionary
+ may be undefined (None).
+ """
+ self._check_query(query, city=True)
+ if ipregex.match(query):
+ # If an IP address was passed in
+ ptr = rec_by_addr(self._city, c_char_p(query))
+ else:
+ # If a FQDN was passed in.
+ ptr = rec_by_name(self._city, c_char_p(query))
+
+ # Checking the pointer to the C structure, if valid pull out elements
+ # into a dicionary and return.
+ if bool(ptr):
+ record = ptr.contents
+ return dict((tup[0], getattr(record, tup[0])) for tup in record._fields_)
+ else:
+ return None
+
+ def country_code(self, query):
+ "Returns the country code for the given IP Address or FQDN."
+ self._check_query(query, city_or_country=True)
+ if self._country:
+ if ipregex.match(query): return cntry_code_by_addr(self._country, query)
+ else: return cntry_code_by_name(self._country, query)
+ else:
+ return self.city(query)['country_code']
+
+ def country_name(self, query):
+ "Returns the country name for the given IP Address or FQDN."
+ self._check_query(query, city_or_country=True)
+ if self._country:
+ if ipregex.match(query): return cntry_name_by_addr(self._country, query)
+ else: return cntry_name_by_name(self._country, query)
+ else:
+ return self.city(query)['country_name']
+
+ def country(self, query):
+ """
+ Returns a dictonary with with the country code and name when given an
+ IP address or a Fully Qualified Domain Name (FQDN). For example, both
+ '24.124.1.80' and 'djangoproject.com' are valid parameters.
+ """
+ # Returning the country code and name
+ return {'country_code' : self.country_code(query),
+ 'country_name' : self.country_name(query),
+ }
+
+ #### Coordinate retrieval routines ####
+ def coords(self, query, ordering=('longitude', 'latitude')):
+ cdict = self.city(query)
+ if cdict is None: return None
+ else: return tuple(cdict[o] for o in ordering)
+
+ def lon_lat(self, query):
+ "Returns a tuple of the (longitude, latitude) for the given query."
+ return self.coords(query)
+
+ def lat_lon(self, query):
+ "Returns a tuple of the (latitude, longitude) for the given query."
+ return self.coords(query, ('latitude', 'longitude'))
+
+ def geos(self, query):
+ "Returns a GEOS Point object for the given query."
+ ll = self.lon_lat(query)
+ if ll:
+ from django.contrib.gis.geos import Point
+ return Point(ll, srid=4326)
+ else:
+ return None
+
+ #### GeoIP Database Information Routines ####
+ def country_info(self):
+ "Returns information about the GeoIP country database."
+ if self._country is None:
+ ci = 'No GeoIP Country data in "%s"' % self._country_file
+ else:
+ ci = geoip_dbinfo(self._country)
+ return ci
+ country_info = property(country_info)
+
+ def city_info(self):
+ "Retuns information about the GeoIP city database."
+ if self._city is None:
+ ci = 'No GeoIP City data in "%s"' % self._city_file
+ else:
+ ci = geoip_dbinfo(self._city)
+ return ci
+ city_info = property(city_info)
+
+ def info(self):
+ "Returns information about all GeoIP databases in use."
+ return 'Country:\n\t%s\nCity:\n\t%s' % (self.country_info, self.city_info)
+ info = property(info)
+
+ #### Methods for compatibility w/the GeoIP-Python API. ####
+ @classmethod
+ def open(cls, full_path, cache):
+ return GeoIP(full_path, cache)
+
+ def _rec_by_arg(self, arg):
+ if self._city:
+ return self.city(arg)
+ else:
+ return self.country(arg)
+ region_by_addr = city
+ region_by_name = city
+ record_by_addr = _rec_by_arg
+ record_by_name = _rec_by_arg
+ country_code_by_addr = country_code
+ country_code_by_name = country_code
+ country_name_by_addr = country_name
+ country_name_by_name = country_name
diff --git a/parts/django/django/contrib/gis/utils/layermapping.py b/parts/django/django/contrib/gis/utils/layermapping.py
new file mode 100644
index 0000000..cec1989
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/layermapping.py
@@ -0,0 +1,602 @@
+# LayerMapping -- A Django Model/OGR Layer Mapping Utility
+"""
+ The LayerMapping class provides a way to map the contents of OGR
+ vector files (e.g. SHP files) to Geographic-enabled Django models.
+
+ For more information, please consult the GeoDjango documentation:
+ http://geodjango.org/docs/layermapping.html
+"""
+import sys
+from datetime import date, datetime
+from decimal import Decimal
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import connections, DEFAULT_DB_ALIAS
+from django.contrib.gis.db.models import GeometryField
+from django.contrib.gis.gdal import CoordTransform, DataSource, \
+ OGRException, OGRGeometry, OGRGeomType, SpatialReference
+from django.contrib.gis.gdal.field import \
+ OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
+from django.db import models, transaction
+from django.contrib.localflavor.us.models import USStateField
+
+# LayerMapping exceptions.
+class LayerMapError(Exception): pass
+class InvalidString(LayerMapError): pass
+class InvalidDecimal(LayerMapError): pass
+class InvalidInteger(LayerMapError): pass
+class MissingForeignKey(LayerMapError): pass
+
+class LayerMapping(object):
+ "A class that maps OGR Layers to GeoDjango Models."
+
+ # Acceptable 'base' types for a multi-geometry type.
+ MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
+ 2 : OGRGeomType('MultiLineString'),
+ 3 : OGRGeomType('MultiPolygon'),
+ OGRGeomType('Point25D').num : OGRGeomType('MultiPoint25D'),
+ OGRGeomType('LineString25D').num : OGRGeomType('MultiLineString25D'),
+ OGRGeomType('Polygon25D').num : OGRGeomType('MultiPolygon25D'),
+ }
+
+ # Acceptable Django field types and corresponding acceptable OGR
+ # counterparts.
+ FIELD_TYPES = {
+ models.AutoField : OFTInteger,
+ models.IntegerField : (OFTInteger, OFTReal, OFTString),
+ models.FloatField : (OFTInteger, OFTReal),
+ models.DateField : OFTDate,
+ models.DateTimeField : OFTDateTime,
+ models.EmailField : OFTString,
+ models.TimeField : OFTTime,
+ models.DecimalField : (OFTInteger, OFTReal),
+ models.CharField : OFTString,
+ models.SlugField : OFTString,
+ models.TextField : OFTString,
+ models.URLField : OFTString,
+ USStateField : OFTString,
+ models.XMLField : OFTString,
+ models.SmallIntegerField : (OFTInteger, OFTReal, OFTString),
+ models.PositiveSmallIntegerField : (OFTInteger, OFTReal, OFTString),
+ }
+
+ # The acceptable transaction modes.
+ TRANSACTION_MODES = {'autocommit' : transaction.autocommit,
+ 'commit_on_success' : transaction.commit_on_success,
+ }
+
+ def __init__(self, model, data, mapping, layer=0,
+ source_srs=None, encoding=None,
+ transaction_mode='commit_on_success',
+ transform=True, unique=None, using=DEFAULT_DB_ALIAS):
+ """
+ A LayerMapping object is initialized using the given Model (not an instance),
+ a DataSource (or string path to an OGR-supported data file), and a mapping
+ dictionary. See the module level docstring for more details and keyword
+ argument usage.
+ """
+ # Getting the DataSource and the associated Layer.
+ if isinstance(data, basestring):
+ self.ds = DataSource(data)
+ else:
+ self.ds = data
+ self.layer = self.ds[layer]
+
+ self.using = using
+ self.spatial_backend = connections[using].ops
+
+ # Setting the mapping & model attributes.
+ self.mapping = mapping
+ self.model = model
+
+ # Checking the layer -- intitialization of the object will fail if
+ # things don't check out before hand.
+ self.check_layer()
+
+ # Getting the geometry column associated with the model (an
+ # exception will be raised if there is no geometry column).
+ if self.spatial_backend.mysql:
+ transform = False
+ else:
+ self.geo_field = self.geometry_field()
+
+ # Checking the source spatial reference system, and getting
+ # the coordinate transformation object (unless the `transform`
+ # keyword is set to False)
+ if transform:
+ self.source_srs = self.check_srs(source_srs)
+ self.transform = self.coord_transform()
+ else:
+ self.transform = transform
+
+ # Setting the encoding for OFTString fields, if specified.
+ if encoding:
+ # Making sure the encoding exists, if not a LookupError
+ # exception will be thrown.
+ from codecs import lookup
+ lookup(encoding)
+ self.encoding = encoding
+ else:
+ self.encoding = None
+
+ if unique:
+ self.check_unique(unique)
+ transaction_mode = 'autocommit' # Has to be set to autocommit.
+ self.unique = unique
+ else:
+ self.unique = None
+
+ # Setting the transaction decorator with the function in the
+ # transaction modes dictionary.
+ if transaction_mode in self.TRANSACTION_MODES:
+ self.transaction_decorator = self.TRANSACTION_MODES[transaction_mode]
+ self.transaction_mode = transaction_mode
+ else:
+ raise LayerMapError('Unrecognized transaction mode: %s' % transaction_mode)
+
+ if using is None:
+ pass
+
+ #### Checking routines used during initialization ####
+ def check_fid_range(self, fid_range):
+ "This checks the `fid_range` keyword."
+ if fid_range:
+ if isinstance(fid_range, (tuple, list)):
+ return slice(*fid_range)
+ elif isinstance(fid_range, slice):
+ return fid_range
+ else:
+ raise TypeError
+ else:
+ return None
+
+ def check_layer(self):
+ """
+ This checks the Layer metadata, and ensures that it is compatible
+ with the mapping information and model. Unlike previous revisions,
+ there is no need to increment through each feature in the Layer.
+ """
+ # The geometry field of the model is set here.
+ # TODO: Support more than one geometry field / model. However, this
+ # depends on the GDAL Driver in use.
+ self.geom_field = False
+ self.fields = {}
+
+ # Getting lists of the field names and the field types available in
+ # the OGR Layer.
+ ogr_fields = self.layer.fields
+ ogr_field_types = self.layer.field_types
+
+ # Function for determining if the OGR mapping field is in the Layer.
+ def check_ogr_fld(ogr_map_fld):
+ try:
+ idx = ogr_fields.index(ogr_map_fld)
+ except ValueError:
+ raise LayerMapError('Given mapping OGR field "%s" not found in OGR Layer.' % ogr_map_fld)
+ return idx
+
+ # No need to increment through each feature in the model, simply check
+ # the Layer metadata against what was given in the mapping dictionary.
+ for field_name, ogr_name in self.mapping.items():
+ # Ensuring that a corresponding field exists in the model
+ # for the given field name in the mapping.
+ try:
+ model_field = self.model._meta.get_field(field_name)
+ except models.fields.FieldDoesNotExist:
+ raise LayerMapError('Given mapping field "%s" not in given Model fields.' % field_name)
+
+ # Getting the string name for the Django field class (e.g., 'PointField').
+ fld_name = model_field.__class__.__name__
+
+ if isinstance(model_field, GeometryField):
+ if self.geom_field:
+ raise LayerMapError('LayerMapping does not support more than one GeometryField per model.')
+
+ # Getting the coordinate dimension of the geometry field.
+ coord_dim = model_field.dim
+
+ try:
+ if coord_dim == 3:
+ gtype = OGRGeomType(ogr_name + '25D')
+ else:
+ gtype = OGRGeomType(ogr_name)
+ except OGRException:
+ raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name)
+
+ # Making sure that the OGR Layer's Geometry is compatible.
+ ltype = self.layer.geom_type
+ if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)):
+ raise LayerMapError('Invalid mapping geometry; model has %s%s, '
+ 'layer geometry type is %s.' %
+ (fld_name, (coord_dim == 3 and '(dim=3)') or '', ltype))
+
+ # Setting the `geom_field` attribute w/the name of the model field
+ # that is a Geometry. Also setting the coordinate dimension
+ # attribute.
+ self.geom_field = field_name
+ self.coord_dim = coord_dim
+ fields_val = model_field
+ elif isinstance(model_field, models.ForeignKey):
+ if isinstance(ogr_name, dict):
+ # Is every given related model mapping field in the Layer?
+ rel_model = model_field.rel.to
+ for rel_name, ogr_field in ogr_name.items():
+ idx = check_ogr_fld(ogr_field)
+ try:
+ rel_field = rel_model._meta.get_field(rel_name)
+ except models.fields.FieldDoesNotExist:
+ raise LayerMapError('ForeignKey mapping field "%s" not in %s fields.' %
+ (rel_name, rel_model.__class__.__name__))
+ fields_val = rel_model
+ else:
+ raise TypeError('ForeignKey mapping must be of dictionary type.')
+ else:
+ # Is the model field type supported by LayerMapping?
+ if not model_field.__class__ in self.FIELD_TYPES:
+ raise LayerMapError('Django field type "%s" has no OGR mapping (yet).' % fld_name)
+
+ # Is the OGR field in the Layer?
+ idx = check_ogr_fld(ogr_name)
+ ogr_field = ogr_field_types[idx]
+
+ # Can the OGR field type be mapped to the Django field type?
+ if not issubclass(ogr_field, self.FIELD_TYPES[model_field.__class__]):
+ raise LayerMapError('OGR field "%s" (of type %s) cannot be mapped to Django %s.' %
+ (ogr_field, ogr_field.__name__, fld_name))
+ fields_val = model_field
+
+ self.fields[field_name] = fields_val
+
+ def check_srs(self, source_srs):
+ "Checks the compatibility of the given spatial reference object."
+
+ if isinstance(source_srs, SpatialReference):
+ sr = source_srs
+ elif isinstance(source_srs, self.spatial_backend.spatial_ref_sys()):
+ sr = source_srs.srs
+ elif isinstance(source_srs, (int, basestring)):
+ sr = SpatialReference(source_srs)
+ else:
+ # Otherwise just pulling the SpatialReference from the layer
+ sr = self.layer.srs
+
+ if not sr:
+ raise LayerMapError('No source reference system defined.')
+ else:
+ return sr
+
+ def check_unique(self, unique):
+ "Checks the `unique` keyword parameter -- may be a sequence or string."
+ if isinstance(unique, (list, tuple)):
+ # List of fields to determine uniqueness with
+ for attr in unique:
+ if not attr in self.mapping: raise ValueError
+ elif isinstance(unique, basestring):
+ # Only a single field passed in.
+ if unique not in self.mapping: raise ValueError
+ else:
+ raise TypeError('Unique keyword argument must be set with a tuple, list, or string.')
+
+ #### Keyword argument retrieval routines ####
+ def feature_kwargs(self, feat):
+ """
+ Given an OGR Feature, this will return a dictionary of keyword arguments
+ for constructing the mapped model.
+ """
+ # The keyword arguments for model construction.
+ kwargs = {}
+
+ # Incrementing through each model field and OGR field in the
+ # dictionary mapping.
+ for field_name, ogr_name in self.mapping.items():
+ model_field = self.fields[field_name]
+
+ if isinstance(model_field, GeometryField):
+ # Verify OGR geometry.
+ val = self.verify_geom(feat.geom, model_field)
+ elif isinstance(model_field, models.base.ModelBase):
+ # The related _model_, not a field was passed in -- indicating
+ # another mapping for the related Model.
+ val = self.verify_fk(feat, model_field, ogr_name)
+ else:
+ # Otherwise, verify OGR Field type.
+ val = self.verify_ogr_field(feat[ogr_name], model_field)
+
+ # Setting the keyword arguments for the field name with the
+ # value obtained above.
+ kwargs[field_name] = val
+
+ return kwargs
+
+ def unique_kwargs(self, kwargs):
+ """
+ Given the feature keyword arguments (from `feature_kwargs`) this routine
+ will construct and return the uniqueness keyword arguments -- a subset
+ of the feature kwargs.
+ """
+ if isinstance(self.unique, basestring):
+ return {self.unique : kwargs[self.unique]}
+ else:
+ return dict((fld, kwargs[fld]) for fld in self.unique)
+
+ #### Verification routines used in constructing model keyword arguments. ####
+ def verify_ogr_field(self, ogr_field, model_field):
+ """
+ Verifies if the OGR Field contents are acceptable to the Django
+ model field. If they are, the verified value is returned,
+ otherwise the proper exception is raised.
+ """
+ if (isinstance(ogr_field, OFTString) and
+ isinstance(model_field, (models.CharField, models.TextField))):
+ if self.encoding:
+ # The encoding for OGR data sources may be specified here
+ # (e.g., 'cp437' for Census Bureau boundary files).
+ val = unicode(ogr_field.value, self.encoding)
+ else:
+ val = ogr_field.value
+ if len(val) > model_field.max_length:
+ raise InvalidString('%s model field maximum string length is %s, given %s characters.' %
+ (model_field.name, model_field.max_length, len(val)))
+ elif isinstance(ogr_field, OFTReal) and isinstance(model_field, models.DecimalField):
+ try:
+ # Creating an instance of the Decimal value to use.
+ d = Decimal(str(ogr_field.value))
+ except:
+ raise InvalidDecimal('Could not construct decimal from: %s' % ogr_field.value)
+
+ # Getting the decimal value as a tuple.
+ dtup = d.as_tuple()
+ digits = dtup[1]
+ d_idx = dtup[2] # index where the decimal is
+
+ # Maximum amount of precision, or digits to the left of the decimal.
+ max_prec = model_field.max_digits - model_field.decimal_places
+
+ # Getting the digits to the left of the decimal place for the
+ # given decimal.
+ if d_idx < 0:
+ n_prec = len(digits[:d_idx])
+ else:
+ n_prec = len(digits) + d_idx
+
+ # If we have more than the maximum digits allowed, then throw an
+ # InvalidDecimal exception.
+ if n_prec > max_prec:
+ raise InvalidDecimal('A DecimalField with max_digits %d, decimal_places %d must round to an absolute value less than 10^%d.' %
+ (model_field.max_digits, model_field.decimal_places, max_prec))
+ val = d
+ elif isinstance(ogr_field, (OFTReal, OFTString)) and isinstance(model_field, models.IntegerField):
+ # Attempt to convert any OFTReal and OFTString value to an OFTInteger.
+ try:
+ val = int(ogr_field.value)
+ except:
+ raise InvalidInteger('Could not construct integer from: %s' % ogr_field.value)
+ else:
+ val = ogr_field.value
+ return val
+
+ def verify_fk(self, feat, rel_model, rel_mapping):
+ """
+ Given an OGR Feature, the related model and its dictionary mapping,
+ this routine will retrieve the related model for the ForeignKey
+ mapping.
+ """
+ # TODO: It is expensive to retrieve a model for every record --
+ # explore if an efficient mechanism exists for caching related
+ # ForeignKey models.
+
+ # Constructing and verifying the related model keyword arguments.
+ fk_kwargs = {}
+ for field_name, ogr_name in rel_mapping.items():
+ fk_kwargs[field_name] = self.verify_ogr_field(feat[ogr_name], rel_model._meta.get_field(field_name))
+
+ # Attempting to retrieve and return the related model.
+ try:
+ return rel_model.objects.get(**fk_kwargs)
+ except ObjectDoesNotExist:
+ raise MissingForeignKey('No ForeignKey %s model found with keyword arguments: %s' % (rel_model.__name__, fk_kwargs))
+
+ def verify_geom(self, geom, model_field):
+ """
+ Verifies the geometry -- will construct and return a GeometryCollection
+ if necessary (for example if the model field is MultiPolygonField while
+ the mapped shapefile only contains Polygons).
+ """
+ # Downgrade a 3D geom to a 2D one, if necessary.
+ if self.coord_dim != geom.coord_dim:
+ geom.coord_dim = self.coord_dim
+
+ if self.make_multi(geom.geom_type, model_field):
+ # Constructing a multi-geometry type to contain the single geometry
+ multi_type = self.MULTI_TYPES[geom.geom_type.num]
+ g = OGRGeometry(multi_type)
+ g.add(geom)
+ else:
+ g = geom
+
+ # Transforming the geometry with our Coordinate Transformation object,
+ # but only if the class variable `transform` is set w/a CoordTransform
+ # object.
+ if self.transform: g.transform(self.transform)
+
+ # Returning the WKT of the geometry.
+ return g.wkt
+
+ #### Other model methods ####
+ def coord_transform(self):
+ "Returns the coordinate transformation object."
+ SpatialRefSys = self.spatial_backend.spatial_ref_sys()
+ try:
+ # Getting the target spatial reference system
+ target_srs = SpatialRefSys.objects.get(srid=self.geo_field.srid).srs
+
+ # Creating the CoordTransform object
+ return CoordTransform(self.source_srs, target_srs)
+ except Exception, msg:
+ raise LayerMapError('Could not translate between the data source and model geometry: %s' % msg)
+
+ def geometry_field(self):
+ "Returns the GeometryField instance associated with the geographic column."
+ # Use the `get_field_by_name` on the model's options so that we
+ # get the correct field instance if there's model inheritance.
+ opts = self.model._meta
+ fld, model, direct, m2m = opts.get_field_by_name(self.geom_field)
+ return fld
+
+ def make_multi(self, geom_type, model_field):
+ """
+ Given the OGRGeomType for a geometry and its associated GeometryField,
+ determine whether the geometry should be turned into a GeometryCollection.
+ """
+ return (geom_type.num in self.MULTI_TYPES and
+ model_field.__class__.__name__ == 'Multi%s' % geom_type.django)
+
+ def save(self, verbose=False, fid_range=False, step=False,
+ progress=False, silent=False, stream=sys.stdout, strict=False):
+ """
+ Saves the contents from the OGR DataSource Layer into the database
+ according to the mapping dictionary given at initialization.
+
+ Keyword Parameters:
+ verbose:
+ If set, information will be printed subsequent to each model save
+ executed on the database.
+
+ fid_range:
+ May be set with a slice or tuple of (begin, end) feature ID's to map
+ from the data source. In other words, this keyword enables the user
+ to selectively import a subset range of features in the geographic
+ data source.
+
+ step:
+ If set with an integer, transactions will occur at every step
+ interval. For example, if step=1000, a commit would occur after
+ the 1,000th feature, the 2,000th feature etc.
+
+ progress:
+ When this keyword is set, status information will be printed giving
+ the number of features processed and sucessfully saved. By default,
+ progress information will pe printed every 1000 features processed,
+ however, this default may be overridden by setting this keyword with an
+ integer for the desired interval.
+
+ stream:
+ Status information will be written to this file handle. Defaults to
+ using `sys.stdout`, but any object with a `write` method is supported.
+
+ silent:
+ By default, non-fatal error notifications are printed to stdout, but
+ this keyword may be set to disable these notifications.
+
+ strict:
+ Execution of the model mapping will cease upon the first error
+ encountered. The default behavior is to attempt to continue.
+ """
+ # Getting the default Feature ID range.
+ default_range = self.check_fid_range(fid_range)
+
+ # Setting the progress interval, if requested.
+ if progress:
+ if progress is True or not isinstance(progress, int):
+ progress_interval = 1000
+ else:
+ progress_interval = progress
+
+ # Defining the 'real' save method, utilizing the transaction
+ # decorator created during initialization.
+ @self.transaction_decorator
+ def _save(feat_range=default_range, num_feat=0, num_saved=0):
+ if feat_range:
+ layer_iter = self.layer[feat_range]
+ else:
+ layer_iter = self.layer
+
+ for feat in layer_iter:
+ num_feat += 1
+ # Getting the keyword arguments
+ try:
+ kwargs = self.feature_kwargs(feat)
+ except LayerMapError, msg:
+ # Something borked the validation
+ if strict: raise
+ elif not silent:
+ stream.write('Ignoring Feature ID %s because: %s\n' % (feat.fid, msg))
+ else:
+ # Constructing the model using the keyword args
+ is_update = False
+ if self.unique:
+ # If we want unique models on a particular field, handle the
+ # geometry appropriately.
+ try:
+ # Getting the keyword arguments and retrieving
+ # the unique model.
+ u_kwargs = self.unique_kwargs(kwargs)
+ m = self.model.objects.using(self.using).get(**u_kwargs)
+ is_update = True
+
+ # Getting the geometry (in OGR form), creating
+ # one from the kwargs WKT, adding in additional
+ # geometries, and update the attribute with the
+ # just-updated geometry WKT.
+ geom = getattr(m, self.geom_field).ogr
+ new = OGRGeometry(kwargs[self.geom_field])
+ for g in new: geom.add(g)
+ setattr(m, self.geom_field, geom.wkt)
+ except ObjectDoesNotExist:
+ # No unique model exists yet, create.
+ m = self.model(**kwargs)
+ else:
+ m = self.model(**kwargs)
+
+ try:
+ # Attempting to save.
+ m.save(using=self.using)
+ num_saved += 1
+ if verbose: stream.write('%s: %s\n' % (is_update and 'Updated' or 'Saved', m))
+ except SystemExit:
+ raise
+ except Exception, msg:
+ if self.transaction_mode == 'autocommit':
+ # Rolling back the transaction so that other model saves
+ # will work.
+ transaction.rollback_unless_managed()
+ if strict:
+ # Bailing out if the `strict` keyword is set.
+ if not silent:
+ stream.write('Failed to save the feature (id: %s) into the model with the keyword arguments:\n' % feat.fid)
+ stream.write('%s\n' % kwargs)
+ raise
+ elif not silent:
+ stream.write('Failed to save %s:\n %s\nContinuing\n' % (kwargs, msg))
+
+ # Printing progress information, if requested.
+ if progress and num_feat % progress_interval == 0:
+ stream.write('Processed %d features, saved %d ...\n' % (num_feat, num_saved))
+
+ # Only used for status output purposes -- incremental saving uses the
+ # values returned here.
+ return num_saved, num_feat
+
+ nfeat = self.layer.num_feat
+ if step and isinstance(step, int) and step < nfeat:
+ # Incremental saving is requested at the given interval (step)
+ if default_range:
+ raise LayerMapError('The `step` keyword may not be used in conjunction with the `fid_range` keyword.')
+ beg, num_feat, num_saved = (0, 0, 0)
+ indices = range(step, nfeat, step)
+ n_i = len(indices)
+
+ for i, end in enumerate(indices):
+ # Constructing the slice to use for this step; the last slice is
+ # special (e.g, [100:] instead of [90:100]).
+ if i+1 == n_i: step_slice = slice(beg, None)
+ else: step_slice = slice(beg, end)
+
+ try:
+ num_feat, num_saved = _save(step_slice, num_feat, num_saved)
+ beg = end
+ except:
+ stream.write('%s\nFailed to save slice: %s\n' % ('=-' * 20, step_slice))
+ raise
+ else:
+ # Otherwise, just calling the previously defined _save() function.
+ _save()
diff --git a/parts/django/django/contrib/gis/utils/ogrinfo.py b/parts/django/django/contrib/gis/utils/ogrinfo.py
new file mode 100644
index 0000000..1e4c42d
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/ogrinfo.py
@@ -0,0 +1,53 @@
+"""
+This module includes some utility functions for inspecting the layout
+of a GDAL data source -- the functionality is analogous to the output
+produced by the `ogrinfo` utility.
+"""
+
+from django.contrib.gis.gdal import DataSource
+from django.contrib.gis.gdal.geometries import GEO_CLASSES
+
+def ogrinfo(data_source, num_features=10):
+ """
+ Walks the available layers in the supplied `data_source`, displaying
+ the fields for the first `num_features` features.
+ """
+
+ # Checking the parameters.
+ if isinstance(data_source, str):
+ data_source = DataSource(data_source)
+ elif isinstance(data_source, DataSource):
+ pass
+ else:
+ raise Exception('Data source parameter must be a string or a DataSource object.')
+
+ for i, layer in enumerate(data_source):
+ print "data source : %s" % data_source.name
+ print "==== layer %s" % i
+ print " shape type: %s" % GEO_CLASSES[layer.geom_type.num].__name__
+ print " # features: %s" % len(layer)
+ print " srs: %s" % layer.srs
+ extent_tup = layer.extent.tuple
+ print " extent: %s - %s" % (extent_tup[0:2], extent_tup[2:4])
+ print "Displaying the first %s features ====" % num_features
+
+ width = max(*map(len,layer.fields))
+ fmt = " %%%ss: %%s" % width
+ for j, feature in enumerate(layer[:num_features]):
+ print "=== Feature %s" % j
+ for fld_name in layer.fields:
+ type_name = feature[fld_name].type_name
+ output = fmt % (fld_name, type_name)
+ val = feature.get(fld_name)
+ if val:
+ if isinstance(val, str):
+ val_fmt = ' ("%s")'
+ else:
+ val_fmt = ' (%s)'
+ output += val_fmt % val
+ else:
+ output += ' (None)'
+ print output
+
+# For backwards compatibility.
+sample = ogrinfo
diff --git a/parts/django/django/contrib/gis/utils/ogrinspect.py b/parts/django/django/contrib/gis/utils/ogrinspect.py
new file mode 100644
index 0000000..145bd22
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/ogrinspect.py
@@ -0,0 +1,225 @@
+"""
+This module is for inspecting OGR data sources and generating either
+models for GeoDjango and/or mapping dictionaries for use with the
+`LayerMapping` utility.
+
+Author: Travis Pinney, Dane Springmeyer, & Justin Bronn
+"""
+from itertools import izip
+# Requires GDAL to use.
+from django.contrib.gis.gdal import DataSource
+from django.contrib.gis.gdal.field import OFTDate, OFTDateTime, OFTInteger, OFTReal, OFTString, OFTTime
+
+def mapping(data_source, geom_name='geom', layer_key=0, multi_geom=False):
+ """
+ Given a DataSource, generates a dictionary that may be used
+ for invoking the LayerMapping utility.
+
+ Keyword Arguments:
+ `geom_name` => The name of the geometry field to use for the model.
+
+ `layer_key` => The key for specifying which layer in the DataSource to use;
+ defaults to 0 (the first layer). May be an integer index or a string
+ identifier for the layer.
+
+ `multi_geom` => Boolean (default: False) - specify as multigeometry.
+ """
+ if isinstance(data_source, basestring):
+ # Instantiating the DataSource from the string.
+ data_source = DataSource(data_source)
+ elif isinstance(data_source, DataSource):
+ pass
+ else:
+ raise TypeError('Data source parameter must be a string or a DataSource object.')
+
+ # Creating the dictionary.
+ _mapping = {}
+
+ # Generating the field name for each field in the layer.
+ for field in data_source[layer_key].fields:
+ mfield = field.lower()
+ if mfield[-1:] == '_': mfield += 'field'
+ _mapping[mfield] = field
+ gtype = data_source[layer_key].geom_type
+ if multi_geom and gtype.num in (1, 2, 3): prefix = 'MULTI'
+ else: prefix = ''
+ _mapping[geom_name] = prefix + str(gtype).upper()
+ return _mapping
+
+def ogrinspect(*args, **kwargs):
+ """
+ Given a data source (either a string or a DataSource object) and a string
+ model name this function will generate a GeoDjango model.
+
+ Usage:
+
+ >>> from django.contrib.gis.utils import ogrinspect
+ >>> ogrinspect('/path/to/shapefile.shp','NewModel')
+
+ ...will print model definition to stout
+
+ or put this in a python script and use to redirect the output to a new
+ model like:
+
+ $ python generate_model.py > myapp/models.py
+
+ # generate_model.py
+ from django.contrib.gis.utils import ogrinspect
+ shp_file = 'data/mapping_hacks/world_borders.shp'
+ model_name = 'WorldBorders'
+
+ print ogrinspect(shp_file, model_name, multi_geom=True, srid=4326,
+ geom_name='shapes', blank=True)
+
+ Required Arguments
+ `datasource` => string or DataSource object to file pointer
+
+ `model name` => string of name of new model class to create
+
+ Optional Keyword Arguments
+ `geom_name` => For specifying the model name for the Geometry Field.
+ Otherwise will default to `geom`
+
+ `layer_key` => The key for specifying which layer in the DataSource to use;
+ defaults to 0 (the first layer). May be an integer index or a string
+ identifier for the layer.
+
+ `srid` => The SRID to use for the Geometry Field. If it can be determined,
+ the SRID of the datasource is used.
+
+ `multi_geom` => Boolean (default: False) - specify as multigeometry.
+
+ `name_field` => String - specifies a field name to return for the
+ `__unicode__` function (which will be generated if specified).
+
+ `imports` => Boolean (default: True) - set to False to omit the
+ `from django.contrib.gis.db import models` code from the
+ autogenerated models thus avoiding duplicated imports when building
+ more than one model by batching ogrinspect()
+
+ `decimal` => Boolean or sequence (default: False). When set to True
+ all generated model fields corresponding to the `OFTReal` type will
+ be `DecimalField` instead of `FloatField`. A sequence of specific
+ field names to generate as `DecimalField` may also be used.
+
+ `blank` => Boolean or sequence (default: False). When set to True all
+ generated model fields will have `blank=True`. If the user wants to
+ give specific fields to have blank, then a list/tuple of OGR field
+ names may be used.
+
+ `null` => Boolean (default: False) - When set to True all generated
+ model fields will have `null=True`. If the user wants to specify
+ give specific fields to have null, then a list/tuple of OGR field
+ names may be used.
+
+ Note: This routine calls the _ogrinspect() helper to do the heavy lifting.
+ """
+ return '\n'.join(s for s in _ogrinspect(*args, **kwargs))
+
+def _ogrinspect(data_source, model_name, geom_name='geom', layer_key=0, srid=None,
+ multi_geom=False, name_field=None, imports=True,
+ decimal=False, blank=False, null=False):
+ """
+ Helper routine for `ogrinspect` that generates GeoDjango models corresponding
+ to the given data source. See the `ogrinspect` docstring for more details.
+ """
+ # Getting the DataSource
+ if isinstance(data_source, str):
+ data_source = DataSource(data_source)
+ elif isinstance(data_source, DataSource):
+ pass
+ else:
+ raise TypeError('Data source parameter must be a string or a DataSource object.')
+
+ # Getting the layer corresponding to the layer key and getting
+ # a string listing of all OGR fields in the Layer.
+ layer = data_source[layer_key]
+ ogr_fields = layer.fields
+
+ # Creating lists from the `null`, `blank`, and `decimal`
+ # keyword arguments.
+ def process_kwarg(kwarg):
+ if isinstance(kwarg, (list, tuple)):
+ return [s.lower() for s in kwarg]
+ elif kwarg:
+ return [s.lower() for s in ogr_fields]
+ else:
+ return []
+ null_fields = process_kwarg(null)
+ blank_fields = process_kwarg(blank)
+ decimal_fields = process_kwarg(decimal)
+
+ # Gets the `null` and `blank` keywords for the given field name.
+ def get_kwargs_str(field_name):
+ kwlist = []
+ if field_name.lower() in null_fields: kwlist.append('null=True')
+ if field_name.lower() in blank_fields: kwlist.append('blank=True')
+ if kwlist: return ', ' + ', '.join(kwlist)
+ else: return ''
+
+ # For those wishing to disable the imports.
+ if imports:
+ yield '# This is an auto-generated Django model module created by ogrinspect.'
+ yield 'from django.contrib.gis.db import models'
+ yield ''
+
+ yield 'class %s(models.Model):' % model_name
+
+ for field_name, width, precision, field_type in izip(ogr_fields, layer.field_widths, layer.field_precisions, layer.field_types):
+ # The model field name.
+ mfield = field_name.lower()
+ if mfield[-1:] == '_': mfield += 'field'
+
+ # Getting the keyword args string.
+ kwargs_str = get_kwargs_str(field_name)
+
+ if field_type is OFTReal:
+ # By default OFTReals are mapped to `FloatField`, however, they
+ # may also be mapped to `DecimalField` if specified in the
+ # `decimal` keyword.
+ if field_name.lower() in decimal_fields:
+ yield ' %s = models.DecimalField(max_digits=%d, decimal_places=%d%s)' % (mfield, width, precision, kwargs_str)
+ else:
+ yield ' %s = models.FloatField(%s)' % (mfield, kwargs_str[2:])
+ elif field_type is OFTInteger:
+ yield ' %s = models.IntegerField(%s)' % (mfield, kwargs_str[2:])
+ elif field_type is OFTString:
+ yield ' %s = models.CharField(max_length=%s%s)' % (mfield, width, kwargs_str)
+ elif field_type is OFTDate:
+ yield ' %s = models.DateField(%s)' % (mfield, kwargs_str[2:])
+ elif field_type is OFTDateTime:
+ yield ' %s = models.DateTimeField(%s)' % (mfield, kwargs_str[2:])
+ elif field_type is OFTDate:
+ yield ' %s = models.TimeField(%s)' % (mfield, kwargs_str[2:])
+ else:
+ raise TypeError('Unknown field type %s in %s' % (field_type, mfield))
+
+ # TODO: Autodetection of multigeometry types (see #7218).
+ gtype = layer.geom_type
+ if multi_geom and gtype.num in (1, 2, 3):
+ geom_field = 'Multi%s' % gtype.django
+ else:
+ geom_field = gtype.django
+
+ # Setting up the SRID keyword string.
+ if srid is None:
+ if layer.srs is None:
+ srid_str = 'srid=-1'
+ else:
+ srid = layer.srs.srid
+ if srid is None:
+ srid_str = 'srid=-1'
+ elif srid == 4326:
+ # WGS84 is already the default.
+ srid_str = ''
+ else:
+ srid_str = 'srid=%s' % srid
+ else:
+ srid_str = 'srid=%s' % srid
+
+ yield ' %s = models.%s(%s)' % (geom_name, geom_field, srid_str)
+ yield ' objects = models.GeoManager()'
+
+ if name_field:
+ yield ''
+ yield ' def __unicode__(self): return self.%s' % name_field
diff --git a/parts/django/django/contrib/gis/utils/srs.py b/parts/django/django/contrib/gis/utils/srs.py
new file mode 100644
index 0000000..989929e
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/srs.py
@@ -0,0 +1,77 @@
+from django.contrib.gis.gdal import SpatialReference
+from django.db import connections, DEFAULT_DB_ALIAS
+
+def add_srs_entry(srs, auth_name='EPSG', auth_srid=None, ref_sys_name=None,
+ database=DEFAULT_DB_ALIAS):
+ """
+ This function takes a GDAL SpatialReference system and adds its information
+ to the `spatial_ref_sys` table of the spatial backend. Doing this enables
+ database-level spatial transformations for the backend. Thus, this utility
+ is useful for adding spatial reference systems not included by default with
+ the backend -- for example, the so-called "Google Maps Mercator Projection"
+ is excluded in PostGIS 1.3 and below, and the following adds it to the
+ `spatial_ref_sys` table:
+
+ >>> from django.contrib.gis.utils import add_srs_entry
+ >>> add_srs_entry(900913)
+
+ Keyword Arguments:
+ auth_name:
+ This keyword may be customized with the value of the `auth_name` field.
+ Defaults to 'EPSG'.
+
+ auth_srid:
+ This keyword may be customized with the value of the `auth_srid` field.
+ Defaults to the SRID determined by GDAL.
+
+ ref_sys_name:
+ For SpatiaLite users only, sets the value of the the `ref_sys_name` field.
+ Defaults to the name determined by GDAL.
+
+ database:
+ The name of the database connection to use; the default is the value
+ of `django.db.DEFAULT_DB_ALIAS` (at the time of this writing, it's value
+ is 'default').
+ """
+ connection = connections[database]
+ if not hasattr(connection.ops, 'spatial_version'):
+ raise Exception('The `add_srs_entry` utility only works '
+ 'with spatial backends.')
+ if connection.ops.oracle or connection.ops.mysql:
+ raise Exception('This utility does not support the '
+ 'Oracle or MySQL spatial backends.')
+ SpatialRefSys = connection.ops.spatial_ref_sys()
+
+ # If argument is not a `SpatialReference` instance, use it as parameter
+ # to construct a `SpatialReference` instance.
+ if not isinstance(srs, SpatialReference):
+ srs = SpatialReference(srs)
+
+ if srs.srid is None:
+ raise Exception('Spatial reference requires an SRID to be '
+ 'compatible with the spatial backend.')
+
+ # Initializing the keyword arguments dictionary for both PostGIS
+ # and SpatiaLite.
+ kwargs = {'srid' : srs.srid,
+ 'auth_name' : auth_name,
+ 'auth_srid' : auth_srid or srs.srid,
+ 'proj4text' : srs.proj4,
+ }
+
+ # Backend-specific fields for the SpatialRefSys model.
+ if connection.ops.postgis:
+ kwargs['srtext'] = srs.wkt
+ if connection.ops.spatialite:
+ kwargs['ref_sys_name'] = ref_sys_name or srs.name
+
+ # Creating the spatial_ref_sys model.
+ try:
+ # Try getting via SRID only, because using all kwargs may
+ # differ from exact wkt/proj in database.
+ sr = SpatialRefSys.objects.get(srid=srs.srid)
+ except SpatialRefSys.DoesNotExist:
+ sr = SpatialRefSys.objects.create(**kwargs)
+
+# Alias is for backwards-compatibility purposes.
+add_postgis_srs = add_srs_entry
diff --git a/parts/django/django/contrib/gis/utils/wkt.py b/parts/django/django/contrib/gis/utils/wkt.py
new file mode 100644
index 0000000..4aecc62
--- /dev/null
+++ b/parts/django/django/contrib/gis/utils/wkt.py
@@ -0,0 +1,55 @@
+"""
+ Utilities for manipulating Geometry WKT.
+"""
+
+def precision_wkt(geom, prec):
+ """
+ Returns WKT text of the geometry according to the given precision (an
+ integer or a string). If the precision is an integer, then the decimal
+ places of coordinates WKT will be truncated to that number:
+
+ >>> pnt = Point(5, 23)
+ >>> pnt.wkt
+ 'POINT (5.0000000000000000 23.0000000000000000)'
+ >>> precision(geom, 1)
+ 'POINT (5.0 23.0)'
+
+ If the precision is a string, it must be valid Python format string
+ (e.g., '%20.7f') -- thus, you should know what you're doing.
+ """
+ if isinstance(prec, int):
+ num_fmt = '%%.%df' % prec
+ elif isinstance(prec, basestring):
+ num_fmt = prec
+ else:
+ raise TypeError
+
+ # TODO: Support 3D geometries.
+ coord_fmt = ' '.join([num_fmt, num_fmt])
+
+ def formatted_coords(coords):
+ return ','.join([coord_fmt % c[:2] for c in coords])
+
+ def formatted_poly(poly):
+ return ','.join(['(%s)' % formatted_coords(r) for r in poly])
+
+ def formatted_geom(g):
+ gtype = str(g.geom_type).upper()
+ yield '%s(' % gtype
+ if gtype == 'POINT':
+ yield formatted_coords((g.coords,))
+ elif gtype in ('LINESTRING', 'LINEARRING'):
+ yield formatted_coords(g.coords)
+ elif gtype in ('POLYGON', 'MULTILINESTRING'):
+ yield formatted_poly(g)
+ elif gtype == 'MULTIPOINT':
+ yield formatted_coords(g.coords)
+ elif gtype == 'MULTIPOLYGON':
+ yield ','.join(['(%s)' % formatted_poly(p) for p in g])
+ elif gtype == 'GEOMETRYCOLLECTION':
+ yield ','.join([''.join([wkt for wkt in formatted_geom(child)]) for child in g])
+ else:
+ raise TypeError
+ yield ')'
+
+ return ''.join([wkt for wkt in formatted_geom(geom)])