diff --git a/geopandas/geodataframe.py b/geopandas/geodataframe.py index 34c0d0f..a751ada 100644 --- a/geopandas/geodataframe.py +++ b/geopandas/geodataframe.py @@ -327,45 +327,50 @@ class GeoDataFrame(GeoPandasBase, DataFrame): * keep: output the missing entries as NaN show_bbox : include bbox (bounds) in the geojson. default False - """ - def fill_none(row): - """ - Takes in a Series, converts to a dictionary with null values - set to None - - """ - na_keys = row.index[row.isnull()] - d = row.to_dict() - for k in na_keys: - d[k] = None - return d - - # na_methods must take in a Series and return dict - na_methods = {'null': fill_none, - 'drop': lambda row: row.dropna().to_dict(), - 'keep': lambda row: row.to_dict()} - - if na not in na_methods: + if na not in ['null', 'drop', 'keep']: raise ValueError('Unknown na method {0}'.format(na)) - f = na_methods[na] - for name, row in self.iterrows(): - properties = f(row) - del properties[self._geometry_column_name] + ids = np.array(self.index, copy=False) + geometries = np.array(self[self._geometry_column_name], copy=False) - feature = { - 'id': str(name), - 'type': 'Feature', - 'properties': properties, - 'geometry': mapping(row[self._geometry_column_name]) - if row[self._geometry_column_name] else None - } + properties_cols = self.columns.difference([self._geometry_column_name]) - if show_bbox: - feature['bbox'] = row.geometry.bounds + if len(properties_cols) > 0: + # convert to object to get python scalars. + properties = self[properties_cols].astype(object).values + if na == 'null': + properties[pd.isnull(self[properties_cols]).values] = None - yield feature + for i, row in enumerate(properties): + geom = geometries[i] + + if na == 'drop': + properties_items = dict((k, v) for k, v + in zip(properties_cols, row) + if not pd.isnull(v)) + else: + properties_items = dict((k, v) for k, v + in zip(properties_cols, row)) + + feature = {'id': str(ids[i]), + 'type': 'Feature', + 'properties': properties_items, + 'geometry': mapping(geom) if geom else None} + + if show_bbox: + feature['bbox'] = geom.bounds if geom else None + yield feature + + else: + for fid, geom in zip(ids, geometries): + feature = {'id': str(fid), + 'type': 'Feature', + 'properties': {}, + 'geometry': mapping(geom) if geom else None} + if show_bbox: + feature['bbox'] = geom.bounds if geom else None + yield feature def _to_geo(self, **kwargs): """ diff --git a/geopandas/tests/test_geodataframe.py b/geopandas/tests/test_geodataframe.py index 6a4cf13..080e04b 100644 --- a/geopandas/tests/test_geodataframe.py +++ b/geopandas/tests/test_geodataframe.py @@ -619,6 +619,46 @@ class TestDataFrame: assert self.df.__geo_interface__['type'] == 'FeatureCollection' assert len(self.df.__geo_interface__['features']) == self.df.shape[0] + def test_geodataframe_iterfeatures(self): + df = self.df.iloc[:1].copy() + df.loc[0, 'BoroName'] = np.nan + # when containing missing values + # null: ouput the missing entries as JSON null + result = list(df.iterfeatures(na='null'))[0]['properties'] + assert result['BoroName'] is None + # drop: remove the property from the feature. + result = list(df.iterfeatures(na='drop'))[0]['properties'] + assert 'BoroName' not in result.keys() + # keep: output the missing entries as NaN + result = list(df.iterfeatures(na='keep'))[0]['properties'] + assert np.isnan(result['BoroName']) + + # test for checking that the (non-null) features are python scalars and + # not numpy scalars + assert type(df.loc[0, 'Shape_Leng']) is np.float64 + # null + result = list(df.iterfeatures(na='null'))[0] + assert type(result['properties']['Shape_Leng']) is float + # drop + result = list(df.iterfeatures(na='drop'))[0] + assert type(result['properties']['Shape_Leng']) is float + # keep + result = list(df.iterfeatures(na='keep'))[0] + assert type(result['properties']['Shape_Leng']) is float + + # when only having numerical columns + df_only_numerical_cols = df[['Shape_Leng', 'Shape_Area', 'geometry']] + assert type(df_only_numerical_cols.loc[0, 'Shape_Leng']) is np.float64 + # null + result = list(df_only_numerical_cols.iterfeatures(na='null'))[0] + assert type(result['properties']['Shape_Leng']) is float + # drop + result = list(df_only_numerical_cols.iterfeatures(na='drop'))[0] + assert type(result['properties']['Shape_Leng']) is float + # keep + result = list(df_only_numerical_cols.iterfeatures(na='keep'))[0] + assert type(result['properties']['Shape_Leng']) is float + def test_geodataframe_geojson_no_bbox(self): geo = self.df._to_geo(na="null", show_bbox=False) assert 'bbox' not in geo.keys()