Table of Contents for
Python Geospatial Development - Third Edition

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Python Geospatial Development - Third Edition by Erik Westra Published by Packt Publishing, 2016
  1. Cover
  2. Table of Contents
  3. Python Geospatial Development Third Edition
  4. Python Geospatial Development Third Edition
  5. Credits
  6. About the Author
  7. About the Reviewer
  8. www.PacktPub.com
  9. Preface
  10. What you need for this book
  11. Who this book is for
  12. Conventions
  13. Reader feedback
  14. Customer support
  15. 1. Geospatial Development Using Python
  16. Geospatial development
  17. Applications of geospatial development
  18. Recent developments
  19. Summary
  20. 2. GIS
  21. GIS data formats
  22. Working with GIS data manually
  23. Summary
  24. 3. Python Libraries for Geospatial Development
  25. Dealing with projections
  26. Analyzing and manipulating Geospatial data
  27. Visualizing geospatial data
  28. Summary
  29. 4. Sources of Geospatial Data
  30. Sources of geospatial data in raster format
  31. Sources of other types of geospatial data
  32. Choosing your geospatial data source
  33. Summary
  34. 5. Working with Geospatial Data in Python
  35. Working with geospatial data
  36. Changing datums and projections
  37. Performing geospatial calculations
  38. Converting and standardizing units of geometry and distance
  39. Exercises
  40. Summary
  41. 6. Spatial Databases
  42. Spatial indexes
  43. Introducing PostGIS
  44. Setting up a database
  45. Using PostGIS
  46. Recommended best practices
  47. Summary
  48. 7. Using Python and Mapnik to Generate Maps
  49. Creating an example map
  50. Mapnik concepts
  51. Summary
  52. 8. Working with Spatial Data
  53. Designing and building the database
  54. Downloading and importing the data
  55. Implementing the DISTAL application
  56. Using DISTAL
  57. Summary
  58. 9. Improving the DISTAL Application
  59. Dealing with the scale problem
  60. Performance
  61. Summary
  62. 10. Tools for Web-based Geospatial Development
  63. A closer look at three specific tools and techniques
  64. Summary
  65. 11. Putting It All Together – a Complete Mapping System
  66. Designing the ShapeEditor
  67. Prerequisites
  68. Setting up the database
  69. Setting up the ShapeEditor project
  70. Defining the ShapeEditor's applications
  71. Creating the shared application
  72. Defining the data models
  73. Playing with the admin system
  74. Summary
  75. 12. ShapeEditor – Importing and Exporting Shapefiles
  76. Importing shapefiles
  77. Exporting shapefiles
  78. Summary
  79. 13. ShapeEditor – Selecting and Editing Features
  80. Editing features
  81. Adding features
  82. Deleting features
  83. Deleting shapefiles
  84. Using the ShapeEditor
  85. Further improvements and enhancements
  86. Summary
  87. Index

Defining the data models

We already know which database objects we are going to need to store the uploaded shapefiles:

  • The Shapefile object will represent a single uploaded shapefile
  • Each shapefile will have a number of Attribute objects, giving the name, data type, and other information about each attribute within the shapefile
  • Each shapefile will have a number of Feature objects which hold the geometry for each of the shapefile's features
  • Each feature will have a set of AttributeValue objects which hold the value for each of the feature's attributes

Let's look at each of these in more detail and think about exactly what information will need to be stored in each object.

The Shapefile object

When we import a shapefile, there are a few things we need to remember:

  • The original name of the uploaded file. We will display this in the "list shapefiles" view so that the user can identify the shapefile within this list.
  • The spatial reference system used by the shapefile. When we import the shapefile, we will convert it to use latitude and longitude coordinates using the WGS84 datum (EPSG 4326), but we need to remember the shapefile's original spatial reference system so that we can use it again when exporting the features. For simplicity, we're going to store the spatial reference system in WKT format.
  • What type of geometry was stored in the shapefile. We'll need this to know which field in the Feature object holds the geometry.

