mirror of https://github.com/django/django.git
Fixed #23757 -- Added 3D introspection support to Spatialite backend
Thanks Tim Graham for the review.
This commit is contained in:
parent
b4a56ed4f5
commit
65129aac07
|
@ -14,6 +14,8 @@ class BaseSpatialFeatures(object):
|
||||||
# Does the backend introspect GeometryField to its subtypes?
|
# Does the backend introspect GeometryField to its subtypes?
|
||||||
supports_geometry_field_introspection = True
|
supports_geometry_field_introspection = True
|
||||||
|
|
||||||
|
# Does the backend support storing 3D geometries?
|
||||||
|
supports_3d_storage = False
|
||||||
# Reference implementation of 3D functions is:
|
# Reference implementation of 3D functions is:
|
||||||
# http://postgis.net/docs/PostGIS_Special_Functions_Index.html#PostGIS_3D_Functions
|
# http://postgis.net/docs/PostGIS_Special_Functions_Index.html#PostGIS_3D_Functions
|
||||||
supports_3d_functions = False
|
supports_3d_functions = False
|
||||||
|
|
|
@ -4,5 +4,6 @@ from django.db.backends.postgresql_psycopg2.features import \
|
||||||
|
|
||||||
|
|
||||||
class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures):
|
class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures):
|
||||||
|
supports_3d_storage = True
|
||||||
supports_3d_functions = True
|
supports_3d_functions = True
|
||||||
supports_left_right_lookups = True
|
supports_left_right_lookups = True
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
|
||||||
|
from django.contrib.gis.geos import geos_version
|
||||||
from django.db.backends.sqlite3.features import \
|
from django.db.backends.sqlite3.features import \
|
||||||
DatabaseFeatures as SQLiteDatabaseFeatures
|
DatabaseFeatures as SQLiteDatabaseFeatures
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
@ -15,3 +16,7 @@ class DatabaseFeatures(BaseSpatialFeatures, SQLiteDatabaseFeatures):
|
||||||
# which can result in a significant performance improvement when
|
# which can result in a significant performance improvement when
|
||||||
# creating the database.
|
# creating the database.
|
||||||
return self.connection.ops.spatial_version >= (4, 1, 0)
|
return self.connection.ops.spatial_version >= (4, 1, 0)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def supports_3d_storage(self):
|
||||||
|
return geos_version() >= '3.3'
|
||||||
|
|
|
@ -41,7 +41,13 @@ class SpatiaLiteIntrospection(DatabaseIntrospection):
|
||||||
|
|
||||||
# OGRGeomType does not require GDAL and makes it easy to convert
|
# OGRGeomType does not require GDAL and makes it easy to convert
|
||||||
# from OGC geom type name to Django field.
|
# from OGC geom type name to Django field.
|
||||||
field_type = OGRGeomType(row[2]).django
|
ogr_type = row[2]
|
||||||
|
if isinstance(ogr_type, six.integer_types) and ogr_type > 1000:
|
||||||
|
# Spatialite versions >= 4 use the new SFSQL 1.2 offsets
|
||||||
|
# 1000 (Z), 2000 (M), and 3000 (ZM) to indicate the presence of
|
||||||
|
# higher dimensional coordinates (M not yet supported by Django).
|
||||||
|
ogr_type = ogr_type % 1000 + OGRGeomType.wkb25bit
|
||||||
|
field_type = OGRGeomType(ogr_type).django
|
||||||
|
|
||||||
# Getting any GeometryField keyword arguments that are not the default.
|
# Getting any GeometryField keyword arguments that are not the default.
|
||||||
dim = row[0]
|
dim = row[0]
|
||||||
|
@ -49,7 +55,7 @@ class SpatiaLiteIntrospection(DatabaseIntrospection):
|
||||||
field_params = {}
|
field_params = {}
|
||||||
if srid != 4326:
|
if srid != 4326:
|
||||||
field_params['srid'] = srid
|
field_params['srid'] = srid
|
||||||
if isinstance(dim, six.string_types) and 'Z' in dim:
|
if (isinstance(dim, six.string_types) and 'Z' in dim) or dim == 3:
|
||||||
field_params['dim'] = 3
|
field_params['dim'] = 3
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
|
|
|
@ -74,7 +74,7 @@ bbox_data = (
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(HAS_GDAL, "GDAL is required for Geo3DTest.")
|
@skipUnless(HAS_GDAL, "GDAL is required for Geo3DTest.")
|
||||||
@skipUnlessDBFeature("gis_enabled", "supports_3d_functions")
|
@skipUnlessDBFeature("gis_enabled", "supports_3d_storage")
|
||||||
class Geo3DTest(TestCase):
|
class Geo3DTest(TestCase):
|
||||||
"""
|
"""
|
||||||
Only a subset of the PostGIS routines are 3D-enabled, and this TestCase
|
Only a subset of the PostGIS routines are 3D-enabled, and this TestCase
|
||||||
|
@ -189,6 +189,7 @@ class Geo3DTest(TestCase):
|
||||||
ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
|
ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
|
||||||
self.assertTrue(ref_json_regex.match(h.geojson))
|
self.assertTrue(ref_json_regex.match(h.geojson))
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_functions")
|
||||||
def test_union(self):
|
def test_union(self):
|
||||||
"""
|
"""
|
||||||
Testing the Union aggregate of 3D models.
|
Testing the Union aggregate of 3D models.
|
||||||
|
@ -207,6 +208,7 @@ class Geo3DTest(TestCase):
|
||||||
# Ordering of points in the resulting geometry may vary between implementations
|
# Ordering of points in the resulting geometry may vary between implementations
|
||||||
self.assertSetEqual({p.ewkt for p in ref_union}, {p.ewkt for p in union})
|
self.assertSetEqual({p.ewkt for p in ref_union}, {p.ewkt for p in union})
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_functions")
|
||||||
@ignore_warnings(category=RemovedInDjango20Warning)
|
@ignore_warnings(category=RemovedInDjango20Warning)
|
||||||
def test_extent(self):
|
def test_extent(self):
|
||||||
"""
|
"""
|
||||||
|
@ -227,6 +229,7 @@ class Geo3DTest(TestCase):
|
||||||
self.assertIsNone(City3D.objects.none().extent3d())
|
self.assertIsNone(City3D.objects.none().extent3d())
|
||||||
self.assertIsNone(City3D.objects.none().aggregate(Extent3D('point'))['point__extent3d'])
|
self.assertIsNone(City3D.objects.none().aggregate(Extent3D('point'))['point__extent3d'])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_functions")
|
||||||
def test_perimeter(self):
|
def test_perimeter(self):
|
||||||
"""
|
"""
|
||||||
Testing GeoQuerySet.perimeter() on 3D fields.
|
Testing GeoQuerySet.perimeter() on 3D fields.
|
||||||
|
@ -244,6 +247,7 @@ class Geo3DTest(TestCase):
|
||||||
Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m,
|
Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m,
|
||||||
tol)
|
tol)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_functions")
|
||||||
def test_length(self):
|
def test_length(self):
|
||||||
"""
|
"""
|
||||||
Testing GeoQuerySet.length() on 3D fields.
|
Testing GeoQuerySet.length() on 3D fields.
|
||||||
|
@ -276,6 +280,7 @@ class Geo3DTest(TestCase):
|
||||||
InterstateProj3D.objects.length().get(name='I-45').length.m,
|
InterstateProj3D.objects.length().get(name='I-45').length.m,
|
||||||
tol)
|
tol)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_functions")
|
||||||
def test_scale(self):
|
def test_scale(self):
|
||||||
"""
|
"""
|
||||||
Testing GeoQuerySet.scale() on Z values.
|
Testing GeoQuerySet.scale() on Z values.
|
||||||
|
@ -287,6 +292,7 @@ class Geo3DTest(TestCase):
|
||||||
for city in City3D.objects.scale(1.0, 1.0, zscale):
|
for city in City3D.objects.scale(1.0, 1.0, zscale):
|
||||||
self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
|
self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_functions")
|
||||||
def test_translate(self):
|
def test_translate(self):
|
||||||
"""
|
"""
|
||||||
Testing GeoQuerySet.translate() on Z values.
|
Testing GeoQuerySet.translate() on Z values.
|
||||||
|
|
|
@ -14,3 +14,11 @@ class AllOGRFields(models.Model):
|
||||||
point = models.PointField()
|
point = models.PointField()
|
||||||
|
|
||||||
objects = models.GeoManager()
|
objects = models.GeoManager()
|
||||||
|
|
||||||
|
|
||||||
|
class Fields3D(models.Model):
|
||||||
|
point = models.PointField(dim=3)
|
||||||
|
line = models.LineStringField(dim=3)
|
||||||
|
poly = models.PolygonField(dim=3)
|
||||||
|
|
||||||
|
objects = models.GeoManager()
|
||||||
|
|
|
@ -27,9 +27,11 @@ class InspectDbTests(TestCase):
|
||||||
Test the geo-enabled inspectdb command.
|
Test the geo-enabled inspectdb command.
|
||||||
"""
|
"""
|
||||||
out = StringIO()
|
out = StringIO()
|
||||||
call_command('inspectdb',
|
call_command(
|
||||||
table_name_filter=lambda tn: tn.startswith('inspectapp_'),
|
'inspectdb',
|
||||||
stdout=out)
|
table_name_filter=lambda tn: tn == 'inspectapp_allogrfields',
|
||||||
|
stdout=out
|
||||||
|
)
|
||||||
output = out.getvalue()
|
output = out.getvalue()
|
||||||
if connection.features.supports_geometry_field_introspection:
|
if connection.features.supports_geometry_field_introspection:
|
||||||
self.assertIn('geom = models.PolygonField()', output)
|
self.assertIn('geom = models.PolygonField()', output)
|
||||||
|
@ -39,6 +41,25 @@ class InspectDbTests(TestCase):
|
||||||
self.assertIn('point = models.GeometryField(', output)
|
self.assertIn('point = models.GeometryField(', output)
|
||||||
self.assertIn('objects = models.GeoManager()', output)
|
self.assertIn('objects = models.GeoManager()', output)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature("supports_3d_storage")
|
||||||
|
def test_3d_columns(self):
|
||||||
|
out = StringIO()
|
||||||
|
call_command(
|
||||||
|
'inspectdb',
|
||||||
|
table_name_filter=lambda tn: tn == 'inspectapp_fields3d',
|
||||||
|
stdout=out
|
||||||
|
)
|
||||||
|
output = out.getvalue()
|
||||||
|
if connection.features.supports_geometry_field_introspection:
|
||||||
|
self.assertIn('point = models.PointField(dim=3)', output)
|
||||||
|
self.assertIn('line = models.LineStringField(dim=3)', output)
|
||||||
|
self.assertIn('poly = models.PolygonField(dim=3)', output)
|
||||||
|
else:
|
||||||
|
self.assertIn('point = models.GeometryField(', output)
|
||||||
|
self.assertIn('line = models.GeometryField(', output)
|
||||||
|
self.assertIn('poly = models.GeometryField(', output)
|
||||||
|
self.assertIn('objects = models.GeoManager()', output)
|
||||||
|
|
||||||
|
|
||||||
@skipUnless(HAS_GDAL, "OGRInspectTest needs GDAL support")
|
@skipUnless(HAS_GDAL, "OGRInspectTest needs GDAL support")
|
||||||
@skipUnlessDBFeature("gis_enabled")
|
@skipUnlessDBFeature("gis_enabled")
|
||||||
|
|
Loading…
Reference in New Issue