From 8648cf78cd42fddf7279477e44c09da4c1937617 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Mon, 25 Jun 2007 01:29:01 +0000 Subject: [PATCH] gis: added the Envelope class, used in getting layer and geometry extents; also added boundary and convex_hull properties. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5527 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/Envelope.py | 112 +++++++++++++++++++++++ django/contrib/gis/gdal/Layer.py | 12 ++- django/contrib/gis/gdal/OGRGeometry.py | 28 +++++- django/contrib/gis/gdal/__init__.py | 1 + django/contrib/gis/tests/test_gdal_ds.py | 16 +++- 5 files changed, 160 insertions(+), 9 deletions(-) create mode 100644 django/contrib/gis/gdal/Envelope.py diff --git a/django/contrib/gis/gdal/Envelope.py b/django/contrib/gis/gdal/Envelope.py new file mode 100644 index 0000000000..1a7ff80529 --- /dev/null +++ b/django/contrib/gis/gdal/Envelope.py @@ -0,0 +1,112 @@ +from ctypes import Structure, c_double +from types import TupleType + +""" + 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----------+ + +""" + +# 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): + "A class that will wrap an OGR Envelope structure." + + def __init__(self, *args): + if len(args) == 1: + if isinstance(args[0], OGREnvelope): + # OGREnvelope (a ctypes Structure) was passed in. + self._envelope = args[0] + elif isinstance(args[0], TupleType) and len(args[0]) == 4: + # A Tuple was passed in + self._from_tuple(args[0]) + else: + raise OGRException, 'Incorrect type of argument: %s' % str(type(args[0])) + elif len(args) == 4: + self._from_tuple(args) + else: + raise OGRException, 'Incorrect number of arguments!' + + 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, TupleType) 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_tuple(self, tup): + "Initializes the C OGR Envelope structure from the given tuple." + self._envelope = OGREnvelope() + self._envelope.MinX = tup[0] + self._envelope.MinY = tup[1] + self._envelope.MaxX = tup[2] + self._envelope.MaxY = tup[3] + + @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((%f %f,%f %f,%f %f,%f %f,%f %f))' % (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/django/contrib/gis/gdal/Layer.py b/django/contrib/gis/gdal/Layer.py index f7e46bcb2d..0ea3560a3c 100644 --- a/django/contrib/gis/gdal/Layer.py +++ b/django/contrib/gis/gdal/Layer.py @@ -1,12 +1,13 @@ # Needed ctypes routines -from ctypes import c_int, c_long, c_void_p, string_at +from ctypes import c_int, c_long, c_void_p, byref, string_at # The GDAL C Library from django.contrib.gis.gdal.libgdal import lgdal # Other GDAL imports. +from django.contrib.gis.gdal.Envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.Feature import Feature -from django.contrib.gis.gdal.OGRError import OGRException +from django.contrib.gis.gdal.OGRError import OGRException, check_err from django.contrib.gis.gdal.SpatialReference import SpatialReference # For more information, see the OGR C API source code: @@ -57,6 +58,13 @@ class Layer(object): return self.name #### Layer properties #### + @property + def extent(self): + "Returns the extent (an Envelope) of this layer." + env = OGREnvelope() + check_err(lgdal.OGR_L_GetExtent(self._layer, byref(env), c_int(1))) + return Envelope(env) + @property def name(self): "Returns the name of this layer in the Data Source." diff --git a/django/contrib/gis/gdal/OGRGeometry.py b/django/contrib/gis/gdal/OGRGeometry.py index d1b334ec43..4296989036 100644 --- a/django/contrib/gis/gdal/OGRGeometry.py +++ b/django/contrib/gis/gdal/OGRGeometry.py @@ -4,6 +4,7 @@ from ctypes import byref, string_at, c_char_p, c_double, c_int, c_void_p # Getting the GDAL C library and error checking facilities from django.contrib.gis.gdal.libgdal import lgdal +from django.contrib.gis.gdal.Envelope import Envelope, OGREnvelope from django.contrib.gis.gdal.OGRError import check_err, OGRException from django.contrib.gis.gdal.SpatialReference import SpatialReference, CoordTransform @@ -248,6 +249,13 @@ class OGRGeometry(object): "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise." a = lgdal.OGR_G_GetArea(self._g) return a.value + + @property + def envelope(self): + "Returns the envelope for this Geometry." + env = OGREnvelope() + lgdal.OGR_G_GetEnvelope(self._g, byref(env)) + return Envelope(env) #### Geometry Methods #### def clone(self): @@ -322,10 +330,22 @@ class OGRGeometry(object): return self._topology(lgdal.OGR_G_Overlaps, other) #### Geometry-generation Methods #### - def _geomgen(self, gen_func, other): - if not isinstance(other, OGRGeometry): - raise OGRException, 'Must use another OGRGeometry object for geometry-generating operations!' - return OGRGeometry(gen_func(self._g, other._g)) + 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._g, other._g)) + else: + return OGRGeometry(gen_func(self._g)) + + @property + def boundary(self): + "Returns the boundary of this geometry." + return self._geomgen(lgdal.OGR_G_GetBoundary) + + @property + def convex_hull(self): + "Returns the smallest convex Polygon that contains all the points in the Geometry." + return self._geomgen(lgdal.OGR_G_ConvexHull) def union(self, other): """Returns a new geometry consisting of the region which is the union of diff --git a/django/contrib/gis/gdal/__init__.py b/django/contrib/gis/gdal/__init__.py index 891a60675b..180dc558ac 100644 --- a/django/contrib/gis/gdal/__init__.py +++ b/django/contrib/gis/gdal/__init__.py @@ -1,4 +1,5 @@ from Driver import Driver +from Envelope import Envelope from DataSource import DataSource from SpatialReference import SpatialReference, CoordTransform from OGRGeometry import OGRGeometry, OGRGeomType diff --git a/django/contrib/gis/tests/test_gdal_ds.py b/django/contrib/gis/tests/test_gdal_ds.py index 658f2c1935..6e6964c224 100644 --- a/django/contrib/gis/tests/test_gdal_ds.py +++ b/django/contrib/gis/tests/test_gdal_ds.py @@ -1,5 +1,6 @@ import os, os.path, unittest from django.contrib.gis.gdal import DataSource, OGRException +from django.contrib.gis.gdal.Envelope import Envelope from django.contrib.gis.gdal.Field import OFTReal, OFTInteger, OFTString # Path for SHP files @@ -16,8 +17,10 @@ class TestSHP: # List of acceptable data sources. ds_list = (TestSHP('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, fields={'dbl' : OFTReal, 'int' : OFTReal, '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]]'), TestSHP('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, fields={'float' : OFTReal, 'int' : OFTReal, '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]]'), ) @@ -67,8 +70,15 @@ class DataSourceTest(unittest.TestCase): self.assertEqual(len(layer), source.nfeat) # Making sure we get the number of fields we expect - self.assertEqual(layer.num_fields, source.nfld) - self.assertEqual(len(layer.fields), source.nfld) + 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 + 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 @@ -113,7 +123,7 @@ class DataSourceTest(unittest.TestCase): # Making sure the SpatialReference is as expected. self.assertEqual(source.srs_wkt, g.srs.wkt) - + def suite(): s = unittest.TestSuite() s.addTest(unittest.makeSuite(DataSourceTest))