The Attribute object

When we export a shapefile, it has to have the same attributes as the original imported file. Because of this, we have to remember the shapefile's attributes. This is what the Attribute object does. We will need to remember the following information for each attribute:

  • Which shapefile the attribute belongs to
  • The name of the attribute
  • The type of data stored in this attribute (string, floating-point number, and so on)
  • The field width of the attribute, in characters
  • The number of digits to display after the decimal point (for floating-point attributes)

All of this information comes directly from the shapefile's layer definition.

The Feature object

Each feature in the imported shapefile will need to be stored in the database. Because PostGIS (and GeoDjango) uses different field types for different types of geometries, we need to define separate fields for each geometry type. Because of this, the Feature object will need to store the following information:

  • The shapefile the feature belongs to
  • The Point geometry, if the shapefile stores this type of geometry
  • The MultiPoint geometry, if the shapefile stores this type of geometry
  • The MultiLineString geometry, if the shapefile stores this type of geometry
  • The MultiPolygon geometry, if the shapefile stores this type of geometry
  • The GeometryCollection geometry, if the shapefile stores this type of geometry

Tip

Isn't something missing?

If you've been paying attention, you've probably noticed that some of the geometry types are missing. What about Polygons and LineStrings? Because of the way data is stored in a shapefile, it is impossible to know in advance whether a shapefile holds Polygons or MultiPolygons and, similarly, whether it holds LineStrings or MultiLineStrings (or Points or MultiPoints). The shapefile's internal structure makes no distinction between these geometry types. Because of this, a shapefile may claim to store Polygons when it really contains MultiPolygons and may make similar claims for LineString geometries. For more information, check out http://code.djangoproject.com/ticket/7218.

To work around this limitation, we store all Polygons as MultiPolygons, all LineStrings as MultiLineStrings, and all Points as MultiPoints. This is why we don't need Polygon, Point, or LineString fields in the Feature object.

The AttributeValue object

The AttributeValue object holds the value for each of the feature's attributes. This object is quite straightforward, storing the following information:

  • Which feature the attribute value is for
  • Which attribute the value is for
  • The attribute's value, as a Unicode string

For simplicity, we'll store all attribute values as strings.

The models.py file

Now that we know what information we want to store in our database, it's easy to define our various model objects. To do this, edit the models.py file in the shapeEditor.shared directory, and make sure it looks like this:

from django.contrib.gis.db import models


class Shapefile(models.Model):
    filename  = models.CharField(max_length=255)
    srs_wkt   = models.CharField(max_length=255)
    geom_type = models.CharField(max_length=50)


class Attribute(models.Model):
    shapefile = models.ForeignKey(Shapefile)
    name      = models.CharField(max_length=255)
    type      = models.IntegerField()
    width     = models.IntegerField()
    precision = models.IntegerField()


class Feature(models.Model):
    shapefile = models.ForeignKey(Shapefile)
    geom_point = models.PointField(srid=4326, 
                                   blank=True, null=True)
    geom_multipoint = \
            models.MultiPointField(srid=4326,
                                   blank=True, null=True)
    geom_multilinestring = \
            models.MultiLineStringField(srid=4326,
                                        blank=True, null=True)
    geom_multipolygon = \
            models.MultiPolygonField(srid=4326,
                                     blank=True, null=True)
    geom_geometrycollection = \
            models.GeometryCollectionField(srid=4326,
                                           blank=True,
                                           null=True)

    objects = models.GeoManager()


class AttributeValue(models.Model):
    feature   = models.ForeignKey(Feature)
    attribute = models.ForeignKey(Attribute)
    value     = models.CharField(max_length=255,
                                 blank=True, null=True)

