mirror of
https://github.com/wassname/geopandas.git
synced 2026-09-20 12:50:47 +08:00
ENH: write GeoDataFrame with mixed geometry types (#870)
This commit is contained in:
committed by
Joris Van den Bossche
parent
ab5945bee6
commit
0fbc7ef3f7
@@ -9,6 +9,7 @@ __pycache__/
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
venv*/
|
||||
build/
|
||||
dist/
|
||||
eggs/
|
||||
|
||||
+54
-15
@@ -149,27 +149,66 @@ def infer_schema(df):
|
||||
if df.empty:
|
||||
raise ValueError("Cannot write empty DataFrame to file.")
|
||||
|
||||
geom_type = _common_geom_type(df)
|
||||
# Since https://github.com/Toblerity/Fiona/issues/446 resolution,
|
||||
# Fiona allows a list of geometry types
|
||||
geom_types = _geometry_types(df)
|
||||
|
||||
schema = {'geometry': geom_type, 'properties': properties}
|
||||
schema = {'geometry': geom_types, 'properties': properties}
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def _common_geom_type(df):
|
||||
# Need to check geom_types before we write to file...
|
||||
# Some (most?) providers expect a single geometry type:
|
||||
# Point, LineString, or Polygon
|
||||
geom_types = df.geometry.geom_type.unique()
|
||||
def _geometry_types(df):
|
||||
"""
|
||||
Determine the geometry types in the GeoDataFrame for the schema.
|
||||
"""
|
||||
if _FIONA18:
|
||||
# Starting from Fiona 1.8, schema submitted to fiona to write a gdf
|
||||
# can have mixed geometries:
|
||||
# - 3D and 2D shapes can coexist in inferred schema
|
||||
# - Shape and MultiShape types can (and must) coexist in inferred
|
||||
# schema
|
||||
geom_types_2D = df[~df.geometry.has_z].geometry.geom_type.unique()
|
||||
geom_types_2D = [gtype for gtype in geom_types_2D if gtype is not None]
|
||||
geom_types_3D = df[df.geometry.has_z].geometry.geom_type.unique()
|
||||
geom_types_3D = ["3D " + gtype for gtype in geom_types_3D
|
||||
if gtype is not None]
|
||||
geom_types = geom_types_3D + geom_types_2D
|
||||
|
||||
from os.path import commonprefix
|
||||
# use reversed geom types and commonprefix to find the common suffix,
|
||||
# then reverse the result to get back to a geom type
|
||||
geom_type = commonprefix([g[::-1] for g in geom_types if g])[::-1]
|
||||
if not geom_type:
|
||||
else:
|
||||
# Before Fiona 1.8, schema submitted to write a gdf should have
|
||||
# one single geometry type whenever possible:
|
||||
# - 3D and 2D shapes cannot coexist in inferred schema
|
||||
# - Shape and MultiShape can not coexist in inferred schema
|
||||
geom_types = _geometry_types_back_compat(df)
|
||||
|
||||
if len(geom_types) == 0:
|
||||
# Default geometry type supported by Fiona
|
||||
# (Since https://github.com/Toblerity/Fiona/issues/446 resolution)
|
||||
return 'Unknown'
|
||||
|
||||
if df.geometry.has_z.any():
|
||||
geom_type = "3D " + geom_type
|
||||
if len(geom_types) == 1:
|
||||
geom_types = geom_types[0]
|
||||
|
||||
return geom_type
|
||||
return geom_types
|
||||
|
||||
|
||||
def _geometry_types_back_compat(df):
|
||||
"""
|
||||
for backward compatibility with Fiona<1.8 only
|
||||
"""
|
||||
unique_geom_types = df.geometry.geom_type.unique()
|
||||
unique_geom_types = [
|
||||
gtype for gtype in unique_geom_types if gtype is not None]
|
||||
|
||||
# merge single and Multi types (eg Polygon and MultiPolygon)
|
||||
unique_geom_types = [
|
||||
gtype for gtype in unique_geom_types
|
||||
if not gtype.startswith('Multi') or gtype[5:] not in unique_geom_types]
|
||||
|
||||
if df.geometry.has_z.any():
|
||||
# declare all geometries as 3D geometries
|
||||
unique_geom_types = ["3D " + type for type in unique_geom_types]
|
||||
# by default, all geometries are 2D geometries
|
||||
|
||||
return unique_geom_types
|
||||
|
||||
@@ -5,8 +5,9 @@ from distutils.version import LooseVersion
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
from shapely.geometry import Point, Polygon, box
|
||||
|
||||
import fiona
|
||||
from shapely.geometry import Point, Polygon, box
|
||||
|
||||
import geopandas
|
||||
from geopandas import GeoDataFrame, read_file
|
||||
@@ -96,26 +97,32 @@ def test_to_file_bool(tmpdir, driver, ext):
|
||||
assert_geodataframe_equal(result, df, check_column_type=False)
|
||||
|
||||
|
||||
def test_to_file_with_point_z(tmpdir):
|
||||
@pytest.mark.parametrize(
|
||||
'ext, driver', [('shp', 'ESRI Shapefile'), ('geojson', 'GeoJSON')])
|
||||
def test_to_file_with_point_z(tmpdir, ext, driver):
|
||||
"""Test that 3D geometries are retained in writes (GH #612)."""
|
||||
|
||||
tempfilename = os.path.join(str(tmpdir), 'test_3Dpoint.shp')
|
||||
tempfilename = os.path.join(str(tmpdir), 'test_3Dpoint.' + ext)
|
||||
point3d = Point(0, 0, 500)
|
||||
point2d = Point(1, 1)
|
||||
df = GeoDataFrame({'a': [1, 2]}, geometry=[point3d, point2d], crs={})
|
||||
df.to_file(tempfilename)
|
||||
df = GeoDataFrame({'a': [1, 2]}, geometry=[point3d, point2d],
|
||||
crs={'init': 'epsg:4326'})
|
||||
df.to_file(tempfilename, driver=driver)
|
||||
df_read = GeoDataFrame.from_file(tempfilename)
|
||||
assert_geoseries_equal(df.geometry, df_read.geometry)
|
||||
|
||||
|
||||
def test_to_file_with_poly_z(tmpdir):
|
||||
@pytest.mark.parametrize(
|
||||
'ext, driver', [('shp', 'ESRI Shapefile'), ('geojson', 'GeoJSON')])
|
||||
def test_to_file_with_poly_z(tmpdir, ext, driver):
|
||||
"""Test that 3D geometries are retained in writes (GH #612)."""
|
||||
|
||||
tempfilename = os.path.join(str(tmpdir), 'test_3Dpoly.shp')
|
||||
tempfilename = os.path.join(str(tmpdir), 'test_3Dpoly.' + ext)
|
||||
poly3d = Polygon([[0, 0, 5], [0, 1, 5], [1, 1, 5], [1, 0, 5]])
|
||||
poly2d = Polygon([[0, 0], [0, 1], [1, 1], [1, 0]])
|
||||
df = GeoDataFrame({'a': [1, 2]}, geometry=[poly3d, poly2d], crs={})
|
||||
df.to_file(tempfilename)
|
||||
df = GeoDataFrame({'a': [1, 2]}, geometry=[poly3d, poly2d],
|
||||
crs={'init': 'epsg:4326'})
|
||||
df.to_file(tempfilename, driver=driver)
|
||||
df_read = GeoDataFrame.from_file(tempfilename)
|
||||
assert_geoseries_equal(df.geometry, df_read.geometry)
|
||||
|
||||
@@ -132,24 +139,6 @@ def test_to_file_types(tmpdir, df_points):
|
||||
df.to_file(tempfilename)
|
||||
|
||||
|
||||
def test_to_file_mixed_types(tmpdir):
|
||||
""" Test that mixed geometry types raise error when writing to file """
|
||||
tempfilename = os.path.join(str(tmpdir), 'test.shp')
|
||||
s = GeoDataFrame({'geometry': [Point(0, 0),
|
||||
Polygon([(0, 0), (1, 0), (1, 1)])]})
|
||||
# Exception type is different for different `fiona` versions
|
||||
with pytest.raises((ValueError, RuntimeError)):
|
||||
s.to_file(tempfilename)
|
||||
|
||||
|
||||
def test_to_file_geojson_mixed_types(tmpdir):
|
||||
""" Test that mixed geometry types can be saved as GeoJSON (GH #827) """
|
||||
tempfilename = os.path.join(str(tmpdir), 'test.geojson')
|
||||
s = GeoDataFrame({'geometry': [Point(0, 0),
|
||||
Polygon([(0, 0), (1, 0), (1, 1)])]})
|
||||
s.to_file(tempfilename, driver='GeoJSON')
|
||||
|
||||
|
||||
def test_to_file_empty(tmpdir):
|
||||
input_empty_df = GeoDataFrame()
|
||||
tempfilename = os.path.join(str(tmpdir), 'test.shp')
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from enum import Enum
|
||||
|
||||
from shapely.geometry import Point, Polygon, MultiPolygon, MultiPoint, \
|
||||
LineString, MultiLineString
|
||||
|
||||
import geopandas
|
||||
from geopandas import GeoDataFrame
|
||||
from geopandas.io.file import _FIONA18
|
||||
|
||||
import pytest
|
||||
from geopandas.testing import assert_geodataframe_equal
|
||||
|
||||
|
||||
# Credit: Polygons below come from Montreal city Open Data portal
|
||||
# http://donnees.ville.montreal.qc.ca/dataset/unites-evaluation-fonciere
|
||||
city_hall_boundaries = Polygon((
|
||||
(-73.5541107525234, 45.5091983609661),
|
||||
(-73.5546126200639, 45.5086813829106),
|
||||
(-73.5540185061397, 45.5084409343852),
|
||||
(-73.5539986525799, 45.5084323044531),
|
||||
(-73.5535801792994, 45.5089539203786),
|
||||
(-73.5541107525234, 45.5091983609661)
|
||||
))
|
||||
vauquelin_place = Polygon((
|
||||
(-73.5542465586147, 45.5081555487952),
|
||||
(-73.5540185061397, 45.5084409343852),
|
||||
(-73.5546126200639, 45.5086813829106),
|
||||
(-73.5548825850032, 45.5084033554357),
|
||||
(-73.5542465586147, 45.5081555487952)
|
||||
))
|
||||
|
||||
city_hall_walls = [
|
||||
LineString((
|
||||
(-73.5541107525234, 45.5091983609661),
|
||||
(-73.5546126200639, 45.5086813829106),
|
||||
(-73.5540185061397, 45.5084409343852)
|
||||
)),
|
||||
LineString((
|
||||
(-73.5539986525799, 45.5084323044531),
|
||||
(-73.5535801792994, 45.5089539203786),
|
||||
(-73.5541107525234, 45.5091983609661)
|
||||
))
|
||||
]
|
||||
|
||||
city_hall_entrance = Point(-73.553785, 45.508722)
|
||||
city_hall_balcony = Point(-73.554138, 45.509080)
|
||||
city_hall_council_chamber = Point(-73.554246, 45.508931)
|
||||
|
||||
point_3D = Point(-73.553785, 45.508722, 300)
|
||||
|
||||
|
||||
# *****************************************
|
||||
# TEST TOOLING
|
||||
|
||||
class _Fiona(Enum):
|
||||
below_1_8 = 'fiona_below_1_8'
|
||||
above_1_8 = 'fiona_above_1_8'
|
||||
|
||||
|
||||
class _ExpectedError:
|
||||
def __init__(self, error_type, error_message_match):
|
||||
self.type = error_type
|
||||
self.match = error_message_match
|
||||
|
||||
|
||||
class _ExpectedErrorBuilder:
|
||||
def __init__(self, composite_key):
|
||||
self.composite_key = composite_key
|
||||
|
||||
def to_raise(self, error_type, error_match):
|
||||
_expected_exceptions[self.composite_key] = _ExpectedError(error_type,
|
||||
error_match)
|
||||
|
||||
|
||||
def _expect_writing(gdf, ogr_driver, fiona_version):
|
||||
return _ExpectedErrorBuilder(
|
||||
_composite_key(gdf, ogr_driver, fiona_version)
|
||||
)
|
||||
|
||||
|
||||
def _composite_key(gdf, ogr_driver, fiona_version):
|
||||
return frozenset([id(gdf), ogr_driver, fiona_version.value])
|
||||
|
||||
|
||||
def _expected_error_on(gdf, ogr_driver, is_fiona_above_1_8):
|
||||
if is_fiona_above_1_8:
|
||||
composite_key = _composite_key(gdf, ogr_driver, _Fiona.above_1_8)
|
||||
else:
|
||||
composite_key = _composite_key(gdf, ogr_driver, _Fiona.below_1_8)
|
||||
return _expected_exceptions.get(composite_key, None)
|
||||
|
||||
|
||||
# *****************************************
|
||||
# TEST CASES
|
||||
_geodataframes_to_write = []
|
||||
_expected_exceptions = {}
|
||||
|
||||
# ------------------
|
||||
# gdf with Points
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[city_hall_entrance, city_hall_balcony]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with MultiPoints
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[
|
||||
MultiPoint([
|
||||
city_hall_balcony,
|
||||
city_hall_council_chamber]),
|
||||
MultiPoint([
|
||||
city_hall_entrance,
|
||||
city_hall_balcony,
|
||||
city_hall_council_chamber]
|
||||
)]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with Points and MultiPoints
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[
|
||||
MultiPoint([city_hall_entrance, city_hall_balcony]),
|
||||
city_hall_balcony
|
||||
]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
# 'ESRI Shapefile' driver supports writing LineString/MultiLinestring and
|
||||
# Polygon/MultiPolygon but does not mention Point/MultiPoint
|
||||
# see https://www.gdal.org/drv_shapefile.html
|
||||
for driver in ('ESRI Shapefile', 'GPKG'):
|
||||
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
|
||||
ValueError,
|
||||
"Record's geometry type does not match collection schema's geometry "
|
||||
"type: 'MultiPoint' != 'Point'"
|
||||
)
|
||||
_expect_writing(gdf, 'ESRI Shapefile', _Fiona.above_1_8).to_raise(
|
||||
RuntimeError,
|
||||
"Failed to write record"
|
||||
)
|
||||
|
||||
# ------------------
|
||||
# gdf with LineStrings
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=city_hall_walls
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with MultiLineStrings
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[
|
||||
MultiLineString(city_hall_walls),
|
||||
MultiLineString(city_hall_walls)
|
||||
]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with LineStrings and MultiLineStrings
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[MultiLineString(city_hall_walls), city_hall_walls[0]]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
_expect_writing(gdf, 'GPKG', _Fiona.below_1_8).to_raise(
|
||||
ValueError,
|
||||
"Record's geometry type does not match collection schema's geometry "
|
||||
"type: 'MultiLineString' != 'LineString'"
|
||||
)
|
||||
|
||||
# ------------------
|
||||
# gdf with Polygons
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[city_hall_boundaries, vauquelin_place]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with MultiPolygon
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[MultiPolygon((city_hall_boundaries, vauquelin_place))]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with Polygon and MultiPolygon
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place)),
|
||||
city_hall_boundaries
|
||||
]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
_expect_writing(gdf, 'GPKG', _Fiona.below_1_8).to_raise(
|
||||
ValueError,
|
||||
"Record's geometry type does not match collection schema's geometry "
|
||||
"type: 'MultiPolygon' != 'Polygon'"
|
||||
)
|
||||
|
||||
# ------------------
|
||||
# gdf with null geometry and Point
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[None, city_hall_entrance]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with null geometry and 3D Point
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[None, point_3D]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with null geometries only
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[None, None]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
|
||||
# ------------------
|
||||
# gdf with all shape types mixed together
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2, 3, 4, 5, 6]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place)),
|
||||
city_hall_entrance,
|
||||
MultiLineString(city_hall_walls),
|
||||
city_hall_walls[0],
|
||||
MultiPoint([city_hall_entrance, city_hall_balcony]),
|
||||
city_hall_balcony
|
||||
]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
# Not supported by 'ESRI Shapefile' driver
|
||||
for driver in ('ESRI Shapefile', 'GPKG'):
|
||||
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
|
||||
AttributeError,
|
||||
"'list' object has no attribute 'lstrip'"
|
||||
)
|
||||
_expect_writing(gdf, 'ESRI Shapefile', _Fiona.above_1_8).to_raise(
|
||||
RuntimeError,
|
||||
"Failed to write record"
|
||||
)
|
||||
|
||||
# ------------------
|
||||
# gdf with all 2D shape types and 3D Point mixed together
|
||||
gdf = GeoDataFrame(
|
||||
{'a': [1, 2, 3, 4, 5, 6, 7]},
|
||||
crs={'init': 'epsg:4326'},
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place)),
|
||||
city_hall_entrance,
|
||||
MultiLineString(city_hall_walls),
|
||||
city_hall_walls[0],
|
||||
MultiPoint([city_hall_entrance, city_hall_balcony]),
|
||||
city_hall_balcony,
|
||||
point_3D
|
||||
]
|
||||
)
|
||||
_geodataframes_to_write.append(gdf)
|
||||
# Not supported by 'ESRI Shapefile' driver
|
||||
for driver in ('ESRI Shapefile', 'GPKG'):
|
||||
_expect_writing(gdf, driver, _Fiona.below_1_8).to_raise(
|
||||
AttributeError,
|
||||
"'list' object has no attribute 'lstrip'"
|
||||
)
|
||||
_expect_writing(gdf, 'ESRI Shapefile', _Fiona.above_1_8).to_raise(
|
||||
RuntimeError,
|
||||
"Failed to write record"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(params=_geodataframes_to_write)
|
||||
def geodataframe(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(params=[
|
||||
'GeoJSON', 'ESRI Shapefile',
|
||||
pytest.param('GPKG', marks=pytest.mark.skipif(
|
||||
(sys.version_info < (3, 0)) and sys.platform.startswith('win'),
|
||||
reason="GPKG tests failing on AppVeyor for Python 2.7"))
|
||||
])
|
||||
def ogr_driver(request):
|
||||
return request.param
|
||||
|
||||
|
||||
def test_to_file_roundtrip(tmpdir, geodataframe, ogr_driver):
|
||||
output_file = os.path.join(str(tmpdir), 'output_file')
|
||||
|
||||
expected_error = _expected_error_on(geodataframe, ogr_driver, _FIONA18)
|
||||
if expected_error:
|
||||
with pytest.raises(expected_error.type, match=expected_error.match):
|
||||
geodataframe.to_file(output_file, driver=ogr_driver)
|
||||
else:
|
||||
geodataframe.to_file(output_file, driver=ogr_driver)
|
||||
|
||||
reloaded = geopandas.read_file(output_file)
|
||||
|
||||
check_column_type = 'equiv'
|
||||
if sys.version_info[0] < 3:
|
||||
# do not check column types in python 2 (mixed string/unicode)
|
||||
check_column_type = False
|
||||
|
||||
assert_geodataframe_equal(geodataframe, reloaded,
|
||||
check_column_type=check_column_type)
|
||||
@@ -0,0 +1,350 @@
|
||||
from collections import OrderedDict
|
||||
|
||||
from shapely.geometry import Point, Polygon, MultiPolygon, MultiPoint, \
|
||||
LineString, MultiLineString
|
||||
|
||||
from geopandas import GeoDataFrame
|
||||
from geopandas.io.file import infer_schema, _FIONA18
|
||||
|
||||
|
||||
# Credit: Polygons below come from Montreal city Open Data portal
|
||||
# http://donnees.ville.montreal.qc.ca/dataset/unites-evaluation-fonciere
|
||||
city_hall_boundaries = Polygon((
|
||||
(-73.5541107525234, 45.5091983609661),
|
||||
(-73.5546126200639, 45.5086813829106),
|
||||
(-73.5540185061397, 45.5084409343852),
|
||||
(-73.5539986525799, 45.5084323044531),
|
||||
(-73.5535801792994, 45.5089539203786),
|
||||
(-73.5541107525234, 45.5091983609661)
|
||||
))
|
||||
vauquelin_place = Polygon((
|
||||
(-73.5542465586147, 45.5081555487952),
|
||||
(-73.5540185061397, 45.5084409343852),
|
||||
(-73.5546126200639, 45.5086813829106),
|
||||
(-73.5548825850032, 45.5084033554357),
|
||||
(-73.5542465586147, 45.5081555487952)
|
||||
))
|
||||
|
||||
city_hall_walls = [
|
||||
LineString((
|
||||
(-73.5541107525234, 45.5091983609661),
|
||||
(-73.5546126200639, 45.5086813829106),
|
||||
(-73.5540185061397, 45.5084409343852)
|
||||
)),
|
||||
LineString((
|
||||
(-73.5539986525799, 45.5084323044531),
|
||||
(-73.5535801792994, 45.5089539203786),
|
||||
(-73.5541107525234, 45.5091983609661)
|
||||
))
|
||||
]
|
||||
|
||||
city_hall_entrance = Point(-73.553785, 45.508722)
|
||||
city_hall_balcony = Point(-73.554138, 45.509080)
|
||||
city_hall_council_chamber = Point(-73.554246, 45.508931)
|
||||
|
||||
point_3D = Point(-73.553785, 45.508722, 300)
|
||||
linestring_3D = LineString((
|
||||
(-73.5541107525234, 45.5091983609661, 300),
|
||||
(-73.5546126200639, 45.5086813829106, 300),
|
||||
(-73.5540185061397, 45.5084409343852, 300)
|
||||
))
|
||||
polygon_3D = Polygon((
|
||||
(-73.5541107525234, 45.5091983609661, 300),
|
||||
(-73.5535801792994, 45.5089539203786, 300),
|
||||
(-73.5541107525234, 45.5091983609661, 300)
|
||||
))
|
||||
|
||||
|
||||
def test_infer_schema_only_points():
|
||||
df = GeoDataFrame(
|
||||
geometry=[city_hall_entrance, city_hall_balcony]
|
||||
)
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'Point',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_points_and_multipoints():
|
||||
df = GeoDataFrame(
|
||||
geometry=[
|
||||
MultiPoint([city_hall_entrance, city_hall_balcony]),
|
||||
city_hall_balcony
|
||||
]
|
||||
)
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['MultiPoint', 'Point'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'Point',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_multipoints():
|
||||
df = GeoDataFrame(
|
||||
geometry=[MultiPoint([
|
||||
city_hall_entrance,
|
||||
city_hall_balcony,
|
||||
city_hall_council_chamber
|
||||
])]
|
||||
)
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'MultiPoint',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_linestrings():
|
||||
df = GeoDataFrame(geometry=city_hall_walls)
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'LineString',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_linestrings_and_multilinestrings():
|
||||
df = GeoDataFrame(
|
||||
geometry=[
|
||||
MultiLineString(city_hall_walls),
|
||||
city_hall_walls[0]
|
||||
]
|
||||
)
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['MultiLineString', 'LineString'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'LineString',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_multilinestrings():
|
||||
df = GeoDataFrame(geometry=[MultiLineString(city_hall_walls)])
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'MultiLineString',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_polygons():
|
||||
df = GeoDataFrame(
|
||||
geometry=[city_hall_boundaries, vauquelin_place]
|
||||
)
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'Polygon',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_polygons_and_multipolygons():
|
||||
df = GeoDataFrame(
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place)),
|
||||
city_hall_boundaries
|
||||
]
|
||||
)
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['MultiPolygon', 'Polygon'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'Polygon',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_multipolygons():
|
||||
df = GeoDataFrame(
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place))
|
||||
]
|
||||
)
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'MultiPolygon',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_multiple_shape_types():
|
||||
df = GeoDataFrame(
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place)),
|
||||
city_hall_boundaries,
|
||||
MultiLineString(city_hall_walls),
|
||||
city_hall_walls[0],
|
||||
MultiPoint([city_hall_entrance, city_hall_balcony]),
|
||||
city_hall_balcony
|
||||
]
|
||||
)
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': [
|
||||
'MultiPolygon', 'Polygon',
|
||||
'MultiLineString', 'LineString',
|
||||
'MultiPoint', 'Point'
|
||||
],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': [
|
||||
'Polygon',
|
||||
'LineString',
|
||||
'Point'
|
||||
],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_mixed_3D_shape_type():
|
||||
df = GeoDataFrame(
|
||||
geometry=[
|
||||
MultiPolygon((city_hall_boundaries, vauquelin_place)),
|
||||
city_hall_boundaries,
|
||||
MultiLineString(city_hall_walls),
|
||||
city_hall_walls[0],
|
||||
MultiPoint([city_hall_entrance, city_hall_balcony]),
|
||||
city_hall_balcony,
|
||||
point_3D
|
||||
]
|
||||
)
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': [
|
||||
'3D Point',
|
||||
'MultiPolygon', 'Polygon',
|
||||
'MultiLineString', 'LineString',
|
||||
'MultiPoint', 'Point'
|
||||
],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['3D Polygon', '3D LineString', '3D Point'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_mixed_3D_Point():
|
||||
df = GeoDataFrame(geometry=[city_hall_balcony, point_3D])
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['3D Point', 'Point'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D Point',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_3D_Points():
|
||||
df = GeoDataFrame(geometry=[point_3D, point_3D])
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D Point',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_mixed_3D_linestring():
|
||||
df = GeoDataFrame(
|
||||
geometry=[city_hall_walls[0], linestring_3D]
|
||||
)
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['3D LineString', 'LineString'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D LineString',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_3D_linestrings():
|
||||
df = GeoDataFrame(geometry=[linestring_3D, linestring_3D])
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D LineString',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_mixed_3D_Polygon():
|
||||
df = GeoDataFrame(geometry=[city_hall_boundaries, polygon_3D])
|
||||
|
||||
if _FIONA18:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': ['3D Polygon', 'Polygon'],
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
else:
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D Polygon',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_only_3D_Polygons():
|
||||
df = GeoDataFrame(geometry=[polygon_3D, polygon_3D])
|
||||
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D Polygon',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_null_geometry_and_2D_point():
|
||||
df = GeoDataFrame(geometry=[None, city_hall_entrance])
|
||||
|
||||
# None geometry type is then omitted
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'Point',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_null_geometry_and_3D_point():
|
||||
df = GeoDataFrame(geometry=[None, point_3D])
|
||||
|
||||
# None geometry type is then omitted
|
||||
assert infer_schema(df) == {
|
||||
'geometry': '3D Point',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
|
||||
|
||||
def test_infer_schema_null_geometry_all():
|
||||
df = GeoDataFrame(geometry=[None, None])
|
||||
|
||||
# None geometry type in then replaced by 'Unknown'
|
||||
# (default geometry type supported by Fiona)
|
||||
assert infer_schema(df) == {
|
||||
'geometry': 'Unknown',
|
||||
'properties': OrderedDict()
|
||||
}
|
||||
Reference in New Issue
Block a user