|
1 ================== |
|
2 GeoDjango Tutorial |
|
3 ================== |
|
4 |
|
5 Introduction |
|
6 ============ |
|
7 |
|
8 GeoDjango is an add-on for Django that turns it into a world-class geographic |
|
9 Web framework. GeoDjango strives to make at as simple as possible to create |
|
10 geographic Web applications, like location-based services. Some features include: |
|
11 |
|
12 * Django model fields for `OGC`_ geometries. |
|
13 * Extensions to Django's ORM for the querying and manipulation of spatial data. |
|
14 * Loosely-coupled, high-level Python interfaces for GIS geometry operations and |
|
15 data formats. |
|
16 * Editing of geometry fields inside the admin. |
|
17 |
|
18 This tutorial assumes a familiarity with Django; thus, if you're brand new to |
|
19 Django please read through the :doc:`regular tutorial </intro/tutorial01>` to introduce |
|
20 yourself with basic Django concepts. |
|
21 |
|
22 .. note:: |
|
23 |
|
24 GeoDjango has special prerequisites overwhat is required by Django -- |
|
25 please consult the :ref:`installation documentation <ref-gis-install>` |
|
26 for more details. |
|
27 |
|
28 This tutorial will guide you through the creation of a geographic Web |
|
29 application for viewing the `world borders`_. [#]_ Some of the code |
|
30 used in this tutorial is taken from and/or inspired by the `GeoDjango |
|
31 basic apps`_ project. [#]_ |
|
32 |
|
33 .. note:: |
|
34 |
|
35 Proceed through the tutorial sections sequentially for step-by-step |
|
36 instructions. |
|
37 |
|
38 .. _OGC: http://www.opengeospatial.org/ |
|
39 .. _world borders: http://thematicmapping.org/downloads/world_borders.php |
|
40 .. _GeoDjango basic apps: http://code.google.com/p/geodjango-basic-apps/ |
|
41 |
|
42 Setting Up |
|
43 ========== |
|
44 |
|
45 Create a Spatial Database |
|
46 ------------------------- |
|
47 |
|
48 .. note:: |
|
49 |
|
50 MySQL and Oracle users can skip this section because spatial types |
|
51 are already built into the database. |
|
52 |
|
53 First, a spatial database needs to be created for our project. If using |
|
54 PostgreSQL and PostGIS, then the following commands will |
|
55 create the database from a :ref:`spatial database template <spatialdb_template>`:: |
|
56 |
|
57 $ createdb -T template_postgis geodjango |
|
58 |
|
59 .. note:: |
|
60 |
|
61 This command must be issued by a database user that has permissions to |
|
62 create a database. Here is an example set of commands to create such |
|
63 a user:: |
|
64 |
|
65 $ sudo su - postgres |
|
66 $ createuser --createdb geo |
|
67 $ exit |
|
68 |
|
69 Replace ``geo`` to correspond to the system login user name will be |
|
70 connecting to the database. For example, ``johndoe`` if that is the |
|
71 system user that will be running GeoDjango. |
|
72 |
|
73 Users of SQLite and SpatiaLite should consult the instructions on how |
|
74 to create a :ref:`SpatiaLite database <create_spatialite_db>`. |
|
75 |
|
76 Create GeoDjango Project |
|
77 ------------------------ |
|
78 |
|
79 Use the ``django-admin.py`` script like normal to create a ``geodjango`` project:: |
|
80 |
|
81 $ django-admin.py startproject geodjango |
|
82 |
|
83 With the project initialized, now create a ``world`` Django application within |
|
84 the ``geodjango`` project:: |
|
85 |
|
86 $ cd geodjango |
|
87 $ python manage.py startapp world |
|
88 |
|
89 Configure ``settings.py`` |
|
90 ------------------------- |
|
91 |
|
92 The ``geodjango`` project settings are stored in the ``settings.py`` file. Edit |
|
93 the database connection settings appropriately:: |
|
94 |
|
95 DATABASES = { |
|
96 'default': { |
|
97 'ENGINE': 'django.contrib.gis.db.backends.postgis', |
|
98 'NAME': 'geodjango', |
|
99 'USER': 'geo', |
|
100 } |
|
101 } |
|
102 |
|
103 .. note:: |
|
104 |
|
105 These database settings are for Django 1.2 and above. |
|
106 |
|
107 In addition, modify the :setting:`INSTALLED_APPS` setting to include |
|
108 :mod:`django.contrib.admin`, :mod:`django.contrib.gis`, |
|
109 and ``world`` (our newly created application):: |
|
110 |
|
111 INSTALLED_APPS = ( |
|
112 'django.contrib.auth', |
|
113 'django.contrib.contenttypes', |
|
114 'django.contrib.sessions', |
|
115 'django.contrib.sites', |
|
116 'django.contrib.admin', |
|
117 'django.contrib.gis', |
|
118 'world' |
|
119 ) |
|
120 |
|
121 Geographic Data |
|
122 =============== |
|
123 |
|
124 .. _worldborders: |
|
125 |
|
126 World Borders |
|
127 ------------- |
|
128 |
|
129 The world borders data is available in this `zip file`__. Create a data directory |
|
130 in the ``world`` application, download the world borders data, and unzip. |
|
131 On GNU/Linux platforms the following commands should do it:: |
|
132 |
|
133 $ mkdir world/data |
|
134 $ cd world/data |
|
135 $ wget http://thematicmapping.org/downloads/TM_WORLD_BORDERS-0.3.zip |
|
136 $ unzip TM_WORLD_BORDERS-0.3.zip |
|
137 $ cd ../.. |
|
138 |
|
139 The world borders ZIP file contains a set of data files collectively known as |
|
140 an `ESRI Shapefile`__, one of the most popular geospatial data formats. When |
|
141 unzipped the world borders data set includes files with the following extensions: |
|
142 |
|
143 * ``.shp``: Holds the vector data for the world borders geometries. |
|
144 * ``.shx``: Spatial index file for geometries stored in the ``.shp``. |
|
145 * ``.dbf``: Database file for holding non-geometric attribute data |
|
146 (e.g., integer and character fields). |
|
147 * ``.prj``: Contains the spatial reference information for the geographic |
|
148 data stored in the shapefile. |
|
149 |
|
150 __ http://thematicmapping.org/downloads/TM_WORLD_BORDERS-0.3.zip |
|
151 __ http://en.wikipedia.org/wiki/Shapefile |
|
152 |
|
153 Use ``ogrinfo`` to examine spatial data |
|
154 --------------------------------------- |
|
155 |
|
156 The GDAL ``ogrinfo`` utility is excellent for examining metadata about |
|
157 shapefiles (or other vector data sources):: |
|
158 |
|
159 $ ogrinfo world/data/TM_WORLD_BORDERS-0.3.shp |
|
160 INFO: Open of `world/data/TM_WORLD_BORDERS-0.3.shp' |
|
161 using driver `ESRI Shapefile' successful. |
|
162 1: TM_WORLD_BORDERS-0.3 (Polygon) |
|
163 |
|
164 Here ``ogrinfo`` is telling us that the shapefile has one layer, and that |
|
165 layer contains polygon data. To find out more we'll specify the layer name |
|
166 and use the ``-so`` option to get only important summary information:: |
|
167 |
|
168 $ ogrinfo -so world/data/TM_WORLD_BORDERS-0.3.shp TM_WORLD_BORDERS-0.3 |
|
169 INFO: Open of `world/data/TM_WORLD_BORDERS-0.3.shp' |
|
170 using driver `ESRI Shapefile' successful. |
|
171 |
|
172 Layer name: TM_WORLD_BORDERS-0.3 |
|
173 Geometry: Polygon |
|
174 Feature Count: 246 |
|
175 Extent: (-180.000000, -90.000000) - (180.000000, 83.623596) |
|
176 Layer SRS WKT: |
|
177 GEOGCS["GCS_WGS_1984", |
|
178 DATUM["WGS_1984", |
|
179 SPHEROID["WGS_1984",6378137.0,298.257223563]], |
|
180 PRIMEM["Greenwich",0.0], |
|
181 UNIT["Degree",0.0174532925199433]] |
|
182 FIPS: String (2.0) |
|
183 ISO2: String (2.0) |
|
184 ISO3: String (3.0) |
|
185 UN: Integer (3.0) |
|
186 NAME: String (50.0) |
|
187 AREA: Integer (7.0) |
|
188 POP2005: Integer (10.0) |
|
189 REGION: Integer (3.0) |
|
190 SUBREGION: Integer (3.0) |
|
191 LON: Real (8.3) |
|
192 LAT: Real (7.3) |
|
193 |
|
194 This detailed summary information tells us the number of features in the layer |
|
195 (246), the geographical extent, the spatial reference system ("SRS WKT"), |
|
196 as well as detailed information for each attribute field. For example, |
|
197 ``FIPS: String (2.0)`` indicates that there's a ``FIPS`` character field |
|
198 with a maximum length of 2; similarly, ``LON: Real (8.3)`` is a floating-point |
|
199 field that holds a maximum of 8 digits up to three decimal places. Although |
|
200 this information may be found right on the `world borders`_ Web site, this shows |
|
201 you how to determine this information yourself when such metadata is not |
|
202 provided. |
|
203 |
|
204 Geographic Models |
|
205 ================= |
|
206 |
|
207 Defining a Geographic Model |
|
208 --------------------------- |
|
209 |
|
210 Now that we've examined our world borders data set using ``ogrinfo``, we can |
|
211 create a GeoDjango model to represent this data:: |
|
212 |
|
213 from django.contrib.gis.db import models |
|
214 |
|
215 class WorldBorders(models.Model): |
|
216 # Regular Django fields corresponding to the attributes in the |
|
217 # world borders shapefile. |
|
218 name = models.CharField(max_length=50) |
|
219 area = models.IntegerField() |
|
220 pop2005 = models.IntegerField('Population 2005') |
|
221 fips = models.CharField('FIPS Code', max_length=2) |
|
222 iso2 = models.CharField('2 Digit ISO', max_length=2) |
|
223 iso3 = models.CharField('3 Digit ISO', max_length=3) |
|
224 un = models.IntegerField('United Nations Code') |
|
225 region = models.IntegerField('Region Code') |
|
226 subregion = models.IntegerField('Sub-Region Code') |
|
227 lon = models.FloatField() |
|
228 lat = models.FloatField() |
|
229 |
|
230 # GeoDjango-specific: a geometry field (MultiPolygonField), and |
|
231 # overriding the default manager with a GeoManager instance. |
|
232 mpoly = models.MultiPolygonField() |
|
233 objects = models.GeoManager() |
|
234 |
|
235 # So the model is pluralized correctly in the admin. |
|
236 class Meta: |
|
237 verbose_name_plural = "World Borders" |
|
238 |
|
239 # Returns the string representation of the model. |
|
240 def __unicode__(self): |
|
241 return self.name |
|
242 |
|
243 Two important things to note: |
|
244 |
|
245 1. The ``models`` module is imported from :mod:`django.contrib.gis.db`. |
|
246 2. The model overrides its default manager with |
|
247 :class:`~django.contrib.gis.db.models.GeoManager`; this is *required* |
|
248 to perform spatial queries. |
|
249 |
|
250 When declaring a geometry field on your model the default spatial reference system |
|
251 is WGS84 (meaning the `SRID`__ is 4326) -- in other words, the field coordinates are in |
|
252 longitude/latitude pairs in units of degrees. If you want the coordinate system to be |
|
253 different, then SRID of the geometry field may be customized by setting the ``srid`` |
|
254 with an integer corresponding to the coordinate system of your choice. |
|
255 |
|
256 __ http://en.wikipedia.org/wiki/SRID |
|
257 |
|
258 Run ``syncdb`` |
|
259 -------------- |
|
260 |
|
261 After you've defined your model, it needs to be synced with the spatial database. |
|
262 First, let's look at the SQL that will generate the table for the ``WorldBorders`` |
|
263 model:: |
|
264 |
|
265 $ python manage.py sqlall world |
|
266 |
|
267 This management command should produce the following output:: |
|
268 |
|
269 BEGIN; |
|
270 CREATE TABLE "world_worldborders" ( |
|
271 "id" serial NOT NULL PRIMARY KEY, |
|
272 "name" varchar(50) NOT NULL, |
|
273 "area" integer NOT NULL, |
|
274 "pop2005" integer NOT NULL, |
|
275 "fips" varchar(2) NOT NULL, |
|
276 "iso2" varchar(2) NOT NULL, |
|
277 "iso3" varchar(3) NOT NULL, |
|
278 "un" integer NOT NULL, |
|
279 "region" integer NOT NULL, |
|
280 "subregion" integer NOT NULL, |
|
281 "lon" double precision NOT NULL, |
|
282 "lat" double precision NOT NULL |
|
283 ) |
|
284 ; |
|
285 SELECT AddGeometryColumn('world_worldborders', 'mpoly', 4326, 'MULTIPOLYGON', 2); |
|
286 ALTER TABLE "world_worldborders" ALTER "mpoly" SET NOT NULL; |
|
287 CREATE INDEX "world_worldborders_mpoly_id" ON "world_worldborders" USING GIST ( "mpoly" GIST_GEOMETRY_OPS ); |
|
288 COMMIT; |
|
289 |
|
290 If satisfied, you may then create this table in the database by running the |
|
291 ``syncdb`` management command:: |
|
292 |
|
293 $ python manage.py syncdb |
|
294 Creating table world_worldborders |
|
295 Installing custom SQL for world.WorldBorders model |
|
296 |
|
297 The ``syncdb`` command may also prompt you to create an admin user; go ahead and |
|
298 do so (not required now, may be done at any point in the future using the |
|
299 ``createsuperuser`` management command). |
|
300 |
|
301 Importing Spatial Data |
|
302 ====================== |
|
303 |
|
304 This section will show you how to take the data from the world borders |
|
305 shapefile and import it into GeoDjango models using the :ref:`ref-layermapping`. |
|
306 There are many different different ways to import data in to a |
|
307 spatial database -- besides the tools included within GeoDjango, you |
|
308 may also use the following to populate your spatial database: |
|
309 |
|
310 * `ogr2ogr`_: Command-line utility, included with GDAL, that |
|
311 supports loading a multitude of vector data formats into |
|
312 the PostGIS, MySQL, and Oracle spatial databases. |
|
313 * `shp2pgsql`_: This utility is included with PostGIS and only supports |
|
314 ESRI shapefiles. |
|
315 |
|
316 .. _ogr2ogr: http://www.gdal.org/ogr2ogr.html |
|
317 .. _shp2pgsql: http://postgis.refractions.net/documentation/manual-1.5/ch04.html#shp2pgsql_usage |
|
318 |
|
319 .. _gdalinterface: |
|
320 |
|
321 GDAL Interface |
|
322 -------------- |
|
323 |
|
324 Earlier we used the the ``ogrinfo`` to explore the contents of the world borders |
|
325 shapefile. Included within GeoDjango is an interface to GDAL's powerful OGR |
|
326 library -- in other words, you'll be able explore all the vector data sources |
|
327 that OGR supports via a Pythonic API. |
|
328 |
|
329 First, invoke the Django shell:: |
|
330 |
|
331 $ python manage.py shell |
|
332 |
|
333 If the :ref:`worldborders` data was downloaded like earlier in the |
|
334 tutorial, then we can determine the path using Python's built-in |
|
335 ``os`` module:: |
|
336 |
|
337 >>> import os |
|
338 >>> from geodjango import world |
|
339 >>> world_shp = os.path.abspath(os.path.join(os.path.dirname(world.__file__), |
|
340 ... 'data/TM_WORLD_BORDERS-0.3.shp')) |
|
341 |
|
342 Now, the world borders shapefile may be opened using GeoDjango's |
|
343 :class:`~django.contrib.gis.gdal.DataSource` interface:: |
|
344 |
|
345 >>> from django.contrib.gis.gdal import * |
|
346 >>> ds = DataSource(world_shp) |
|
347 >>> print ds |
|
348 / ... /geodjango/world/data/TM_WORLD_BORDERS-0.3.shp (ESRI Shapefile) |
|
349 |
|
350 Data source objects can have different layers of geospatial features; however, |
|
351 shapefiles are only allowed to have one layer:: |
|
352 |
|
353 >>> print len(ds) |
|
354 1 |
|
355 >>> lyr = ds[0] |
|
356 >>> print lyr |
|
357 TM_WORLD_BORDERS-0.3 |
|
358 |
|
359 You can see what the geometry type of the layer is and how many features it |
|
360 contains:: |
|
361 |
|
362 >>> print lyr.geom_type |
|
363 Polygon |
|
364 >>> print len(lyr) |
|
365 246 |
|
366 |
|
367 .. note:: |
|
368 |
|
369 Unfortunately the shapefile data format does not allow for greater |
|
370 specificity with regards to geometry types. This shapefile, like |
|
371 many others, actually includes ``MultiPolygon`` geometries in its |
|
372 features. You need to watch out for this when creating your models |
|
373 as a GeoDjango ``PolygonField`` will not accept a ``MultiPolygon`` |
|
374 type geometry -- thus a ``MultiPolygonField`` is used in our model's |
|
375 definition instead. |
|
376 |
|
377 The :class:`~django.contrib.gis.gdal.Layer` may also have a spatial reference |
|
378 system associated with it -- if it does, the ``srs`` attribute will return a |
|
379 :class:`~django.contrib.gis.gdal.SpatialReference` object:: |
|
380 |
|
381 >>> srs = lyr.srs |
|
382 >>> print srs |
|
383 GEOGCS["GCS_WGS_1984", |
|
384 DATUM["WGS_1984", |
|
385 SPHEROID["WGS_1984",6378137.0,298.257223563]], |
|
386 PRIMEM["Greenwich",0.0], |
|
387 UNIT["Degree",0.0174532925199433]] |
|
388 >>> srs.proj4 # PROJ.4 representation |
|
389 '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ' |
|
390 |
|
391 Here we've noticed that the shapefile is in the popular WGS84 spatial reference |
|
392 system -- in other words, the data uses units of degrees longitude and latitude. |
|
393 |
|
394 In addition, shapefiles also support attribute fields that may contain |
|
395 additional data. Here are the fields on the World Borders layer: |
|
396 |
|
397 >>> print lyr.fields |
|
398 ['FIPS', 'ISO2', 'ISO3', 'UN', 'NAME', 'AREA', 'POP2005', 'REGION', 'SUBREGION', 'LON', 'LAT'] |
|
399 |
|
400 Here we are examining the OGR types (e.g., whether a field is an integer or |
|
401 a string) associated with each of the fields: |
|
402 |
|
403 >>> [fld.__name__ for fld in lyr.field_types] |
|
404 ['OFTString', 'OFTString', 'OFTString', 'OFTInteger', 'OFTString', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTInteger', 'OFTReal', 'OFTReal'] |
|
405 |
|
406 You can iterate over each feature in the layer and extract information from both |
|
407 the feature's geometry (accessed via the ``geom`` attribute) as well as the |
|
408 feature's attribute fields (whose **values** are accessed via ``get()`` |
|
409 method):: |
|
410 |
|
411 >>> for feat in lyr: |
|
412 ... print feat.get('NAME'), feat.geom.num_points |
|
413 ... |
|
414 Guernsey 18 |
|
415 Jersey 26 |
|
416 South Georgia South Sandwich Islands 338 |
|
417 Taiwan 363 |
|
418 |
|
419 :class:`~django.contrib.gis.gdal.Layer` objects may be sliced:: |
|
420 |
|
421 >>> lyr[0:2] |
|
422 [<django.contrib.gis.gdal.feature.Feature object at 0x2f47690>, <django.contrib.gis.gdal.feature.Feature object at 0x2f47650>] |
|
423 |
|
424 And individual features may be retrieved by their feature ID:: |
|
425 |
|
426 >>> feat = lyr[234] |
|
427 >>> print feat.get('NAME') |
|
428 San Marino |
|
429 |
|
430 Here the boundary geometry for San Marino is extracted and looking |
|
431 exported to WKT and GeoJSON:: |
|
432 |
|
433 >>> geom = feat.geom |
|
434 >>> print geom.wkt |
|
435 POLYGON ((12.415798 43.957954,12.450554 ... |
|
436 >>> print geom.json |
|
437 { "type": "Polygon", "coordinates": [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ... |
|
438 |
|
439 |
|
440 ``LayerMapping`` |
|
441 ---------------- |
|
442 |
|
443 We're going to dive right in -- create a file called ``load.py`` inside the |
|
444 ``world`` application, and insert the following:: |
|
445 |
|
446 import os |
|
447 from django.contrib.gis.utils import LayerMapping |
|
448 from models import WorldBorders |
|
449 |
|
450 world_mapping = { |
|
451 'fips' : 'FIPS', |
|
452 'iso2' : 'ISO2', |
|
453 'iso3' : 'ISO3', |
|
454 'un' : 'UN', |
|
455 'name' : 'NAME', |
|
456 'area' : 'AREA', |
|
457 'pop2005' : 'POP2005', |
|
458 'region' : 'REGION', |
|
459 'subregion' : 'SUBREGION', |
|
460 'lon' : 'LON', |
|
461 'lat' : 'LAT', |
|
462 'mpoly' : 'MULTIPOLYGON', |
|
463 } |
|
464 |
|
465 world_shp = os.path.abspath(os.path.join(os.path.dirname(__file__), 'data/TM_WORLD_BORDERS-0.3.shp')) |
|
466 |
|
467 def run(verbose=True): |
|
468 lm = LayerMapping(WorldBorders, world_shp, world_mapping, |
|
469 transform=False, encoding='iso-8859-1') |
|
470 |
|
471 lm.save(strict=True, verbose=verbose) |
|
472 |
|
473 A few notes about what's going on: |
|
474 |
|
475 * Each key in the ``world_mapping`` dictionary corresponds to a field in the |
|
476 ``WorldBorders`` model, and the value is the name of the shapefile field |
|
477 that data will be loaded from. |
|
478 * The key ``mpoly`` for the geometry field is ``MULTIPOLYGON``, the |
|
479 geometry type we wish to import as. Even if simple polygons are encountered |
|
480 in the shapefile they will automatically be converted into collections prior |
|
481 to insertion into the database. |
|
482 * The path to the shapefile is not absolute -- in other words, if you move the |
|
483 ``world`` application (with ``data`` subdirectory) to a different location, |
|
484 then the script will still work. |
|
485 * The ``transform`` keyword is set to ``False`` because the data in the |
|
486 shapefile does not need to be converted -- it's already in WGS84 (SRID=4326). |
|
487 * The ``encoding`` keyword is set to the character encoding of string values in |
|
488 the shapefile. This ensures that string values are read and saved correctly |
|
489 from their original encoding system. |
|
490 |
|
491 Afterwards, invoke the Django shell from the ``geodjango`` project directory:: |
|
492 |
|
493 $ python manage.py shell |
|
494 |
|
495 Next, import the ``load`` module, call the ``run`` routine, and watch ``LayerMapping`` |
|
496 do the work:: |
|
497 |
|
498 >>> from world import load |
|
499 >>> load.run() |
|
500 |
|
501 |
|
502 .. _ogrinspect-intro: |
|
503 |
|
504 Try ``ogrinspect`` |
|
505 ------------------ |
|
506 Now that you've seen how to define geographic models and import data with the |
|
507 :ref:`ref-layermapping`, it's possible to further automate this process with |
|
508 use of the :djadmin:`ogrinspect` management command. The :djadmin:`ogrinspect` |
|
509 command introspects a GDAL-supported vector data source (e.g., a shapefile) and |
|
510 generates a model definition and ``LayerMapping`` dictionary automatically. |
|
511 |
|
512 The general usage of the command goes as follows:: |
|
513 |
|
514 $ python manage.py ogrinspect [options] <data_source> <model_name> [options] |
|
515 |
|
516 Where ``data_source`` is the path to the GDAL-supported data source and |
|
517 ``model_name`` is the name to use for the model. Command-line options may |
|
518 be used to further define how the model is generated. |
|
519 |
|
520 For example, the following command nearly reproduces the ``WorldBorders`` model |
|
521 and mapping dictionary created above, automatically:: |
|
522 |
|
523 $ python manage.py ogrinspect world/data/TM_WORLD_BORDERS-0.3.shp WorldBorders --srid=4326 --mapping --multi |
|
524 |
|
525 A few notes about the command-line options given above: |
|
526 |
|
527 * The ``--srid=4326`` option sets the SRID for the geographic field. |
|
528 * The ``--mapping`` option tells ``ogrinspect`` to also generate a |
|
529 mapping dictionary for use with :class:`~django.contrib.gis.utils.LayerMapping`. |
|
530 * The ``--multi`` option is specified so that the geographic field is a |
|
531 :class:`~django.contrib.gis.db.models.MultiPolygonField` instead of just a |
|
532 :class:`~django.contrib.gis.db.models.PolygonField`. |
|
533 |
|
534 The command produces the following output, which may be copied |
|
535 directly into the ``models.py`` of a GeoDjango application:: |
|
536 |
|
537 # This is an auto-generated Django model module created by ogrinspect. |
|
538 from django.contrib.gis.db import models |
|
539 |
|
540 class WorldBorders(models.Model): |
|
541 fips = models.CharField(max_length=2) |
|
542 iso2 = models.CharField(max_length=2) |
|
543 iso3 = models.CharField(max_length=3) |
|
544 un = models.IntegerField() |
|
545 name = models.CharField(max_length=50) |
|
546 area = models.IntegerField() |
|
547 pop2005 = models.IntegerField() |
|
548 region = models.IntegerField() |
|
549 subregion = models.IntegerField() |
|
550 lon = models.FloatField() |
|
551 lat = models.FloatField() |
|
552 geom = models.MultiPolygonField(srid=4326) |
|
553 objects = models.GeoManager() |
|
554 |
|
555 # Auto-generated `LayerMapping` dictionary for WorldBorders model |
|
556 worldborders_mapping = { |
|
557 'fips' : 'FIPS', |
|
558 'iso2' : 'ISO2', |
|
559 'iso3' : 'ISO3', |
|
560 'un' : 'UN', |
|
561 'name' : 'NAME', |
|
562 'area' : 'AREA', |
|
563 'pop2005' : 'POP2005', |
|
564 'region' : 'REGION', |
|
565 'subregion' : 'SUBREGION', |
|
566 'lon' : 'LON', |
|
567 'lat' : 'LAT', |
|
568 'geom' : 'MULTIPOLYGON', |
|
569 } |
|
570 |
|
571 Spatial Queries |
|
572 =============== |
|
573 |
|
574 Spatial Lookups |
|
575 --------------- |
|
576 GeoDjango extends the Django ORM and allows the use of spatial lookups. |
|
577 Let's do an example where we find the ``WorldBorder`` model that contains |
|
578 a point. First, fire up the management shell:: |
|
579 |
|
580 $ python manage.py shell |
|
581 |
|
582 Now, define a point of interest [#]_:: |
|
583 |
|
584 >>> pnt_wkt = 'POINT(-95.3385 29.7245)' |
|
585 |
|
586 The ``pnt_wkt`` string represents the point at -95.3385 degrees longitude, |
|
587 and 29.7245 degrees latitude. The geometry is in a format known as |
|
588 Well Known Text (WKT), an open standard issued by the Open Geospatial |
|
589 Consortium (OGC). [#]_ Import the ``WorldBorders`` model, and perform |
|
590 a ``contains`` lookup using the ``pnt_wkt`` as the parameter:: |
|
591 |
|
592 >>> from world.models import WorldBorders |
|
593 >>> qs = WorldBorders.objects.filter(mpoly__contains=pnt_wkt) |
|
594 >>> qs |
|
595 [<WorldBorders: United States>] |
|
596 |
|
597 Here we retrieved a ``GeoQuerySet`` that has only one model: the one |
|
598 for the United States (which is what we would expect). Similarly, |
|
599 a :ref:`GEOS geometry object <ref-geos>` may also be used -- here the ``intersects`` |
|
600 spatial lookup is combined with the ``get`` method to retrieve |
|
601 only the ``WorldBorders`` instance for San Marino instead of a queryset:: |
|
602 |
|
603 >>> from django.contrib.gis.geos import Point |
|
604 >>> pnt = Point(12.4604, 43.9420) |
|
605 >>> sm = WorldBorders.objects.get(mpoly__intersects=pnt) |
|
606 >>> sm |
|
607 <WorldBorders: San Marino> |
|
608 |
|
609 The ``contains`` and ``intersects`` lookups are just a subset of what's |
|
610 available -- the :ref:`ref-gis-db-api` documentation has more. |
|
611 |
|
612 Automatic Spatial Transformations |
|
613 --------------------------------- |
|
614 When querying the spatial database GeoDjango automatically transforms |
|
615 geometries if they're in a different coordinate system. In the following |
|
616 example, the coordinate will be expressed in terms of `EPSG SRID 32140`__, |
|
617 a coordinate system specific to south Texas **only** and in units of |
|
618 **meters** and not degrees:: |
|
619 |
|
620 >>> from django.contrib.gis.geos import * |
|
621 >>> pnt = Point(954158.1, 4215137.1, srid=32140) |
|
622 |
|
623 Note that ``pnt`` may also constructed with EWKT, an "extended" form of |
|
624 WKT that includes the SRID:: |
|
625 |
|
626 >>> pnt = GEOSGeometry('SRID=32140;POINT(954158.1 4215137.1)') |
|
627 |
|
628 When using GeoDjango's ORM, it will automatically wrap geometry values |
|
629 in transformation SQL, allowing the developer to work at a higher level |
|
630 of abstraction:: |
|
631 |
|
632 >>> qs = WorldBorders.objects.filter(mpoly__intersects=pnt) |
|
633 >>> qs.query.as_sql() # Generating the SQL |
|
634 ('SELECT "world_worldborders"."id", "world_worldborders"."name", "world_worldborders"."area", |
|
635 "world_worldborders"."pop2005", "world_worldborders"."fips", "world_worldborders"."iso2", |
|
636 "world_worldborders"."iso3", "world_worldborders"."un", "world_worldborders"."region", |
|
637 "world_worldborders"."subregion", "world_worldborders"."lon", "world_worldborders"."lat", |
|
638 "world_worldborders"."mpoly" FROM "world_worldborders" |
|
639 WHERE ST_Intersects("world_worldborders"."mpoly", ST_Transform(%s, 4326))', |
|
640 (<django.contrib.gis.db.backend.postgis.adaptor.PostGISAdaptor object at 0x25641b0>,)) |
|
641 >>> qs # printing evaluates the queryset |
|
642 [<WorldBorders: United States>] |
|
643 |
|
644 __ http://spatialreference.org/ref/epsg/32140/ |
|
645 |
|
646 Lazy Geometries |
|
647 --------------- |
|
648 Geometries come to GeoDjango in a standardized textual representation. Upon |
|
649 access of the geometry field, GeoDjango creates a `GEOS geometry object <ref-geos>`, |
|
650 exposing powerful functionality, such as serialization properties for |
|
651 popular geospatial formats:: |
|
652 |
|
653 >>> sm = WorldBorders.objects.get(name='San Marino') |
|
654 >>> sm.mpoly |
|
655 <MultiPolygon object at 0x24c6798> |
|
656 >>> sm.mpoly.wkt # WKT |
|
657 MULTIPOLYGON (((12.4157980000000006 43.9579540000000009, 12.4505540000000003 43.9797209999999978, ... |
|
658 >>> sm.mpoly.wkb # WKB (as Python binary buffer) |
|
659 <read-only buffer for 0x1fe2c70, size -1, offset 0 at 0x2564c40> |
|
660 >>> sm.mpoly.geojson # GeoJSON (requires GDAL) |
|
661 '{ "type": "MultiPolygon", "coordinates": [ [ [ [ 12.415798, 43.957954 ], [ 12.450554, 43.979721 ], ... |
|
662 |
|
663 This includes access to all of the advanced geometric operations provided by |
|
664 the GEOS library:: |
|
665 |
|
666 >>> pnt = Point(12.4604, 43.9420) |
|
667 >>> sm.mpoly.contains(pnt) |
|
668 True |
|
669 >>> pnt.contains(sm.mpoly) |
|
670 False |
|
671 |
|
672 ``GeoQuerySet`` Methods |
|
673 ----------------------- |
|
674 |
|
675 |
|
676 Putting your data on the map |
|
677 ============================ |
|
678 |
|
679 Google |
|
680 ------ |
|
681 |
|
682 Geographic Admin |
|
683 ---------------- |
|
684 |
|
685 GeoDjango extends :doc:`Django's admin application </ref/contrib/admin/index>` |
|
686 to enable support for editing geometry fields. |
|
687 |
|
688 Basics |
|
689 ^^^^^^ |
|
690 |
|
691 GeoDjango also supplements the Django admin by allowing users to create |
|
692 and modify geometries on a JavaScript slippy map (powered by `OpenLayers`_). |
|
693 |
|
694 Let's dive in again -- create a file called ``admin.py`` inside the |
|
695 ``world`` application, and insert the following:: |
|
696 |
|
697 from django.contrib.gis import admin |
|
698 from models import WorldBorders |
|
699 |
|
700 admin.site.register(WorldBorders, admin.GeoModelAdmin) |
|
701 |
|
702 Next, edit your ``urls.py`` in the ``geodjango`` project folder to look |
|
703 as follows:: |
|
704 |
|
705 from django.conf.urls.defaults import * |
|
706 from django.contrib.gis import admin |
|
707 |
|
708 admin.autodiscover() |
|
709 |
|
710 urlpatterns = patterns('', |
|
711 (r'^admin/', include(admin.site.urls)), |
|
712 ) |
|
713 |
|
714 Start up the Django development server:: |
|
715 |
|
716 $ python manage.py runserver |
|
717 |
|
718 Finally, browse to ``http://localhost:8000/admin/``, and log in with the admin |
|
719 user created after running ``syncdb``. Browse to any of the ``WorldBorders`` |
|
720 entries -- the borders may be edited by clicking on a polygon and dragging |
|
721 the vertexes to the desired position. |
|
722 |
|
723 .. _OpenLayers: http://openlayers.org/ |
|
724 .. _Open Street Map: http://openstreetmap.org/ |
|
725 .. _Vector Map Level 0: http://earth-info.nga.mil/publications/vmap0.html |
|
726 .. _Metacarta: http://metacarta.com |
|
727 |
|
728 .. _osmgeoadmin-intro: |
|
729 |
|
730 ``OSMGeoAdmin`` |
|
731 ^^^^^^^^^^^^^^^ |
|
732 |
|
733 With the :class:`~django.contrib.gis.admin.OSMGeoAdmin`, GeoDjango uses |
|
734 a `Open Street Map`_ layer in the admin. |
|
735 This provides more context (including street and thoroughfare details) than |
|
736 available with the :class:`~django.contrib.gis.admin.GeoModelAdmin` |
|
737 (which uses the `Vector Map Level 0`_ WMS data set hosted at `Metacarta`_). |
|
738 |
|
739 First, there are some important requirements and limitations: |
|
740 |
|
741 * :class:`~django.contrib.gis.admin.OSMGeoAdmin` requires that the |
|
742 :ref:`spherical mercator projection be added <addgoogleprojection>` |
|
743 to the to be added to the ``spatial_ref_sys`` table (PostGIS 1.3 and |
|
744 below, only). |
|
745 * The PROJ.4 datum shifting files must be installed (see the |
|
746 :ref:`PROJ.4 installation instructions <proj4>` for more details). |
|
747 |
|
748 If you meet these requirements, then just substitute in the ``OSMGeoAdmin`` |
|
749 option class in your ``admin.py`` file:: |
|
750 |
|
751 admin.site.register(WorldBorders, admin.OSMGeoAdmin) |
|
752 |
|
753 .. rubric:: Footnotes |
|
754 |
|
755 .. [#] Special thanks to Bjørn Sandvik of `thematicmapping.org <http://thematicmapping.org>`_ for providing and maintaining this data set. |
|
756 .. [#] GeoDjango basic apps was written by Dane Springmeyer, Josh Livni, and Christopher Schmidt. |
|
757 .. [#] Here the point is for the `University of Houston Law Center <http://www.law.uh.edu/>`_ . |
|
758 .. [#] Open Geospatial Consortium, Inc., `OpenGIS Simple Feature Specification For SQL <http://www.opengis.org/docs/99-049.pdf>`_, Document 99-049. |