In [ ]:
import os
import numpy as np
import matplotlib.pyplot as plt
import fiona

from collections import defaultdict, OrderedDict 
from shapely.geometry import shape
from matplotlib.patches import Polygon

## Fiona, ogr2ogr, pyplot demo
##  Live 8.5  *  darkblue-b
##

Demo Area Analysis in IPython Notebook

  • Use North Carolina sample data supplied on the OSGeo Live
  • Extract an area of interest
  • Extract a subset of a state soils record based on a bounding box (BBOX)
  • Plot geometry graphically
  • Buffer the geometry by a fixed amount; calculate the areas of intersection for soil types
  • Produce a chart of the totals with pyplot

In [ ]:
infile = '/usr/local/share/data/north_carolina/shape/urbanarea.shp'
infile_soils = '/usr/local/share/data/north_carolina/shape/soils_general.shp'

out_farmville = '/tmp/farmville_shp'
out_farmville_soils = '/tmp/farmville_soil_shp'

In [ ]:
## -- Call the gdal/ogr tool suite to extract and subset the original statewide dataset
##     use a feature of IPython Notebook to pass python variables to a command line

!rm -rf $out_farmville
!ogr2ogr -f 'ESRI Shapefile' $out_farmville $infile -where "NAME='Farmville'" -dim 2 -a_srs EPSG:3358

##-- advanced example -- raleigh is multiple polygons
#!rm -rf /tmp/raleigh1_shp
#!ogr2ogr -f 'ESRI Shapefile' /tmp/raleigh1_shp $infile -where "NAME='Raleigh'" -dim 2 -a_srs EPSG:3358

In [ ]:
## -- use the IPython notebook to get help on py module Fiona
# fiona?
# fiona.open?

In [ ]:
## open and inspect a Shapefile using Fiona
##  a POLYGON record could be very long, so dont print the record w/ geometry
with fiona.open( out_farmville ) as f:
    crs = f.crs
    print 'CRS:',crs
    rec = f.next()
    #print rec
    print 'SHAPELY REC:',rec.keys()
    print 'SHAPEFILE FLDS: ',rec['properties'].keys()

In [ ]:
with fiona.open( out_farmville,'r') as inp:
    #print dir(inp)  ## whats inside this object?
    #print inp.bounds
    
    ## take the bounding box of the area of interest
    ##  add 300 meters on each side for aesthetics
    ## (left, bottom, right, top)
    left_bnds     = inp.bounds[0] - 300
    bottom_bnds   = inp.bounds[1] - 300
    right_bnds    = inp.bounds[2] + 300
    top_bnds      = inp.bounds[3] + 300

## echo one variable to sanity check
#left_bnds

In [ ]:
## ogr2ogr ... -clipsrc [xmin ymin xmax ymax] ...

!rm -rf $out_farmville_soils
!ogr2ogr -f 'ESRI Shapefile' $out_farmville_soils $infile_soils -clipsrc $left_bnds $bottom_bnds $right_bnds $top_bnds -dim 2 -a_srs EPSG:3358

In [ ]:
## ----------------------------------------
## plot_multipolygon function
##   Author: Kelsey Jordahl, Enthought
##   Scipy 2013 geospatial tutorial

def plot_polygon(ax, poly, color='black'):
    a = np.asarray(poly.exterior)
    ax.add_patch(Polygon(a, facecolor=color, alpha=0.5))
    ax.plot(a[:, 0], a[:, 1], color=color)

def plot_multipolygon(ax, geom, color='red'):
    """ Can safely call with either Polygon or Multipolygon geometry
    """
    if geom.type == 'Polygon':
        plot_polygon(ax, geom, color)
    elif geom.type == 'MultiPolygon':
        for poly in geom.geoms:
            plot_polygon(ax, poly, color)
## ----------------------------------------

In [ ]:
soil_colors = ['green', 'brown', '#ceFFde']

dst_geoms = OrderedDict()
src_geoms = OrderedDict()

cnt = 0
with fiona.open( out_farmville ) as f:
    for rec in f:
        src_geoms[cnt] = shape(rec['geometry'])
        cnt += 1

cnt = 0
with fiona.open( out_farmville_soils ) as f:
    for rec in f:
        ## check the geometry type if desired
        ##  intersections may result in POINT or LINESTRING
        if rec['geometry']['type'] != 'Polygon':
            continue
        gsl = rec['properties']['GSL_NAME']
        dst_geoms[gsl] = shape(rec['geometry'])
        cnt += 1

