From 95609c3770bcd0b43a000f2a1293b86748755e73 Mon Sep 17 00:00:00 2001 From: vagrant Date: Wed, 18 Sep 2013 05:44:06 +0000 Subject: [PATCH] initial commit --- .gitignore | 12 ++++ README.md | 63 ++++++++++++++++++++ scripts/coverage.sh | 1 + scripts/provision.sh | 16 +++++ scripts/rasterstats | 47 +++++++++++++++ setup.cfg | 5 ++ setup.py | 49 +++++++++++++++ src/rasterstats/__init__.py | 5 ++ src/rasterstats/main.py | 112 +++++++++++++++++++++++++++++++++++ src/rasterstats/utils.py | 16 +++++ tests/__init__.py | 0 tests/conftest.py | 3 + tests/data/lines.dbf | Bin 0 -> 87 bytes tests/data/lines.prj | 16 +++++ tests/data/lines.qpj | 1 + tests/data/lines.shp | Bin 0 -> 436 bytes tests/data/lines.shx | Bin 0 -> 116 bytes tests/data/points.dbf | Bin 0 -> 98 bytes tests/data/points.prj | 16 +++++ tests/data/points.qpj | 1 + tests/data/points.shp | Bin 0 -> 184 bytes tests/data/points.shx | Bin 0 -> 124 bytes tests/data/points_noproj.dbf | Bin 0 -> 98 bytes tests/data/points_noproj.shp | Bin 0 -> 184 bytes tests/data/points_noproj.shx | Bin 0 -> 124 bytes tests/data/polygons.dbf | Bin 0 -> 87 bytes tests/data/polygons.prj | 16 +++++ tests/data/polygons.qpj | 1 + tests/data/polygons.shp | Bin 0 -> 436 bytes tests/data/polygons.shx | Bin 0 -> 116 bytes tests/data/slope.tif | Bin 0 -> 27542 bytes tests/test_zonal.py | 30 ++++++++++ 32 files changed, 410 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 scripts/coverage.sh create mode 100644 scripts/provision.sh create mode 100644 scripts/rasterstats create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 src/rasterstats/__init__.py create mode 100644 src/rasterstats/main.py create mode 100644 src/rasterstats/utils.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/data/lines.dbf create mode 100644 tests/data/lines.prj create mode 100644 tests/data/lines.qpj create mode 100644 tests/data/lines.shp create mode 100644 tests/data/lines.shx create mode 100644 tests/data/points.dbf create mode 100644 tests/data/points.prj create mode 100644 tests/data/points.qpj create mode 100644 tests/data/points.shp create mode 100644 tests/data/points.shx create mode 100644 tests/data/points_noproj.dbf create mode 100644 tests/data/points_noproj.shp create mode 100644 tests/data/points_noproj.shx create mode 100644 tests/data/polygons.dbf create mode 100644 tests/data/polygons.prj create mode 100644 tests/data/polygons.qpj create mode 100644 tests/data/polygons.shp create mode 100644 tests/data/polygons.shx create mode 100644 tests/data/slope.tif create mode 100644 tests/test_zonal.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4223611 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.pyc +.coverage +TEST*.xml +build/* +dist/* +*.swp +.DS_Store +*.orig +*.egg-info +MANIFEST +Vagrantfile +.vagrant/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..86df251 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# python-raster-stats + +Summary statistics of raster dataset values based on vector geometries. + +Docs (API, Topics) +Examples +Build status +Test coverage + +## Quickstart + +You've got a raster dataset representing elevation and a vector dataset representing county boundaries. +Show the average, min and max elevation for each county +Show percentage of land cover by county +Output to csv (via CLI) +Pass geometries via wkt or wkb (single & list) +Integrate with other python packages via __geo_interface__ + +## Features + +* Raster data support: + * Any continuous raster band supported by GDAL +* Vector data support: + * OGR layer +* Python module (returns built in python data structures - list of dicts) +* Depends on GDAL, GEOS, shapely and numpy +* Full coverage unit testing + +## Issues +To report a bug via github issues: provide smallest possible raster, vector and code to reproduce it + +## Docs + +## TODO +* respects null/no-data values +* covering edge cases for input datasets +* command line interface which returns csv data and optionally copies over original vector attrs +* supports categorical +* Vector data: + * Points, Lines, Polygon and Multi-* geometries + * Can specify: + * OGR layer + * single geoms or list of geometries represented by wkts, wkbs or any object that supports the __geo_interface__ +* projection support +* pip installable +* python 2 & 3 support +* buildthedocs OR use some sort of literate programming +* travis-ci and https://coveralls.io/info/features +* Examples for PyShp, GeoDjango, Fiona, Path to OGR resource, ArcPy (as Ipython notebooks?) +* reproject on the fly using projection metadata +* heavily profiled using a wide range of input data. The resulting heuristics used to automatically configure for optimal performance. Optimzation heuristic for determining global_src_extent - number of features - extent of features - available system memory vs raster_extent +* CLI: pivots for categorical +* support parallel processing on multiple CPUs via the `multiprocessing` approach +* zonal majority (http://stackoverflow.com/questions/6252280/find-the-most-frequent-number-in-a-numpy-vector) + +## Alternatives + +Grass r.stats +R spatialdataframe +starspan +zonal statistics arcpy +QGIS + diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100644 index 0000000..9d15f67 --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1 @@ +coverage run setup.py test && coverage report -m --include=*rasterstats* diff --git a/scripts/provision.sh b/scripts/provision.sh new file mode 100644 index 0000000..da8db18 --- /dev/null +++ b/scripts/provision.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# Provision base software required for running raster_stats + +#apt-get install -y python-software-properties +#add-apt-repository -y ppa:ubuntugis/ubuntugis-unstable + +apt-get update + +apt-get install -y libgdal-dev gdal-bin \ + python-gdal python-pip python-numpy \ + libspatialindex-dev libspatialindex1 \ + build-essential git atop python-dev python-dateutil + +cd /usr/local/src/python-raster-stats +sudo pip install shapely pytest coverage +python setup.py develop diff --git a/scripts/rasterstats b/scripts/rasterstats new file mode 100644 index 0000000..ac941a1 --- /dev/null +++ b/scripts/rasterstats @@ -0,0 +1,47 @@ +#!/bin/env python +""" +rasterstats + +Usage: + rasterstats COMMAND DATASET [ARGS ...] + rasterstats (-h | --help) + rasterstats --version + +Options: + -h --help Show this screen. + --version Show version. +""" +import sys +from docopt import docopt +from rasterstats import raster_stats + + +def guess_type(thing): + try: + return float(thing) + except: + pass + return str(thing) + + +if __name__ == '__main__': + opt = docopt(__doc__, version='geofu 0.0.1') + if opt['DATASET'] == "-": + # use stdin + opt['DATASET'] = sys.stdin.readlines()[-1].strip() + lyr = load(opt['DATASET']) + args = [] + kwargs = {} + for arg in opt['ARGS']: + if "=" in arg: + k, v = arg.split("=") + v = guess_type(v) + kwargs[k] = v + else: + arg = guess_type(arg) + args.append(arg) + res = getattr(lyr, opt['COMMAND'])(*args, **kwargs) + if isinstance(res, lyr.__class__): + print res.path + else: + print res diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..875240f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +# content of setup.cfg +[pytest] +norecursedirs = examples* src* scripts* +addopts = -rf -v +#addopts = --maxfail=1 --pdb -rf -v diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..de722db --- /dev/null +++ b/setup.py @@ -0,0 +1,49 @@ +import os +import sys +from setuptools import setup +from setuptools.command.test import test as TestCommand + + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + + +class PyTest(TestCommand): + def finalize_options(self): + TestCommand.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + import pytest + errno = pytest.main(self.test_args) + sys.exit(errno) + + +setup( + name="rasterstats", + version="0.1a", + author="Matthew Perry", + author_email="perrygeo@gmail.com", + description=("Summarize geospatial raster datasets based on vector geometries"), + license="BSD", + keywords="gis geospatial geographic raster vector zonal statistics", + url="https://github.com/perrygeo/python-raster-stats", + package_dir={'': 'src'}, + packages=['rasterstats'], + long_description=read('README.md'), + install_requires=[ + 'shapely', + 'numpy', + 'GDAL', + # pandas, pyproj? + ], + scripts=['scripts/rasterstats'], + tests_require=['pytest'], + cmdclass = {'test': PyTest}, + classifiers=[ + "Development Status :: 1 - Planning", + "Topic :: Utilities", + "License :: OSI Approved :: BSD License", + ], +) diff --git a/src/rasterstats/__init__.py b/src/rasterstats/__init__.py new file mode 100644 index 0000000..4abe42d --- /dev/null +++ b/src/rasterstats/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from .main import raster_stats + +__all__ = ['raster_stats'] + diff --git a/src/rasterstats/main.py b/src/rasterstats/main.py new file mode 100644 index 0000000..37045c7 --- /dev/null +++ b/src/rasterstats/main.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +from shapely.geometry import mapping, shape +import numpy as np +from osgeo import gdal, ogr +from osgeo.gdalconst import GA_ReadOnly +from .utils import bbox_to_pixel_offsets +gdal.PushErrorHandler('CPLQuietErrorHandler') + + +def raster_stats(vector, raster, layer_num=0, band_num=1, nodata_value=None, + global_src_extent=False): + rds = gdal.Open(raster, GA_ReadOnly) + assert(rds) + rb = rds.GetRasterBand(band_num) + rgt = rds.GetGeoTransform() + + if nodata_value is not None: + nodata_value = float(nodata_value) + rb.SetNoDataValue(nodata_value) + + vds = ogr.Open(vector, GA_ReadOnly) + assert(vds) + vlyr = vds.GetLayer(layer_num) + + # create an in-memory numpy array of the source raster data + # covering the whole extent of the vector layer + if global_src_extent: + # use global source extent + # useful only when disk IO or raster scanning inefficiencies are your limiting factor + # advantage: reads raster data in one pass + # disadvantage: large vector extents may have big memory requirements + src_offset = bbox_to_pixel_offsets(rgt, vlyr.GetExtent()) + src_array = rb.ReadAsArray(*src_offset) + + # calculate new geotransform of the layer subset + new_gt = ( + (rgt[0] + (src_offset[0] * rgt[1])), + rgt[1], + 0.0, + (rgt[3] + (src_offset[1] * rgt[5])), + 0.0, + rgt[5] + ) + + mem_drv = ogr.GetDriverByName('Memory') + driver = gdal.GetDriverByName('MEM') + + #geometries = iter(vlyr, None) + #for feat in geometries: + stats = [] + + # Loop through geometries + feat = vlyr.GetNextFeature() + while feat: + if not global_src_extent: + # use local source extent + # fastest option when you have fast disks and well indexed raster (ie tiled Geotiff) + # advantage: each feature uses the smallest raster chunk + # disadvantage: lots of reads on the source raster + src_offset = bbox_to_pixel_offsets(rgt, feat.geometry().GetEnvelope()) + src_array = rb.ReadAsArray(*src_offset) + + # calculate new geotransform of the feature subset + new_gt = ( + (rgt[0] + (src_offset[0] * rgt[1])), + rgt[1], + 0.0, + (rgt[3] + (src_offset[1] * rgt[5])), + 0.0, + rgt[5] + ) + + # Create a temporary vector layer in memory + mem_ds = mem_drv.CreateDataSource('out') + mem_layer = mem_ds.CreateLayer('poly', None, ogr.wkbPolygon) + mem_layer.CreateFeature(feat.Clone()) + + # Rasterize it + rvds = driver.Create('', src_offset[2], src_offset[3], 1, gdal.GDT_Byte) + rvds.SetGeoTransform(new_gt) + gdal.RasterizeLayer(rvds, [1], mem_layer, burn_values=[1]) + rv_array = rvds.ReadAsArray() + + # Mask the source data array with our current feature + # we take the logical_not to flip 0<->1 to get the correct mask effect + # we also mask out nodata values explictly + masked = np.ma.MaskedArray( + src_array, + mask=np.logical_or( + src_array == nodata_value, + np.logical_not(rv_array) + ) + ) + + feature_stats = { + 'min': float(masked.min()), + 'mean': float(masked.mean()), + 'max': float(masked.max()), + 'std': float(masked.std()), + 'sum': float(masked.sum()), + 'count': int(masked.count()), + 'fid': int(feat.GetFID())} + + stats.append(feature_stats) + + rvds = None + mem_ds = None + feat = vlyr.GetNextFeature() + + vds = None + rds = None + return stats diff --git a/src/rasterstats/utils.py b/src/rasterstats/utils.py new file mode 100644 index 0000000..82478c6 --- /dev/null +++ b/src/rasterstats/utils.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +def bbox_to_pixel_offsets(gt, bbox): + originX = gt[0] + originY = gt[3] + pixel_width = gt[1] + pixel_height = gt[5] + x1 = int((bbox[0] - originX) / pixel_width) + x2 = int((bbox[1] - originX) / pixel_width) + 1 + + y1 = int((bbox[3] - originY) / pixel_height) + y2 = int((bbox[2] - originY) / pixel_height) + 1 + + xsize = x2 - x1 + ysize = y2 - y1 + return (x1, y1, xsize, ysize) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b554816 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,3 @@ +import logging +import sys +logging.basicConfig(stream=sys.stderr, level=logging.INFO) diff --git a/tests/data/lines.dbf b/tests/data/lines.dbf new file mode 100644 index 0000000000000000000000000000000000000000..ac8457820bae7f137af807b7b921c5f600d35bb3 GIT binary patch literal 87 rcmZRMXP07PU|?`$;0BUtAe@0AGX*Z@2V!x-xex}g0vs5^Sqeq~v>63H literal 0 HcmV?d00001 diff --git a/tests/data/lines.prj b/tests/data/lines.prj new file mode 100644 index 0000000..932909e --- /dev/null +++ b/tests/data/lines.prj @@ -0,0 +1,16 @@ +PROJCS["unnamed", + GEOGCS["GRS 1980(IUGG, 1980)", + DATUM["unknown", + SPHEROID["GRS80",6378137,298.257222101]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433]], + PROJECTION["Albers_Conic_Equal_Area"], + PARAMETER["standard_parallel_1",43], + PARAMETER["standard_parallel_2",48], + PARAMETER["latitude_of_center",34], + PARAMETER["longitude_of_center",-120], + PARAMETER["false_easting",600000], + PARAMETER["false_northing",0], + UNIT["metre",1, + AUTHORITY["EPSG","9001"]]] + diff --git a/tests/data/lines.qpj b/tests/data/lines.qpj new file mode 100644 index 0000000..5fbc831 --- /dev/null +++ b/tests/data/lines.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/tests/data/lines.shp b/tests/data/lines.shp new file mode 100644 index 0000000000000000000000000000000000000000..dfe374a476e4bb809f2826cc55322afc6ae44eb2 GIT binary patch literal 436 zcmZQzQ0HR64sN|*W?%p!hOo)3B1hlxI%;f|_xxO^=P0SuadDpjpQA~w?z)^VJxA=? zQA8PmyabS0@iVsMl(X>RGz+AV6RH(v54ycx%p&u=8uT3Nl)bjH1Kq*Xm)NM^sOLC; zv$ET6Hap5cA qjv@*&h6~v&B)v>9xtcp7EjPa)>;Z}cT>#Msvy-PUu~ENK&k+EZogq~K literal 0 HcmV?d00001 diff --git a/tests/data/points.shx b/tests/data/points.shx new file mode 100644 index 0000000000000000000000000000000000000000..740f6b2e37ca30b67dbfc24acf9537eef68a6220 GIT binary patch literal 124 zcmZQzQ0HR64(whqGcYg$<)%6AX#e?~*HPDEres{bo+H1~RD+lNe2zxf3qCrx>p5cA Rjv{IV-8UQHf4#5Bb literal 0 HcmV?d00001 diff --git a/tests/data/points_noproj.dbf b/tests/data/points_noproj.dbf new file mode 100644 index 0000000000000000000000000000000000000000..3aee2835477f4a0fd80d129f2d3096912182e6a7 GIT binary patch literal 98 ucmZRMXP07TU|?`$;0BUtAe@0AGX*Z@2V!x-xex}g0vs5^Sqer7rZE8abOy2j literal 0 HcmV?d00001 diff --git a/tests/data/points_noproj.shp b/tests/data/points_noproj.shp new file mode 100644 index 0000000000000000000000000000000000000000..3fc12bd04da047f9310631626bc61d565b09f320 GIT binary patch literal 184 zcmZQzQ0HR64q{#~GcYg$<)%6AX#e?~*HPDEres{bo+H1~RD+lNe2zxf3qCrx>p5cA qjv@*&h6~v&B)v>9xtcp7EjPa)>;Z}cT>#Msvy-PUu~ENK&k+EZogq~K literal 0 HcmV?d00001 diff --git a/tests/data/points_noproj.shx b/tests/data/points_noproj.shx new file mode 100644 index 0000000000000000000000000000000000000000..740f6b2e37ca30b67dbfc24acf9537eef68a6220 GIT binary patch literal 124 zcmZQzQ0HR64(whqGcYg$<)%6AX#e?~*HPDEres{bo+H1~RD+lNe2zxf3qCrx>p5cA Rjv{IV-8UQHf4#5Bb literal 0 HcmV?d00001 diff --git a/tests/data/polygons.dbf b/tests/data/polygons.dbf new file mode 100644 index 0000000000000000000000000000000000000000..ac8457820bae7f137af807b7b921c5f600d35bb3 GIT binary patch literal 87 rcmZRMXP07PU|?`$;0BUtAe@0AGX*Z@2V!x-xex}g0vs5^Sqeq~v>63H literal 0 HcmV?d00001 diff --git a/tests/data/polygons.prj b/tests/data/polygons.prj new file mode 100644 index 0000000..932909e --- /dev/null +++ b/tests/data/polygons.prj @@ -0,0 +1,16 @@ +PROJCS["unnamed", + GEOGCS["GRS 1980(IUGG, 1980)", + DATUM["unknown", + SPHEROID["GRS80",6378137,298.257222101]], + PRIMEM["Greenwich",0], + UNIT["degree",0.0174532925199433]], + PROJECTION["Albers_Conic_Equal_Area"], + PARAMETER["standard_parallel_1",43], + PARAMETER["standard_parallel_2",48], + PARAMETER["latitude_of_center",34], + PARAMETER["longitude_of_center",-120], + PARAMETER["false_easting",600000], + PARAMETER["false_northing",0], + UNIT["metre",1, + AUTHORITY["EPSG","9001"]]] + diff --git a/tests/data/polygons.qpj b/tests/data/polygons.qpj new file mode 100644 index 0000000..5fbc831 --- /dev/null +++ b/tests/data/polygons.qpj @@ -0,0 +1 @@ +GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] diff --git a/tests/data/polygons.shp b/tests/data/polygons.shp new file mode 100644 index 0000000000000000000000000000000000000000..80d58e8c72ae1f66163096057270420064438672 GIT binary patch literal 436 zcmZQzQ0HR64sN|*W?*0i%Bfv;n|t~muVb2%L2hTOp5q7RJ-!*de2z8`lP5`b=s9B7 zjv~qkObRk{3{VGp4G1B2vW!n)e5tB;SJTYB%s_&=0Mw< zj}U5MdX26Zd~^oNqw^PQW!MM;&4$?pGw%gU?$v!jyJ38o`9N!#VD60B`8~pvoe!s< zk==RX_WP59Tzrl#37i33!0_(cbE7X0QJMd7iPB^ NXHT+Jo1P=O`2aisf+GL` literal 0 HcmV?d00001 diff --git a/tests/data/polygons.shx b/tests/data/polygons.shx new file mode 100644 index 0000000000000000000000000000000000000000..a7d2fe5d56ba00a9c6b07cb20b4993820f4a61e4 GIT binary patch literal 116 zcmZQzQ0HR64y;}Ah)ws&+!BE9^VXJK1Z8}$&(~I^c=Bk PM-epw@&bUk4MYO~w?q#s literal 0 HcmV?d00001 diff --git a/tests/data/slope.tif b/tests/data/slope.tif new file mode 100644 index 0000000000000000000000000000000000000000..c0be86fef27bf3d72bc2b8bbe7c019be24b4df7a GIT binary patch literal 27542 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