From 1a376d64de28debd3b17b108c78b2578982828fb Mon Sep 17 00:00:00 2001 From: Carson Farmer Date: Sun, 21 Jul 2013 16:36:38 -0400 Subject: [PATCH 1/2] add initial support for writing to geospatial data formats via fiona --- geopandas/geodataframe.py | 56 ++++++++++++++++++++++++++++++++++++-- geopandas/geoseries.py | 30 ++++++++++++++++++-- tests/test_geodataframe.py | 12 ++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py index 3d95caa..3ac2e48 100644 --- a/geopandas/geodataframe.py +++ b/geopandas/geodataframe.py @@ -1,5 +1,5 @@ -from collections import defaultdict -import json +from collections import defaultdict, OrderedDict +import json, os import fiona from pandas import DataFrame @@ -8,6 +8,8 @@ from shapely.geometry import mapping, shape from geopandas import GeoSeries from plotting import plot_dataframe +import numpy as np + class GeoDataFrame(DataFrame): """ @@ -70,6 +72,56 @@ class GeoDataFrame(DataFrame): {'type': 'FeatureCollection', 'features': [feature(i, row) for i, row in self.iterrows()]}, **kwargs ) + + def to_file(self, filename, driver="ESRI Shapefile", **kwargs): + """ + Write GeoSeries geometries (and optionally a DataFrame) to an OGR + data source + + A dictionary of supported OGR providers is available via: + >>> import fiona + >>> fiona.supported_drivers + + Parameters + ---------- + filename : string + File path or file handle to write to. + driver : string, default 'ESRI Shapefile' + The OGR format driver used to write the vector file. + + The *kwargs* are passed to fiona.open, and can be used to write + to multi-layer data, store data within archives (zip files), etc. + """ + def convert_type(in_type): + if in_type == object: + return 'str' + return type(np.asscalar(np.zeros(1, in_type))).__name__ + + def feature(i, row): + return { + 'id': str(i), + 'type': 'Feature', + 'properties': { + k: v for k, v in row.iteritems() if k != 'geometry'}, + 'geometry': mapping(row['geometry']) } + + properties = OrderedDict([(col, convert_type(_type)) for col, _type + in zip(self.columns, self.dtypes) if col!='geometry']) + # Need to check geom_types before we write to file... + # Some (most?) providers expect a single geometry type: + # Point, LineString, or Polygon + geom_types = self['geometry'].geom_type.unique() + from os.path import commonprefix # To find longest common prefix + geom_type = commonprefix([g[::-1] for g in geom_types])[::-1] # Reverse + if geom_type == '': # No common suffix = mixed geometry types + raise ValueError("Geometry column cannot contains mutiple " + "geometry types when writing to file.") + schema = {'geometry': geom_type, 'properties': properties} + filename = os.path.abspath(os.path.expanduser(filename)) + with fiona.open(filename, 'w', driver=driver, crs=self.crs, + schema=schema, **kwargs) as c: + for i, row in self.iterrows(): + c.write(feature(i, row)) def to_crs(self, crs=None, epsg=None, inplace=False): """Transform geometries to a new coordinate reference system diff --git a/geopandas/geoseries.py b/geopandas/geoseries.py index d8504cd..cbfb148 100644 --- a/geopandas/geoseries.py +++ b/geopandas/geoseries.py @@ -46,10 +46,26 @@ class GeoSeries(Series): self.crs = crs @classmethod - def from_file(cls, filename): - """Alternate constructor to create a GeoSeries from a file""" + def from_file(cls, filename, **kwargs): + """ + Alternate constructor to create a GeoSeries from a file + + Parameters + ---------- + + filename : str + File path or file handle to read from. Depending on which kwargs + are included, the content of filename may vary, see: + http://toblerity.github.io/fiona/README.html#usage + for usage details. + kwargs : key-word arguments + These arguments are passed to fiona.open, and can be used to + access multi-layer data, data stored within archives (zip files), + etc. + + """ geoms = [] - with fiona.open(filename) as f: + with fiona.open(filename, **kwargs) as f: crs = f.crs for rec in f: geoms.append(shape(rec['geometry'])) @@ -57,6 +73,14 @@ class GeoSeries(Series): g.crs = crs return g + def to_file(self, filename, driver="ESRI Shapefile", **kwargs): + from geopandas import GeoDataFrame + data = GeoDataFrame({"geometry": self, + "id":self.index.values}, + index=self.index) + data.crs = self.crs + data.to_file(filename, driver, **kwargs) + # # Internal methods # diff --git a/tests/test_geodataframe.py b/tests/test_geodataframe.py index d85c185..9037311 100644 --- a/tests/test_geodataframe.py +++ b/tests/test_geodataframe.py @@ -2,6 +2,8 @@ import unittest import json import numpy as np +import tempfile + from geopandas import GeoDataFrame class TestDataFrame(unittest.TestCase): @@ -23,3 +25,13 @@ class TestDataFrame(unittest.TestCase): data = json.loads(text) self.assertTrue(data['type'] == 'FeatureCollection') self.assertTrue(len(data['features']) == 5) + + def test_to_file(self): + with tempfile.NamedTemporaryFile(suffix='.shp') as t: + self.df.to_file(t.name) + # Read layer back in? + df = GeoDataFrame.from_file(t.name) + self.assertTrue('geometry' in df) + self.assertTrue(len(df) == 5) + self.assertTrue(np.alltrue(df['BoroName'].values == np.array(['Staten Island', + 'Queens', 'Brooklyn', 'Manhattan', 'Bronx']))) From d015688d2758f3ed0082aaace915755a9ca6d01c Mon Sep 17 00:00:00 2001 From: Carson Farmer Date: Sun, 21 Jul 2013 16:44:22 -0400 Subject: [PATCH 2/2] update doc string for --- geopandas/geodataframe.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py index 3ac2e48..797da06 100644 --- a/geopandas/geodataframe.py +++ b/geopandas/geodataframe.py @@ -75,8 +75,7 @@ class GeoDataFrame(DataFrame): def to_file(self, filename, driver="ESRI Shapefile", **kwargs): """ - Write GeoSeries geometries (and optionally a DataFrame) to an OGR - data source + Write this GeoDataFrame to an OGR data source A dictionary of supported OGR providers is available via: >>> import fiona @@ -89,7 +88,7 @@ class GeoDataFrame(DataFrame): driver : string, default 'ESRI Shapefile' The OGR format driver used to write the vector file. - The *kwargs* are passed to fiona.open, and can be used to write + The *kwargs* are passed to fiona.open and can be used to write to multi-layer data, store data within archives (zip files), etc. """ def convert_type(in_type):