Skip to content

Commit

Permalink
Merge pull request #27 from tulsawebdevs/case-insensitive-mgmtcmd
Browse files Browse the repository at this point in the history
Case insensitive mgmtcmd
  • Loading branch information
ryanmark committed Jun 13, 2013
2 parents 05c0bba + e79c194 commit f760ffc
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 37 deletions.
27 changes: 19 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
=============================
The Newsapps Boundary Service
The Boundary Service
=============================

The Boundary Service is a ready-to-deploy system for aggregating regional boundary data (from shapefiles) and republishing via a RESTful JSON API. It is packaged as a pluggable Django application so that it can be easily integrated into any project.

Open source examples of implementing the boundary service:

* `hacktyler-boundaryservice <https://github.com/hacktyler/hacktyler-boundaryservice>`_: A complete example, including deployment.
* `blank-boundaryservice <https://github.com/opennorth/blank-boundaryservice>`_: No extra cruft! A great starting point! (Maintained by `James McKinney <https://github.com/jpmckinney>`_.)
* `News Apps Boundary Service <https://github.com/newsapps/django-boundaryservice>`_: The inspiration and parent

Installation
============

Install and setup PostgreSQL, PostGIS, postgres-postgis, postgres-dev packages.

Next, create a PostgreSQL database, as the Boundary Service depends on PostGIS::
$ DB=EXAMPLE_DB_NAME
$ createdb -h localhost $DB
$ createlang -h localhost plpgsql $DB

To spatially-enable the database, you must load PostGIS definitions files. You can use `locate` (Linux) or `mdfind` (OS X) to find these files::

psql -h localhost -d $DB -f postgis.sql
psql -h localhost -d $DB -f spatial_ref_sys.sql

Using pip::

$ pip install django-boundaryservice
$ pip install git+git://github.com/tulsawebdevs/django-boundaryservice.git
$ python manage.py syncdb

Add the following to INSTALLED_APPS in your settings.py

Expand All @@ -36,7 +47,7 @@ Adding data

To add data you will first need to add a shapefile and its related files (prj, dbf, etc.) to the data/shapefiles directory. Shapefiles and your definitions.py go into this folder. See the `hacktyler demo site <https://github.com/hacktyler/hacktyler-boundaryservice>`_ for a complete example.

If you havn't already, you can create your definitions file using a management command::
If you are choosing not to upload shapefiles via the Shapefile model, you can create your definitions file using a management command::

$ python manage.py startshapedefinitions

Expand Down Expand Up @@ -70,7 +81,7 @@ As a matter of best practice when shapefiles have been acquired from government
Credits
=======

The Boundary Service is a product of the `News Applications team <http://blog.apps.chicagotribune.com>`_ at the Chicago Tribune. Core development was done by `Christopher Groskopf <http://twitter.com/onyxfish>`_ and `Ryan Nagle <http://twitter.com/ryannagle>`_.
The Boundary Service is a product of the `News Applications team <http://blog.apps.chicagotribune.com>`_ at the Chicago Tribune. Core development was done by `Christopher Groskopf <http://twitter.com/onyxfish>`_ and `Ryan Nagle <http://twitter.com/ryannagle>`_. Modified by `Jeremy Satterfield <https://plus.google.com/103708024549095350813/about>`_ as part of `OklahomaData.org <http://www.oklahomadata.org`_.

License
=======
Expand Down
74 changes: 45 additions & 29 deletions boundaryservice/management/commands/loadshapefiles.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import logging
import logging
log = logging.getLogger('boundaries.api.load_shapefiles')
from optparse import make_option
import os, os.path
Expand All @@ -8,7 +8,8 @@
from tempfile import mkdtemp

from django.conf import settings
from django.contrib.gis.gdal import CoordTransform, DataSource, OGRGeometry, OGRGeomType
from django.contrib.gis.gdal import (CoordTransform, DataSource, OGRGeometry,
OGRGeomType)
from django.core.management.base import BaseCommand
from django.db import connections, DEFAULT_DB_ALIAS, transaction

Expand All @@ -23,15 +24,18 @@ class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option('-c', '--clear', action='store_true', dest='clear',
help='Clear all jurisdictions in the DB.'),
make_option('-d', '--data-dir', action='store', dest='data_dir',
make_option('-d', '--data-dir', action='store', dest='data_dir',
default=DEFAULT_SHAPEFILES_DIR,
help='Load shapefiles from this directory'),
make_option('-e', '--except', action='store', dest='except',
default=False, help='Don\'t load these kinds of Areas, comma-delimited.'),
default=False,
help='Don\'t load these kinds of Areas, comma-delimited.'),
make_option('-o', '--only', action='store', dest='only',
default=False, help='Only load these kinds of Areas, comma-delimited.'),
default=False,
help='Only load these kinds of Areas, comma-delimited.'),
make_option('-u', '--database', action='store', dest='database',
default=DEFAULT_DB_ALIAS, help='Specify a database to load shape data into.'),
default=DEFAULT_DB_ALIAS,
help='Specify a database to load shape data into.'),
)

def get_version(self):
Expand All @@ -43,16 +47,19 @@ def handle(self, *args, **options):
from definitions import SHAPEFILES

