diff --git a/django/contrib/gis/db/backends/base.py b/django/contrib/gis/db/backends/base.py index bbf38b0242..0eaacae63f 100644 --- a/django/contrib/gis/db/backends/base.py +++ b/django/contrib/gis/db/backends/base.py @@ -50,6 +50,7 @@ class BaseSpatialOperations(object): perimeter3d = False point_on_surface = False polygonize = False + reverse = False scale = False snap_to_grid = False sym_difference = False diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index b444bb2899..cf210274db 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -88,6 +88,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations): 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' diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 030599dfff..2929352386 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -252,6 +252,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 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' @@ -268,6 +269,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): 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' diff --git a/django/contrib/gis/db/models/manager.py b/django/contrib/gis/db/models/manager.py index 84d728dd1e..1e936c79cd 100644 --- a/django/contrib/gis/db/models/manager.py +++ b/django/contrib/gis/db/models/manager.py @@ -36,6 +36,9 @@ class GeoManager(Manager): 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 geojson(self, *args, **kwargs): return self.get_query_set().geojson(*args, **kwargs) @@ -69,6 +72,9 @@ class GeoManager(Manager): def point_on_surface(self, *args, **kwargs): return self.get_query_set().point_on_surface(*args, **kwargs) + def reverse(self, *args, **kwargs): + return self.get_query_set().reverse(*args, **kwargs) + def scale(self, *args, **kwargs): return self.get_query_set().scale(*args, **kwargs) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index f736a8dd8d..781df8c76f 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -2,7 +2,7 @@ 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 +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 @@ -119,6 +119,15 @@ class GeoQuerySet(QuerySet): """ 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` @@ -244,6 +253,16 @@ class GeoQuerySet(QuerySet): """ return self._geom_attribute('point_on_surface', **kwargs) + def reverse(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(),} + 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 @@ -489,7 +508,8 @@ class GeoQuerySet(QuerySet): # 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) + 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'] diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py index c4ce893bee..89027eedfb 100644 --- a/django/contrib/gis/tests/geoapp/models.py +++ b/django/contrib/gis/tests/geoapp/models.py @@ -27,6 +27,12 @@ class State(models.Model): 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) diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index 24a7c1c02f..ba662bb48b 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -8,7 +8,7 @@ from django.contrib.gis.tests.utils import \ mysql, oracle, postgis, spatialite from django.test import TestCase -from models import Country, City, PennsylvaniaCity, State +from models import Country, City, PennsylvaniaCity, State, Track if not spatialite: from models import Feature, MinusOneSRID @@ -687,6 +687,33 @@ class GeoModelTest(TestCase): 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()." + coords = [ (-95.363151, 29.763374), (-95.448601, 29.713803) ] + Track.objects.create(name='Foo', line=LineString(coords)) + t = Track.objects.reverse().get(name='Foo') + coords.reverse() + self.assertEqual(tuple(coords), t.reverse.coords) + if oracle: + self.assertRaises(TypeError, State.objects.reverse) + + @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) + from test_feeds import GeoFeedTest from test_regress import GeoRegressionTests from test_sitemaps import GeoSitemapTest