In [ ]:
fig, ax = plt.subplots(figsize=(11,11))
plt.title("Soil Class and Urban Area via N. Carolina Sample Dataset (CC-by-SA) OSGeo")

cnt = 0
for key in dst_geoms :
    ## cnt mod (number of colors) is always a safe index into colors[]
    color = soil_colors[ cnt%len(soil_colors) ]
    plot_multipolygon(ax, dst_geoms[key], color=color)
    cnt += 1
    
cnt = 0
color = 'gray'
with fiona.open( out_farmville ) as f:
    for rec in f:
        plot_multipolygon(ax, src_geoms[cnt], color=color)
        cnt += 1

#ax.add_patch(Polygon( src_geoms[0].centroid, facecolor='black', alpha=0.5))

labels = ax.get_xticklabels() 
for label in labels: 
    label.set_rotation(90)

In [ ]:
## show all the variables defined so far
%whos

In [ ]:
## what geomtries have been created ?
print 'src_geoms len: ',len(src_geoms)
print 'dst_geoms len: ',len(dst_geoms )

In [ ]:
## buffer the urbanarea by N meters
##  save the result in the source-geometry list

src_geom_buffered = src_geoms[0].buffer(166)
src_geoms[1] = src_geom_buffered

In [ ]:
fig, ax = plt.subplots(figsize=(11,11))

cnt = 0
for key in dst_geoms :
    color = soil_colors[ cnt%len(soil_colors) ]
    plot_multipolygon(ax, dst_geoms[key], color=color)
    cnt += 1

plot_multipolygon(ax, src_geoms[1], color='gray')
plot_multipolygon(ax, src_geoms[0], color='gray')
    
labels = ax.get_xticklabels() 
for label in labels: 
    label.set_rotation(90)

In [ ]:
## take the intersection of each soil area against the buffered poly of interest
##  store into a third convenient list
res_geoms = OrderedDict()
for key in dst_geoms:
    res_geoms[key] = src_geom_buffered.intersection( dst_geoms[key] )

In [ ]:
fig, ax = plt.subplots(figsize=(11,11))

cnt = 0
for key in res_geoms :
    color = soil_colors[ cnt%len(soil_colors) ]
    plot_multipolygon(ax, res_geoms[key], color=color)
    cnt += 1
    
labels = ax.get_xticklabels() 
for label in labels: 
    label.set_rotation(90)

In [ ]:
## define a lookup table of mock attributes of each soil type
##  (substitute a real data source here)

soils_lkup = {
  'NC038' : { 'TEXTURE_CLAY':0.53, 'TEXTURE_SILT':0.33, 'TEXTURE_SAND':0.44 },
  'NC035' : { 'TEXTURE_CLAY':0.70, 'TEXTURE_SILT':0.33, 'TEXTURE_SAND':0.44 },
  'NC034' : { 'TEXTURE_CLAY':0.23, 'TEXTURE_SILT':0.74, 'TEXTURE_SAND':0.44 }
}

## get a rough total area for display purposes
sum_areas = 0.0
for key in res_geoms:
    sum_areas += int(res_geoms[key].area)

## record the area and percentage area in a convenient dictionary 
tdd = {}
for key in res_geoms:
    tdd[key] = soils_lkup[key]
    tdd[key]['area'] = int(res_geoms[key].area)
    tdd[key]['perc'] = int((res_geoms[key].area/sum_areas)*100)/100.0

In [ ]:
## Now all attributes for visualization are available in a single dict
##  in a larger system this could be delivered for serving graphical reports
tdd

In [ ]:
## What matplotlib pylot graphs are available ?
## http://matplotlib.org/api/pyplot_api.html
##
##   Use IPython built-in help to discover pie chart attributes

plt.pie?

In [ ]:
# The slices will be ordered and plotted counter-clockwise.
labels = [ 'NC034', 'NC035', 'NC038' ]
sizes = [  tdd['NC034']['perc']*100, tdd['NC035']['perc']*100, tdd['NC038']['perc']*100 ]
pie_colors = ['lightcoral', '#eeefee', 'yellowgreen']
# only "explode" the 2nd slice (i.e. 'NC035')
explode = (0, 0.1, 0)

plt.pie(sizes, explode=explode, labels=labels, colors=pie_colors,
        autopct='%1.1f%%', shadow=True, startangle=90)
# Set aspect ratio to be equal so that pie is drawn as a circle.
plt.axis('equal')

plt.show()

In [ ]: