Author: jbronn
Date: 2007-09-17 07:09:54 -0500 (Mon, 17 Sep 2007)
New Revision: 6369
Added:
django/branches/gis/django/contrib/gis/utils/geoip.py
django/branches/gis/django/contrib/gis/utils/layermapping.py
Removed:
django/branches/gis/django/contrib/gis/utils/LayerMapping.py
Modified:
django/branches/gis/django/contrib/gis/utils/__init__.py
Log:
gis: Added the GeoIP utility for IP-based geolocation; got rid of camel case
and fixed relative imports in utils module.
Deleted: django/branches/gis/django/contrib/gis/utils/LayerMapping.py
===================================================================
--- django/branches/gis/django/contrib/gis/utils/LayerMapping.py
2007-09-17 04:54:53 UTC (rev 6368)
+++ django/branches/gis/django/contrib/gis/utils/LayerMapping.py
2007-09-17 12:09:54 UTC (rev 6369)
@@ -1,340 +0,0 @@
-# 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.
-
-This grew out of my personal needs, specifically the code repetition
- that went into pulling geometries and fields out of an OGR layer,
- converting to another coordinate system (e.g. WGS84), and then inserting
- into a Geographic Django model.
-
-This utility is still in early stages of development, so its usage
- is subject to change -- please report any bugs.
-
-TODO: Unit tests and documentation.
-
-Requirements: OGR C Library (from GDAL) required.
-
-Usage:
- lm = LayerMapping(model, source_file, mapping) where,
-
- model -- GeoDjango model (not an instance)
-
- data -- OGR-supported data source file (e.g. a shapefile) or
- gdal.DataSource instance
-
- mapping -- A python dictionary, keys are strings corresponding
- to the GeoDjango model field, and values correspond to
- string field names for the OGR feature, or if the model field
- is a geographic then it should correspond to the OGR
- geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
-
-Keyword Args:
- layer -- The index of the layer to use from the Data Source (defaults to 0)
-
- source_srs -- Use this to specify the source SRS manually (for example,
- some shapefiles don't come with a '.prj' file)
-
-Example:
-
- 1. You need a GDAL-supported data source, like a shapefile.
-
- Assume we're using the test_poly SHP file:
- >>> from django.contrib.gis.gdal import DataSource
- >>> ds = DataSource('test_poly.shp')
- >>> layer = ds[0]
- >>> print layer.fields # Exploring the fields in the layer, we only want the
'str' field.
- ['float', 'int', 'str']
- >>> print len(layer) # getting the number of features in the layer (should
be 3)
- 3
- >>> print layer.geom_type # Should be 3 (a Polygon)
- 3
- >>> print layer.srs # WGS84
- GEOGCS["GCS_WGS_1984",
- DATUM["WGS_1984",
- SPHEROID["WGS_1984",6378137,298.257223563]],
- PRIMEM["Greenwich",0],
- UNIT["Degree",0.017453292519943295]]
-
- 2. Now we define our corresponding Django model (make sure to use syncdb):
-
- from django.contrib.gis.db import models
- class TestGeo(models.Model, models.GeoMixin):
- name = models.CharField(maxlength=25) # corresponds to the 'str' field
- poly = models.PolygonField(srid=4269) # we want our model in a different
SRID
- objects = models.GeoManager()
- def __str__(self):
- return 'Name: %s' % self.name
-
- 3. Use LayerMapping to extract all the features and place them in the
database:
-
- >>> from django.contrib.gis.utils import LayerMapping
- >>> from geoapp.models import TestGeo
- >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str'
layer field.
- 'poly' : 'POLYGON', # For geometry fields use OGC name.
- } # The mapping is a dictionary
- >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
- >>> lm.save(verbose=True) # Save the layermap, imports the data.
- Saved: Name: 1
- Saved: Name: 2
- Saved: Name: 3
-
- LayerMapping just transformed the three geometries from the SHP file from
their
- source spatial reference system (WGS84) to the spatial reference system of
- the GeoDjango model (NAD83). If no spatial reference system is defined for
- the layer, use the `source_srs` keyword with a SpatialReference object to
- specify one. Further, data is selectively imported from the given data
source
- fields into the model fields.
-"""
-from types import StringType, TupleType
-from datetime import datetime
-from django.contrib.gis.gdal import \
- OGRGeometry, OGRGeomType, SpatialReference, CoordTransform, \
- DataSource, OGRException
-from django.contrib.gis.gdal.field import Field, OFTInteger, OFTReal,
OFTString, OFTDateTime
-from django.contrib.gis.models import GeometryColumns, SpatialRefSys
-from django.db import connection, transaction
-from django.core.exceptions import ObjectDoesNotExist
-
-# A mapping of given geometry types to their OGR integer type.
-ogc_types = {'POINT' : OGRGeomType('Point'),
- 'LINESTRING' : OGRGeomType('LineString'),
- 'POLYGON' : OGRGeomType('Polygon'),
- 'MULTIPOINT' : OGRGeomType('MultiPoint'),
- 'MULTILINESTRING' : OGRGeomType('MultiLineString'),
- 'MULTIPOLYGON' : OGRGeomType('MultiPolygon'),
- 'GEOMETRYCOLLECTION' : OGRGeomType('GeometryCollection'),
- }
-
-# The django.contrib.gis model types.
-gis_fields = {'PointField' : 'POINT',
- 'LineStringField': 'LINESTRING',
- 'PolygonField': 'POLYGON',
- 'MultiPointField' : 'MULTIPOINT',
- 'MultiLineStringField' : 'MULTILINESTRING',
- 'MultiPolygonField' : 'MULTIPOLYGON',
- }
-
-# Acceptable 'base' types for a multi-geometry type.
-multi_types = {'POINT' : OGRGeomType('MultiPoint'),
- 'LINESTRING' : OGRGeomType('MultiLineString'),
- 'POLYGON' : OGRGeomType('MultiPolygon'),
- }
-
-def map_foreign_key(django_field):
- from django.db.models.fields.related import ForeignKey
-
- if not django_field.__class__ is ForeignKey:
- return django_field.__class__.__name__
-
-
- rf=django_field.rel.get_related_field()
-
- return rf.get_internal_type()
-
-# The acceptable Django field types that map to OGR fields.
-field_types = {
- 'AutoField' : OFTInteger,
- 'IntegerField' : OFTInteger,
- 'FloatField' : OFTReal,
- 'DateTimeField' : OFTDateTime,
- 'DecimalField' : OFTReal,
- 'CharField' : OFTString,
- 'SmallIntegerField' : OFTInteger,
- 'PositiveSmallIntegerField' : OFTInteger,
- }
-
-def make_multi(geom_name, model_type):
- "Determines whether the geometry should be made into a GeometryCollection."
- if (geom_name in multi_types) and (model_type.startswith('Multi')):
- return True
- else:
- return False
-
-def check_feature(feat, model_fields, mapping):
- "Checks the OGR layer feature."
-
- HAS_GEO = False
-
- # Incrementing through each model_field & ogr_field in the given mapping.
- for model_field, ogr_field in mapping.items():
-
- # Making sure the given mapping model field is in the given model
fields.
- if model_field in model_fields:
- model_type = model_fields[model_field]
- elif model_field[:-3] in model_fields: #foreign key
- model_type = model_fields[model_field[:-3]]
- else:
- raise Exception, 'Given mapping field "%s" not in given Model
fields!' % model_field
-
- ## Handling if we get a geometry in the Field ###
- if ogr_field in ogc_types:
- # At this time, no more than one geographic field per model =(
- if HAS_GEO:
- raise Exception, 'More than one geographic field in mapping
not allowed (yet).'
- else:
- HAS_GEO = ogr_field
-
- # Making sure this geometry field type is a valid Django GIS field.
- if not model_type in gis_fields:
- raise Exception, 'Unknown Django GIS field type "%s"' %
model_type
-
- # Getting the OGRGeometry, it's type (an integer) and it's name (a
string)
- geom = feat.geom
- gtype = geom.geom_type
- gname = geom.geom_name
-
- if make_multi(gname, model_type):
- # Do we have to 'upsample' into a Geometry Collection?
- pass
- elif gtype == ogc_types[gis_fields[model_type]]:
- # The geometry type otherwise was expected
- pass
- else:
- raise Exception, 'Invalid mapping geometry; model has %s,
feature has %s' % (model_type, gtype)
-
- ## Handling other fields
- else:
- # Making sure the model field is
- if not model_type in field_types:
- raise Exception, 'Django field type "%s" has no OGR mapping
(yet).' % model_type
-
- # Otherwise, we've got an OGR Field. Making sure that an
- # index exists for the mapping OGR field.
- try:
- fi = feat.index(ogr_field)
- except:
- raise Exception, 'Given mapping OGR field "%s" not in given
OGR layer feature!' % ogr_field
-
-def check_layer(layer, fields, mapping):
- "Checks the OGR layer by incrementing through and checking each feature."
- # Incrementing through each feature in the layer.
- for feat in layer:
- check_feature(feat, fields, mapping)
-
-def check_srs(layer, source_srs):
- "Checks the compatibility of the given spatial reference object."
- if isinstance(source_srs, SpatialReference):
- sr = source_srs
- elif isinstance(source_srs, SpatialRefSys):
- sr = source_srs.srs
- else:
- sr = layer.srs
- if not sr:
- raise Exception, 'No source reference system defined.'
- else:
- return sr
-
-class LayerMapping:
- "A class that maps OGR Layers to Django Models."
-
- def __init__(self, model, data, mapping, layer=0, source_srs=None):
- "Takes the Django model, the data source, and the mapping (dictionary)"
-
- # Getting the field names and types from the model
- fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)
- # Getting the DataSource and its Layer
- if isinstance(data, basestring):
- self.ds = DataSource(data)
- else:
- self.ds = data
- self.layer = self.ds[layer]
-
- # Checking the layer -- intitialization of the object will fail if
- # things don't check out before hand.
- check_layer(self.layer, fields, mapping)
-
- # Since the layer checked out, setting the fields and the mapping.
- self.fields = fields
- self.mapping = mapping
- self.model = model
- self.source_srs = check_srs(self.layer, source_srs)
-
- @transaction.commit_on_success
- def save(self, verbose=False):
- "Runs the layer mapping on the given SHP file, and saves to the
database."
-
- # Getting the GeometryColumn object.
- try:
- geo_col =
GeometryColumns.objects.get(f_table_name=self.model._meta.db_table)
- except:
- raise Exception, 'Geometry column does not exist. (did you run
syncdb?)'
-
- # Getting the coordinate system needed for transformation (with
CoordTransform)
- try:
- # Getting the target spatial reference system
- target_srs = SpatialRefSys.objects.get(srid=geo_col.srid).srs
-
- # Creating the CoordTransform object
- ct = CoordTransform(self.source_srs, target_srs)
- except Exception, msg:
- raise Exception, 'Could not translate between the data source and
model geometry: %s' % msg
-
- for feat in self.layer:
- # The keyword arguments for model construction
- kwargs = {}
-
- # Incrementing through each model field and the OGR field in the
mapping
- all_prepped = True
-
- for model_field, ogr_field in self.mapping.items():
- is_fk = False
- try:
- model_type = self.fields[model_field]
- except KeyError: #foreign key
- model_type = self.fields[model_field[:-3]]
- is_fk = True
-
- if ogr_field in ogc_types:
- ## Getting the OGR geometry from the field
- geom = feat.geom
-
- if make_multi(geom.geom_name, model_type):
- # Constructing a multi-geometry type to contain the
single geometry
- multi_type = multi_types[geom.geom_name]
- g = OGRGeometry(multi_type)
- g.add(geom)
- else:
- g = geom
-
- # Transforming the geometry with our Coordinate
Transformation object.
- g.transform(ct)
-
- # Updating the keyword args with the WKT of the
transformed model.
- val = g.wkt
- else:
- ## Otherwise, this is an OGR field type
- fi = feat.index(ogr_field)
- val = feat[fi].value
-
- if is_fk:
- rel_obj = None
- field_name = model_field[:-3]
- try:
- #FIXME: refactor to efficiently fetch FKs.
- # Requires significant re-work. :-/
- rel = self.model._meta.get_field(field_name).rel
- rel_obj = rel.to._default_manager.get(**{('%s__exact'
% rel.field_name):val})
- except ObjectDoesNotExist:
- all_prepped = False
-
- kwargs[model_field[:-3]] = rel_obj
- else:
- kwargs[model_field] = val
-
- # Constructing the model using the constructed keyword args
- if all_prepped:
- m = self.model(**kwargs)
-
- # Saving the model
- try:
- if all_prepped:
- m.save()
- if verbose: print 'Saved: %s' % str(m)
- else:
- print "Skipping %s due to missing relation." % kwargs
- except SystemExit:
- raise
- except Exception, e:
- print "Failed to save %s\n Continuing" % kwargs
-
Modified: django/branches/gis/django/contrib/gis/utils/__init__.py
===================================================================
--- django/branches/gis/django/contrib/gis/utils/__init__.py 2007-09-17
04:54:53 UTC (rev 6368)
+++ django/branches/gis/django/contrib/gis/utils/__init__.py 2007-09-17
12:09:54 UTC (rev 6369)
@@ -1,3 +1,10 @@
-from LayerMapping import LayerMapping
-from inspect_data import sample
+from django.contrib.gis.utils.layermapping import LayerMapping
+from django.contrib.gis.utils.inspect_data import sample
+from django.contrib.gis.utils.shortcuts import render_to_kml
+# Importing GeoIP
+try:
+ from django.contrib.gis.utils.geoip import GeoIP
+ HAS_GEOIP = True
+except:
+ HAS_GEOIP = False
Added: django/branches/gis/django/contrib/gis/utils/geoip.py
===================================================================
--- django/branches/gis/django/contrib/gis/utils/geoip.py
(rev 0)
+++ django/branches/gis/django/contrib/gis/utils/geoip.py 2007-09-17
12:09:54 UTC (rev 6369)
@@ -0,0 +1,280 @@
+"""
+ This module houses the GeoIP object, a ctypes wrapper for the MaxMind GeoIP(R)
+ C API (http://www.maxmind.com/app/c).
+
+ 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, string_at, Structure, CDLL,
POINTER
+from django.conf import settings
+
+# The Exception class for GeoIP Errors.
+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 os.name == 'nt':
+ ext = '.dll'
+elif os.name == 'posix':
+ platform = os.uname()[0]
+ if platform in ('Linux', 'SunOS'):
+ ext = '.so'
+ elif platofm == 'Darwin':
+ ext = '.dylib'
+ else:
+ raise GeoIPException('Unknown POSIX platform "%s"' % platform)
+lgeoip = CDLL('libGeoIP' + ext)
+
+# A regular expression for recognizing IP addresses
+ipregex =
re.compile(r'^(?P<w>\d\d?\d?)\.(?P<x>\d\d?\d?)\.(?P<y>\d\d?\d?)\.(?P<z>\d\d?\d?)$')
+
+# 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
+#
+cache_options = {0 : c_int(0), # GEOIP_STANDARD
+ 1 : c_int(1), # GEOIP_MEMORY_CACHE
+ 2 : c_int(2), # GEOIP_CHECK_CACHE
+ 4 : c_int(4), # GEOIP_INDEX_CACHE
+ }
+
+# GeoIPRecord C Structure definition.
+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),
+ ('dma_code', c_int),
+ ('area_code', c_int),
+ ]
+
+# ctypes function prototypes
+record_by_addr = lgeoip.GeoIP_record_by_addr
+record_by_addr.restype = POINTER(GeoIPRecord)
+record_by_name = lgeoip.GeoIP_record_by_name
+record_by_name.restype = POINTER(GeoIPRecord)
+
+# The exception class for GeoIP Errors.
+class GeoIPException(Exception): pass
+
+class GeoIP(object):
+ def __init__(self, path=None, country=None, city=None,
+ cache=0):
+ """
+ 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 where the GeoIP 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.
+
+ * 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.
+
+ * cache: The cache settings when opening up the GeoIP datasets,
+ and may be an integer in (0, 1, 2, 4). Defaults to 0, meaning
+ that the data is read from the disk.
+ """
+
+ # Checking the given cache option.
+ if cache in cache_options:
+ self._cache = cache_options[cache]
+ else:
+ raise GeoIPException('Invalid caching option: %s' % cache)
+
+ # Getting the GeoIP data path.
+ if not path:
+ try:
+ self._path = settings.GEOIP_PATH
+ except AttributeError:
+ raise GeoIPException('Must specify GEOIP_PATH in your
settings.py')
+ else:
+ self._path = path
+ if not os.path.isdir(self._path):
+ raise GeoIPException('GEOIP_PATH must be set to a directory.')
+
+ # Getting the GeoIP country data file.
+ if not country:
+ try:
+ cntry_file = settings.GEOIP_COUNTRY
+ except AttributeError:
+ cntry_file = 'GeoIP.dat'
+ else:
+ cntry_file = country
+ self._country_file = os.path.join(self._path, cntry_file)
+
+ # Getting the GeoIP city data file.
+ if not city:
+ try:
+ city_file = settings.GEOIP_CITY
+ except AttributeError:
+ city_file = 'GeoLiteCity.dat'
+ else:
+ city_file = city
+ self._city_file = os.path.join(self._path, city_file)
+
+ # Opening up the GeoIP country data file.
+ if os.path.isfile(self._country_file):
+ self._country = lgeoip.GeoIP_open(c_char_p(self._country_file),
self._cache)
+ else:
+ self._country = None
+
+ # Opening the GeoIP city data file.
+ if os.path.isfile(self._city_file):
+ self._city = lgeoip.GeoIP_open(c_char_p(self._city_file),
self._cache)
+ else:
+ self._city = None
+
+ 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.
+ """
+ if self._country is None:
+ raise GeoIPException('Invalid GeoIP country data file: %s' %
self._country_file)
+
+ if ipregex.match(query):
+ # If an IP address was passed in.
+ code = lgeoip.GeoIP_country_code_by_addr(self._country,
c_char_p(query))
+ name = lgeoip.GeoIP_country_name_by_addr(self._country,
c_char_p(query))
+ else:
+ # If a FQDN was passed in.
+ code = lgeoip.GeoIP_country_code_by_name(self._country,
c_char_p(query))
+ name = lgeoip.GeoIP_country_name_by_name(self._country,
c_char_p(query))
+
+ # Checking our returned country code and name, setting each to
+ # None, if pointer is invalid.
+ if bool(code): code = string_at(code)
+ else: code = None
+ if bool(name): name = string_at(name)
+ else: name = None
+
+ # Returning the country code and name
+ return {'country_code' : code,
+ 'country_name' : name,
+ }
+
+ 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).
+ """
+ if self._city is None:
+ raise GeoIPException('Invalid GeoIP country data file: %s' %
self._city_file)
+
+ if ipregex.match(query):
+ # If an IP address was passed in
+ ptr = record_by_addr(self._city, c_char_p(query))
+ else:
+ # If a FQDN was passed in.
+ ptr = record_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
+
+ #### Coordinate retrieval routines ####
+ def _coords(self, query, ordering):
+ 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, ('longitude', 'latitude'))
+
+ 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 = string_at(lgeoip.GeoIP_database_info(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 = string_at(lgeoip.GeoIP_database_info(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)
Copied: django/branches/gis/django/contrib/gis/utils/layermapping.py (from rev
6302, django/branches/gis/django/contrib/gis/utils/LayerMapping.py)
===================================================================
--- django/branches/gis/django/contrib/gis/utils/layermapping.py
(rev 0)
+++ django/branches/gis/django/contrib/gis/utils/layermapping.py
2007-09-17 12:09:54 UTC (rev 6369)
@@ -0,0 +1,340 @@
+# 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.
+
+This grew out of my personal needs, specifically the code repetition
+ that went into pulling geometries and fields out of an OGR layer,
+ converting to another coordinate system (e.g. WGS84), and then inserting
+ into a Geographic Django model.
+
+This utility is still in early stages of development, so its usage
+ is subject to change -- please report any bugs.
+
+TODO: Unit tests and documentation.
+
+Requirements: OGR C Library (from GDAL) required.
+
+Usage:
+ lm = LayerMapping(model, source_file, mapping) where,
+
+ model -- GeoDjango model (not an instance)
+
+ data -- OGR-supported data source file (e.g. a shapefile) or
+ gdal.DataSource instance
+
+ mapping -- A python dictionary, keys are strings corresponding
+ to the GeoDjango model field, and values correspond to
+ string field names for the OGR feature, or if the model field
+ is a geographic then it should correspond to the OGR
+ geometry type, e.g. 'POINT', 'LINESTRING', 'POLYGON'.
+
+Keyword Args:
+ layer -- The index of the layer to use from the Data Source (defaults to 0)
+
+ source_srs -- Use this to specify the source SRS manually (for example,
+ some shapefiles don't come with a '.prj' file)
+
+Example:
+
+ 1. You need a GDAL-supported data source, like a shapefile.
+
+ Assume we're using the test_poly SHP file:
+ >>> from django.contrib.gis.gdal import DataSource
+ >>> ds = DataSource('test_poly.shp')
+ >>> layer = ds[0]
+ >>> print layer.fields # Exploring the fields in the layer, we only want the
'str' field.
+ ['float', 'int', 'str']
+ >>> print len(layer) # getting the number of features in the layer (should
be 3)
+ 3
+ >>> print layer.geom_type # Should be 3 (a Polygon)
+ 3
+ >>> print layer.srs # WGS84
+ GEOGCS["GCS_WGS_1984",
+ DATUM["WGS_1984",
+ SPHEROID["WGS_1984",6378137,298.257223563]],
+ PRIMEM["Greenwich",0],
+ UNIT["Degree",0.017453292519943295]]
+
+ 2. Now we define our corresponding Django model (make sure to use syncdb):
+
+ from django.contrib.gis.db import models
+ class TestGeo(models.Model, models.GeoMixin):
+ name = models.CharField(maxlength=25) # corresponds to the 'str' field
+ poly = models.PolygonField(srid=4269) # we want our model in a different
SRID
+ objects = models.GeoManager()
+ def __str__(self):
+ return 'Name: %s' % self.name
+
+ 3. Use LayerMapping to extract all the features and place them in the
database:
+
+ >>> from django.contrib.gis.utils import LayerMapping
+ >>> from geoapp.models import TestGeo
+ >>> mapping = {'name' : 'str', # The 'name' model field maps to the 'str'
layer field.
+ 'poly' : 'POLYGON', # For geometry fields use OGC name.
+ } # The mapping is a dictionary
+ >>> lm = LayerMapping(TestGeo, 'test_poly.shp', mapping)
+ >>> lm.save(verbose=True) # Save the layermap, imports the data.
+ Saved: Name: 1
+ Saved: Name: 2
+ Saved: Name: 3
+
+ LayerMapping just transformed the three geometries from the SHP file from
their
+ source spatial reference system (WGS84) to the spatial reference system of
+ the GeoDjango model (NAD83). If no spatial reference system is defined for
+ the layer, use the `source_srs` keyword with a SpatialReference object to
+ specify one. Further, data is selectively imported from the given data
source
+ fields into the model fields.
+"""
+from types import StringType, TupleType
+from datetime import datetime
+from django.contrib.gis.gdal import \
+ OGRGeometry, OGRGeomType, SpatialReference, CoordTransform, \
+ DataSource, OGRException
+from django.contrib.gis.gdal.field import Field, OFTInteger, OFTReal,
OFTString, OFTDateTime
+from django.contrib.gis.models import GeometryColumns, SpatialRefSys
+from django.db import connection, transaction
+from django.core.exceptions import ObjectDoesNotExist
+
+# A mapping of given geometry types to their OGR integer type.
+ogc_types = {'POINT' : OGRGeomType('Point'),
+ 'LINESTRING' : OGRGeomType('LineString'),
+ 'POLYGON' : OGRGeomType('Polygon'),
+ 'MULTIPOINT' : OGRGeomType('MultiPoint'),
+ 'MULTILINESTRING' : OGRGeomType('MultiLineString'),
+ 'MULTIPOLYGON' : OGRGeomType('MultiPolygon'),
+ 'GEOMETRYCOLLECTION' : OGRGeomType('GeometryCollection'),
+ }
+
+# The django.contrib.gis model types.
+gis_fields = {'PointField' : 'POINT',
+ 'LineStringField': 'LINESTRING',
+ 'PolygonField': 'POLYGON',
+ 'MultiPointField' : 'MULTIPOINT',
+ 'MultiLineStringField' : 'MULTILINESTRING',
+ 'MultiPolygonField' : 'MULTIPOLYGON',
+ }
+
+# Acceptable 'base' types for a multi-geometry type.
+multi_types = {'POINT' : OGRGeomType('MultiPoint'),
+ 'LINESTRING' : OGRGeomType('MultiLineString'),
+ 'POLYGON' : OGRGeomType('MultiPolygon'),
+ }
+
+def map_foreign_key(django_field):
+ from django.db.models.fields.related import ForeignKey
+
+ if not django_field.__class__ is ForeignKey:
+ return django_field.__class__.__name__
+
+
+ rf=django_field.rel.get_related_field()
+
+ return rf.get_internal_type()
+
+# The acceptable Django field types that map to OGR fields.
+field_types = {
+ 'AutoField' : OFTInteger,
+ 'IntegerField' : OFTInteger,
+ 'FloatField' : OFTReal,
+ 'DateTimeField' : OFTDateTime,
+ 'DecimalField' : OFTReal,
+ 'CharField' : OFTString,
+ 'SmallIntegerField' : OFTInteger,
+ 'PositiveSmallIntegerField' : OFTInteger,
+ }
+
+def make_multi(geom_name, model_type):
+ "Determines whether the geometry should be made into a GeometryCollection."
+ if (geom_name in multi_types) and (model_type.startswith('Multi')):
+ return True
+ else:
+ return False
+
+def check_feature(feat, model_fields, mapping):
+ "Checks the OGR layer feature."
+
+ HAS_GEO = False
+
+ # Incrementing through each model_field & ogr_field in the given mapping.
+ for model_field, ogr_field in mapping.items():
+
+ # Making sure the given mapping model field is in the given model
fields.
+ if model_field in model_fields:
+ model_type = model_fields[model_field]
+ elif model_field[:-3] in model_fields: #foreign key
+ model_type = model_fields[model_field[:-3]]
+ else:
+ raise Exception, 'Given mapping field "%s" not in given Model
fields!' % model_field
+
+ ## Handling if we get a geometry in the Field ###
+ if ogr_field in ogc_types:
+ # At this time, no more than one geographic field per model =(
+ if HAS_GEO:
+ raise Exception, 'More than one geographic field in mapping
not allowed (yet).'
+ else:
+ HAS_GEO = ogr_field
+
+ # Making sure this geometry field type is a valid Django GIS field.
+ if not model_type in gis_fields:
+ raise Exception, 'Unknown Django GIS field type "%s"' %
model_type
+
+ # Getting the OGRGeometry, it's type (an integer) and it's name (a
string)
+ geom = feat.geom
+ gtype = geom.geom_type
+ gname = geom.geom_name
+
+ if make_multi(gname, model_type):
+ # Do we have to 'upsample' into a Geometry Collection?
+ pass
+ elif gtype == ogc_types[gis_fields[model_type]]:
+ # The geometry type otherwise was expected
+ pass
+ else:
+ raise Exception, 'Invalid mapping geometry; model has %s,
feature has %s' % (model_type, gtype)
+
+ ## Handling other fields
+ else:
+ # Making sure the model field is
+ if not model_type in field_types:
+ raise Exception, 'Django field type "%s" has no OGR mapping
(yet).' % model_type
+
+ # Otherwise, we've got an OGR Field. Making sure that an
+ # index exists for the mapping OGR field.
+ try:
+ fi = feat.index(ogr_field)
+ except:
+ raise Exception, 'Given mapping OGR field "%s" not in given
OGR layer feature!' % ogr_field
+
+def check_layer(layer, fields, mapping):
+ "Checks the OGR layer by incrementing through and checking each feature."
+ # Incrementing through each feature in the layer.
+ for feat in layer:
+ check_feature(feat, fields, mapping)
+
+def check_srs(layer, source_srs):
+ "Checks the compatibility of the given spatial reference object."
+ if isinstance(source_srs, SpatialReference):
+ sr = source_srs
+ elif isinstance(source_srs, SpatialRefSys):
+ sr = source_srs.srs
+ else:
+ sr = layer.srs
+ if not sr:
+ raise Exception, 'No source reference system defined.'
+ else:
+ return sr
+
+class LayerMapping:
+ "A class that maps OGR Layers to Django Models."
+
+ def __init__(self, model, data, mapping, layer=0, source_srs=None):
+ "Takes the Django model, the data source, and the mapping (dictionary)"
+
+ # Getting the field names and types from the model
+ fields = dict((f.name, map_foreign_key(f)) for f in model._meta.fields)
+ # Getting the DataSource and its Layer
+ if isinstance(data, basestring):
+ self.ds = DataSource(data)
+ else:
+ self.ds = data
+ self.layer = self.ds[layer]
+
+ # Checking the layer -- intitialization of the object will fail if
+ # things don't check out before hand.
+ check_layer(self.layer, fields, mapping)
+
+ # Since the layer checked out, setting the fields and the mapping.
+ self.fields = fields
+ self.mapping = mapping
+ self.model = model
+ self.source_srs = check_srs(self.layer, source_srs)
+
+ @transaction.commit_on_success
+ def save(self, verbose=False):
+ "Runs the layer mapping on the given SHP file, and saves to the
database."
+
+ # Getting the GeometryColumn object.
+ try:
+ geo_col =
GeometryColumns.objects.get(f_table_name=self.model._meta.db_table)
+ except:
+ raise Exception, 'Geometry column does not exist. (did you run
syncdb?)'
+
+ # Getting the coordinate system needed for transformation (with
CoordTransform)
+ try:
+ # Getting the target spatial reference system
+ target_srs = SpatialRefSys.objects.get(srid=geo_col.srid).srs
+
+ # Creating the CoordTransform object
+ ct = CoordTransform(self.source_srs, target_srs)
+ except Exception, msg:
+ raise Exception, 'Could not translate between the data source and
model geometry: %s' % msg
+
+ for feat in self.layer:
+ # The keyword arguments for model construction
+ kwargs = {}
+
+ # Incrementing through each model field and the OGR field in the
mapping
+ all_prepped = True
+
+ for model_field, ogr_field in self.mapping.items():
+ is_fk = False
+ try:
+ model_type = self.fields[model_field]
+ except KeyError: #foreign key
+ model_type = self.fields[model_field[:-3]]
+ is_fk = True
+
+ if ogr_field in ogc_types:
+ ## Getting the OGR geometry from the field
+ geom = feat.geom
+
+ if make_multi(geom.geom_name, model_type):
+ # Constructing a multi-geometry type to contain the
single geometry
+ multi_type = multi_types[geom.geom_name]
+ g = OGRGeometry(multi_type)
+ g.add(geom)
+ else:
+ g = geom
+
+ # Transforming the geometry with our Coordinate
Transformation object.
+ g.transform(ct)
+
+ # Updating the keyword args with the WKT of the
transformed model.
+ val = g.wkt
+ else:
+ ## Otherwise, this is an OGR field type
+ fi = feat.index(ogr_field)
+ val = feat[fi].value
+
+ if is_fk:
+ rel_obj = None
+ field_name = model_field[:-3]
+ try:
+ #FIXME: refactor to efficiently fetch FKs.
+ # Requires significant re-work. :-/
+ rel = self.model._meta.get_field(field_name).rel
+ rel_obj = rel.to._default_manager.get(**{('%s__exact'
% rel.field_name):val})
+ except ObjectDoesNotExist:
+ all_prepped = False
+
+ kwargs[model_field[:-3]] = rel_obj
+ else:
+ kwargs[model_field] = val
+
+ # Constructing the model using the constructed keyword args
+ if all_prepped:
+ m = self.model(**kwargs)
+
+ # Saving the model
+ try:
+ if all_prepped:
+ m.save()
+ if verbose: print 'Saved: %s' % str(m)
+ else:
+ print "Skipping %s due to missing relation." % kwargs
+ except SystemExit:
+ raise
+ except Exception, e:
+ print "Failed to save %s\n Continuing" % kwargs
+
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---