How to Use the Feature Model

The PCL model of a geographic feature is simple and flexible. A PCL feature

  • Has a unique id attribute.
  • Has a bounds attribute, the geographic extent or envelope of the feature.
  • Contains several properties, any number of which can be geometries.
  • Can not contain other features. PCL features are simple, or "flat".

A feature is always of a single feature type. If you are coming from a Plone or CPS background you can think of the relationship between a feature and a feature type as being very much like a content object and the proper content type. A major difference is that feature types will often be defined not in Python, but in the structure of a GIS data file or database relation. Instances of one or more feature types are accessed via PCL's feature store abstraction.

In a nutshell, what we have is a Object-Relational mapper that supports the OGC's new simple features profile.

How to Read Features

For example, let's inspect the feature types in the world borders dataset from mappinghacks.com, which we have been also using as a PCL test fixture. The world_borders shapefile should be wrapped in an OGR virtual file (OVF).

>>> from pprint import pprint
>>> from cartography.data.disk import DiskFeatureStore
>>> store = DiskFeatureStore('/var/wms_data/world/world_borders.ovf')
>>> print store.typenames()
['world_borders']
>>> ft = store.featuretype('world_borders')
>>> pprint(ft.schema_info())
{'AREA': 'float',
 'CAT': 'float',
 'CNTRY_NAME': 'string',
 'FIPS_CNTRY': 'string',
 'POP_CNTRY': 'float',
 'the_geom': 'geometry'}

The feature store has a single feature type, known by the name "world_borders". Essentially, this is the world_borders shapefile. We can readily get the feature type schema in dict form, ideal for use in an HTML template's column headers, or as JSON data for a browser app. If you're coming from a background of using GDAL's ogr.py module, I hope you will appreciate the higher level of PCL's feature model.

A feature type may specify default geometry and spatial reference system. Our data typically (and this is also presumed by OGR) has a single geometry and uniform spatial reference, so feature types from DiskFeatureStore? do have the defaults one expects:

>>> print ft.defaultgeometry().name
'the_geom'
>>> print ft.schema()['the_geom']
<cartography.data.feature.GeometryProperty instance at 0xf6cc1dcc>
>>> print ft.defaultsrs()
+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs

The PCL feature model does not restrict you to a single geometry. The PostGIS backend (for one) can provide you with the means to store multiple geometry features.

Next, let's get the first feature of the world borders type

>>> source = store.source('world_borders')
>>> fiter = source()
>>> feature = fiter.next()

It's easily shown to be an instance of the feature type class

>>> isinstance(feature, ft)
True

The info method provides a summary, also ideal for use in HTML templates, or as data for a Javascript app.

>>> pprint(feature.info())
{'bounds': [-70.063338999999999,
            12.411110000000001,
            -69.873337000000006,
            12.631109],
 'id': '0',
 'properties': {'AREA': 193.0,
                'CAT': 1.0,
                'CNTRY_NAME': 'Aruba',
                'FIPS_CNTRY': 'AA',
                'POP_CNTRY': 71218.0},
 'srs': 'None'}

Primary attributes of a feature are id and bounds:

>>> print feature.id
0
>>> print feature.bounds.totuple()
(-70.063338999999999, 12.411110000000001, -69.873337000000006, 12.631109)

Feature properties, commonly referred to in the GIS business as attributes, or items in MapServer?, can be accessed as dict or with a dot operator:

>>> print feature.properties.CNTRY_NAME
Aruba
>>> print feature.properties['CNTRY_NAME']
Aruba

How to Write Features

PCL's in-memory feature store is yet the only one that supports feature writing. There's no reason why PCL couldn't implement the addfeature method for disk and PostGIS feature stores, but we're in no hurry to do so as there are better tools for the creation of new datasets. Here's an example of creating a new feature type, feature store, and feature instances extracted from testmemfeatures.py:

from cartography.data import feature, memory
from cartography.referencing import srs

# A feature type derives from Feature
class F(feature.Feature):
    __schema__ = feature.PropertyTypeCollection([
                    feature.StringProperty('name'),
                    feature.IntegerProperty('category'),
                    feature.GeometryProperty('the_geom')
                 ])
    __typename__ = 'F'
    __srs__ = srs.SpatialReference(epsg=4326)

# Pass the type to the store constructor
store = memory.MemoryFeatureStore(F)

# Open a source on the store and add two features
sink = store.source('F')
sink.addfeature(F('1', name='foo', category=1, the_geom=Point(1,1)))
sink.addfeature(F('2', name='bar', category=2, the_geom=Point(2,2)))