There are a few things to be aware of here:

  • The from...import statement at the top has changed. We're importing the GeoDjango models rather than the standard Django ones.
  • We use models.CharField objects to represent character data and models.IntegerField objects to represent integer values. Django provides a whole raft of field types for you to use. GeoDjango also adds its own field types to store geometry fields, as you can see from the definition of the Feature object.
  • To represent relations between two objects, we use a models.ForeignKey object.
  • Because the Feature object will store geometry data, we want to allow GeoDjango to perform spatial queries using this data. To enable this, we define a GeoManager() instance for the Feature class.
  • Several fields (in particular, the geom_XXX fields in the Feature object) have both blank=True and null=True. These are actually quite distinct: blank=True means that the admin interface allows the user to leave the field blank, while null=True tells the database that these fields can be set to NULL in the database. For the Feature object, we'll need both so that we don't get validation errors when entering geometries via the admin interface.

That's all we need to do (for now) to define our database models. After you've made these changes, save the file, cd into the topmost project directory, and type the following:

python manage.py makemigrations shared

As we saw in the previous chapter, the makemigrations command creates a database migration file for the given application. With this migration in place, we can now tell Django to create a database structure to match the contents of our database models:

python manage.py migrate

This command tells Django to check the models and create new database tables as required. You should now have a spatial database set up with the various database tables you have defined. Let's take a closer look at this database by typing the following:

psql shapeeditor

When you see the Postgres command prompt, type \d and press Return. You should see a list of all the database tables that have been created:

                       List of relations
 Schema |               Name                |   Type   | Owner 
--------+-----------------------------------+----------+-------
 public | auth_group                        | table    | shapeeditor
 public | auth_group_id_seq                 | sequence | shapeeditor
 public | auth_group_permissions            | table    | shapeeditor
 public | auth_group_permissions_id_seq     | sequence | shapeeditor
 public | auth_permission                   | table    | shapeeditor
 public | auth_permission_id_seq            | sequence | shapeeditor
 public | auth_user                         | table    | shapeeditor
 public | auth_user_groups                  | table    | shapeeditor
 public | auth_user_groups_id_seq           | sequence | shapeeditor
 public | auth_user_id_seq                  | sequence | shapeeditor
 public | auth_user_user_permissions        | table    | shapeeditor
 public | auth_user_user_permissions_id_seq | sequence | shapeeditor
 public | django_admin_log                  | table    | shapeeditor
 public | django_admin_log_id_seq           | sequence | shapeeditor
 public | django_content_type               | table    | shapeeditor
 public | django_content_type_id_seq        | sequence | shapeeditor
 public | django_migrations                 | table    | shapeeditor
 public | django_migrations_id_seq          | sequence | shapeeditor
 public | django_session                    | table    | shapeeditor
 public | geography_columns                 | view     | postgres
 public | geometry_columns                  | view     | postgres
 public | raster_columns                    | view     | postgres
 public | raster_overviews                  | view     | postgres
 public | shared_attribute                  | table    | shapeeditor
 public | shared_attribute_id_seq           | sequence | shapeeditor
 public | shared_attributevalue             | table    | shapeeditor
 public | shared_attributevalue_id_seq      | sequence | shapeeditor
 public | shared_feature                    | table    | shapeeditor
 public | shared_feature_id_seq             | sequence | shapeeditor
 public | shared_shapefile                  | table    | shapeeditor
 public | shared_shapefile_id_seq           | sequence | shapeeditor
 public | spatial_ref_sys                   | table    | postgres
(32 rows)

To make sure that each application's database tables are unique, Django adds the application name to the start of the table name. This means that the table names for the models we have created within the shared application are actually called shared_shapefile, shared_feature, and so on. We'll be working with these database tables directly later on, when we want to use Mapnik to generate maps using the imported Shapefile data.

When you have finished with the Postgres command-line client, type \q and press Return to quit psql.

Now that our database has been set up, let's create a "superuser" account so that we can access it. You can do this by entering the following command:

python manage.py createsuperuser

You'll be prompted to enter the username, e-mail address and password for a new superuser; we'll need this for the next section, where we explore GeoDjango's built-in admin interface.