if options['only']:
only = options['only'].split(',')
# TODO: stripping whitespace here because optparse doesn't handle it correctly
sources = [s for s in SHAPEFILES if s.replace(' ', '') in only]
only = options['only'].upper().split(',')
# TODO: stripping whitespace here because optparse doesn't handle
# it correctly
sources = [s for s in SHAPEFILES
if s.replace(' ', '').upper() in only]
elif options['except']:
exceptions = options['except'].upper().split(',')
# See above
sources = [s for s in SHAPEFILES if s.replace(' ', '') not in exceptions]
sources = [s for s in SHAPEFILES
if s.replace(' ', '').upper() not in exceptions]
else:
sources = [s for s in SHAPEFILES]

for kind, config in SHAPEFILES.items():
if kind not in sources:
log.info('Skipping %s.' % kind)
Expand All @@ -68,18 +75,19 @@ def load_set(self, kind, config, options):

if options['clear']:
bset = None

try:
bset = BoundarySet.objects.get(name=kind)

if bset:
log.info('Clearing old %s.' % kind)
bset.boundaries.all().delete()
bset.delete()

log.info('Loading new %s.' % kind)
except BoundarySet.DoesNotExist:
log.info("No existing boundary set of kind [%s] so nothing to delete" % kind)
log.info('No existing boundary set of kind [%s] so nothing to '
'delete' % kind)

path = os.path.join(options['data_dir'], config['file'])
datasources = create_datasources(path)
Expand All @@ -106,17 +114,20 @@ def load_set(self, kind, config, options):
log.info("Loading %s from %s" % (kind, datasource.name))
# Assume only a single-layer in shapefile
if datasource.layer_count > 1:
log.warn('%s shapefile [%s] has multiple layers, using first.' % (datasource.name, kind))
log.warn('%s shapefile [%s] has multiple layers, using first.'
% (datasource.name, kind))
layer = datasource[0]
self.add_boundaries_for_layer(config, layer, bset, options['database'])

bset.count = Boundary.objects.filter(set=bset).count() # sync this with reality
self.add_boundaries_for_layer(config, layer, bset,
options['database'])
# sync this with reality
bset.count = Boundary.objects.filter(set=bset).count()
bset.save()
log.info('%s count: %i' % (kind, bset.count))

def polygon_to_multipolygon(self, geom):
"""
Convert polygons to multipolygons so all features are homogenous in the database.
Convert polygons to multipolygons so all features are homogenous in the
database.
"""
if geom.__class__.__name__ == 'Polygon':
g = OGRGeometry(OGRGeomType('MultiPolygon'))
Expand All @@ -131,7 +142,8 @@ def add_boundaries_for_layer(self, config, layer, bset, database):
# Get spatial reference system for the postgis geometry field
geometry_field = Boundary._meta.get_field_by_name(GEOMETRY_COLUMN)[0]
SpatialRefSys = connections[database].ops.spatial_ref_sys()
db_srs = SpatialRefSys.objects.using(database).get(srid=geometry_field.srid).srs
db_srs = SpatialRefSys.objects.using(database).get(
srid=geometry_field.srid).srs

if 'srid' in config and config['srid']:
layer_srs = SpatialRefSys.objects.get(srid=config['srid']).srs
Expand All @@ -154,7 +166,8 @@ def add_boundaries_for_layer(self, config, layer, bset, database):
geometry = self.polygon_to_multipolygon(feature.geom)
geometry.transform(transformer)

# Preserve topology prevents a shape from ever crossing over itself.
# Preserve topology prevents a shape from ever crossing over
# itself.
simple_geometry = geometry.geos.simplify(simplification,
preserve_topology=True)

Expand All @@ -165,20 +178,23 @@ def add_boundaries_for_layer(self, config, layer, bset, database):
metadata = {}

for field in layer.fields:

# Decode string fields using encoding specified in definitions config

# Decode string fields using encoding specified in definitions
# config
if config['encoding'] != '':
try:
metadata[field] = feature.get(field).decode(config['encoding'])
# Only strings will be decoded, get value in normal way if int etc.
metadata[field] = feature.get(field).decode(
config['encoding'])
# Only strings will be decoded, get value in normal way if
# int etc.
except AttributeError:
metadata[field] = feature.get(field)
else:
metadata[field] = feature.get(field)

external_id = config['ider'](feature)
feature_name = config['namer'](feature)

# If encoding is specified, decode id and feature name
if config['encoding'] != '':
external_id = external_id.decode(config['encoding'])
Expand Down Expand Up @@ -206,7 +222,7 @@ def create_datasources(path):

if path.endswith('.shp'):
return [DataSource(path)]

# assume it's a directory...
sources = []
for fn in os.listdir(path):
Expand All @@ -220,7 +236,7 @@ def create_datasources(path):
def temp_shapefile_from_zip(zip_path):
"""
Given a path to a ZIP file, unpack it into a temp dir and return the path
to the shapefile that was in there. Doesn't clean up after itself unless
to the shapefile that was in there. Doesn't clean up after itself unless
there was an error.
If you want to cleanup later, you can derive the temp dir from this path.
Expand All @@ -246,5 +262,5 @@ def temp_shapefile_from_zip(zip_path):
os.unlink(os.path.join(tempdir, file))
os.rmdir(tempdir)
raise ValueError("No shapefile found in zip")

return shape_path

0 comments on commit f760ffc

Please sign in to comment.