From d88c9ac6449c272d8df0023fc0736b9794c43fed Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 13 Feb 2008 00:38:36 +0000 Subject: [PATCH] gis: gdal: Added support for GeoJSON input/output in `OGRGeometry`. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7107 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/gis/gdal/geometries.py | 52 +++++++++++++++------- django/contrib/gis/gdal/libgdal.py | 15 ++++--- django/contrib/gis/gdal/prototypes/geom.py | 18 +++++++- django/contrib/gis/tests/geometries.py | 3 ++ django/contrib/gis/tests/test_gdal_geom.py | 10 +++++ 5 files changed, 73 insertions(+), 25 deletions(-) diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 37bb65d3fa..7c01ce10e9 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -42,7 +42,7 @@ import re, sys from binascii import a2b_hex from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p -from types import BufferType, IntType, StringType, UnicodeType +from types import UnicodeType # Getting GDAL prerequisites from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope @@ -62,6 +62,7 @@ from django.contrib.gis.gdal.prototypes.srs import clone_srs # Regular expressions for recognizing HEXEWKB and WKT. hex_regex = re.compile(r'^[0-9A-F]+$', re.I) wkt_regex = re.compile(r'^(?PPOINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+$', re.I) +json_regex = re.compile(r'^\{.+\}$') #### OGRGeometry Class #### class OGRGeometry(object): @@ -71,32 +72,41 @@ class OGRGeometry(object): "Initializes Geometry on either WKT or an OGR pointer as input." self._ptr = c_void_p(None) # Initially NULL - - # Checking if unicode - if isinstance(geom_input, UnicodeType): - # Encoding to ASCII, WKT or HEX doesn't need any more. - geo_input = geo_input.encode('ascii') + str_instance = isinstance(geom_input, basestring) # If HEX, unpack input to to a binary buffer. - if isinstance(geom_input, StringType) and hex_regex.match(geom_input): + if str_instance and hex_regex.match(geom_input): geom_input = buffer(a2b_hex(geom_input.upper())) + str_instance = False - if isinstance(geom_input, StringType): - m = wkt_regex.match(geom_input) - if m: - if m.group('type').upper() == 'LINEARRING': + # Constructing the geometry, + if str_instance: + # Checking if unicode + if isinstance(geom_input, UnicodeType): + # Encoding to ASCII, WKT or HEX doesn't need any more. + geo_input = geo_input.encode('ascii') + + wkt_m = wkt_regex.match(geom_input) + json_m = json_regex.match(geom_input) + if wkt_m: + 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 = create_geom(OGRGeomType(m.group('type')).num) + g = create_geom(OGRGeomType(wkt_m.group('type')).num) import_wkt(g, byref(c_char_p(geom_input))) else: g = from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) + elif json_m: + if GEOJSON: + g = 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 = create_geom(OGRGeomType(geom_input).num) - elif isinstance(geom_input, BufferType): + elif isinstance(geom_input, buffer): # WKB was passed in g = from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input)) elif isinstance(geom_input, OGRGeomType): @@ -223,7 +233,7 @@ class OGRGeometry(object): "Sets the SpatialReference for this geometry." if isinstance(srs, SpatialReference): srs_ptr = clone_srs(srs._ptr) - elif isinstance(srs, (StringType, UnicodeType, IntType)): + elif isinstance(srs, (int, long, basestring)): sr = SpatialReference(srs) srs_ptr = clone_srs(sr._ptr) else: @@ -238,7 +248,7 @@ class OGRGeometry(object): else: return None def set_srid(self, srid): - if isinstance(srid, IntType): + if isinstance(srid, (int, long)): self.srs = srid else: raise TypeError('SRID must be set with an integer.') @@ -263,6 +273,14 @@ class OGRGeometry(object): return str(self.wkb).encode('hex').upper() #return b2a_hex(self.wkb).upper() + @property + def json(self): + if GEOJSON: + return to_json(self._ptr) + else: + raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') + geojson = json + @property def wkb_size(self): "Returns the size of the WKB buffer." @@ -310,7 +328,7 @@ class OGRGeometry(object): geom_transform(self._ptr, coord_trans._ptr) elif isinstance(coord_trans, SpatialReference): geom_transform_to(self._ptr, coord_trans._ptr) - elif isinstance(coord_trans, (int, basestring)): + elif isinstance(coord_trans, (int, long, basestring)): sr = SpatialReference(coord_trans) geom_transform_to(self._ptr, sr._ptr) else: @@ -542,7 +560,7 @@ class GeometryCollection(OGRGeometry): for g in geom: add_geom(self._ptr, g._ptr) else: add_geom(self._ptr, geom._ptr) - elif isinstance(geom, (StringType, UnicodeType)): + elif isinstance(geom, basestring): tmp = OGRGeometry(geom) add_geom(self._ptr, tmp._ptr) else: diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 4330209f52..28d2d49907 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -1,5 +1,5 @@ import os, sys -from ctypes import CDLL, string_at +from ctypes import c_char_p, CDLL from ctypes.util import find_library from django.contrib.gis.gdal.error import OGRException @@ -48,10 +48,11 @@ def std_call(func): return lgdal[func] #### Version-information functions. #### -def _version_info(key): - "Returns GDAL library version information with the given key." - buf = lgdal.GDALVersionInfo(key) - if buf: return string_at(buf) + +# Returns GDAL library version information with the given key. +_version_info = lgdal.GDALVersionInfo +_version_info.argtypes = [c_char_p] +_version_info.restype = c_char_p def gdal_version(): "Returns only the GDAL version number information." @@ -67,9 +68,9 @@ def gdal_release_date(date=False): If the date keyword argument is set to True, a Python datetime object will be returned instead. """ - from datetime import datetime + 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 = datetime(yy, mm, dd) + d = date_type(yy, mm, dd) if date: return d else: return d.strftime('%Y/%m/%d') diff --git a/django/contrib/gis/gdal/prototypes/geom.py b/django/contrib/gis/gdal/prototypes/geom.py index dd449ae024..6ec55ec938 100644 --- a/django/contrib/gis/gdal/prototypes/geom.py +++ b/django/contrib/gis/gdal/prototypes/geom.py @@ -1,11 +1,19 @@ +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 +from django.contrib.gis.gdal.libgdal import lgdal, gdal_version 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 +# Some prototypes need to be aware of what version GDAL we have. +major, minor1, minor2 = map(int, gdal_version().split('.')) +if major <= 1 and minor1 <= 4: + GEOJSON = False +else: + GEOJSON = True + ### Generation routines specific to this module ### def env_func(f, argtypes): "For getting OGREnvelopes." @@ -26,6 +34,14 @@ def topology_func(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) +else: + from_json = False + to_json = False + # GetX, GetY, GetZ all return doubles. getx = pnt_func(lgdal.OGR_G_GetX) gety = pnt_func(lgdal.OGR_G_GetY) diff --git a/django/contrib/gis/tests/geometries.py b/django/contrib/gis/tests/geometries.py index ab2ecea3bf..0001a765a1 100644 --- a/django/contrib/gis/tests/geometries.py +++ b/django/contrib/gis/tests/geometries.py @@ -151,3 +151,6 @@ buffer_geoms = ( (TestGeom('POINT(0 0)'), 2.0, 8), ) +json_geoms = (TestGeom('POINT(100 0)', json='{ "type": "Point", "coordinates": [ 100.000000, 0.000000 ] }'), + TestGeom('MULTIPOLYGON(((102 2, 103 2, 103 3, 102 3, 102 2)), ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))', json='{ "type": "MultiPolygon", "coordinates": [ [ [ [ 102.000000, 2.000000 ], [ 103.000000, 2.000000 ], [ 103.000000, 3.000000 ], [ 102.000000, 3.000000 ], [ 102.000000, 2.000000 ] ] ], [ [ [ 100.000000, 0.000000 ], [ 101.000000, 0.000000 ], [ 101.000000, 1.000000 ], [ 100.000000, 1.000000 ], [ 100.000000, 0.000000 ] ], [ [ 100.200000, 0.200000 ], [ 100.800000, 0.200000 ], [ 100.800000, 0.800000 ], [ 100.200000, 0.800000 ], [ 100.200000, 0.200000 ] ] ] ] }'), + ) diff --git a/django/contrib/gis/tests/test_gdal_geom.py b/django/contrib/gis/tests/test_gdal_geom.py index 06c2eb769e..9e52a7ce57 100644 --- a/django/contrib/gis/tests/test_gdal_geom.py +++ b/django/contrib/gis/tests/test_gdal_geom.py @@ -63,6 +63,16 @@ class OGRGeomTest(unittest.TestCase): 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 json_geoms: + geom = OGRGeometry(g.wkt) + 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."