pyogrio 0.8.0__cp312-cp312-win_amd64.whl → 0.10.0__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyogrio might be problematic. Click here for more details.
- pyogrio/__init__.py +26 -18
- pyogrio/_compat.py +7 -1
- pyogrio/_env.py +4 -6
- pyogrio/_err.c +460 -445
- pyogrio/_err.cp312-win_amd64.pyd +0 -0
- pyogrio/_geometry.c +645 -612
- pyogrio/_geometry.cp312-win_amd64.pyd +0 -0
- pyogrio/_io.c +7764 -7602
- pyogrio/_io.cp312-win_amd64.pyd +0 -0
- pyogrio/_ogr.c +601 -609
- pyogrio/_ogr.cp312-win_amd64.pyd +0 -0
- pyogrio/_version.py +3 -3
- pyogrio/_vsi.c +7570 -2514
- pyogrio/_vsi.cp312-win_amd64.pyd +0 -0
- pyogrio/core.py +86 -20
- pyogrio/errors.py +9 -16
- pyogrio/gdal_data/GDAL-targets.cmake +1 -1
- pyogrio/gdal_data/GDALConfig.cmake +0 -1
- pyogrio/gdal_data/GDALConfigVersion.cmake +3 -3
- pyogrio/gdal_data/MM_m_idofic.csv +321 -0
- pyogrio/gdal_data/gdaltileindex.xsd +269 -0
- pyogrio/gdal_data/gdalvrt.xsd +130 -22
- pyogrio/gdal_data/ogrinfo_output.schema.json +23 -0
- pyogrio/gdal_data/ogrvrt.xsd +3 -0
- pyogrio/gdal_data/pci_datum.txt +222 -155
- pyogrio/gdal_data/pci_ellips.txt +90 -38
- pyogrio/gdal_data/vcpkg.spdx.json +21 -21
- pyogrio/gdal_data/vcpkg_abi_info.txt +27 -27
- pyogrio/geopandas.py +44 -27
- pyogrio/proj_data/proj-config-version.cmake +2 -2
- pyogrio/proj_data/proj-targets.cmake +1 -1
- pyogrio/proj_data/proj.db +0 -0
- pyogrio/proj_data/proj4-targets.cmake +1 -1
- pyogrio/proj_data/projjson.schema.json +1 -1
- pyogrio/proj_data/vcpkg.spdx.json +17 -17
- pyogrio/proj_data/vcpkg_abi_info.txt +15 -15
- pyogrio/raw.py +46 -30
- pyogrio/tests/conftest.py +206 -12
- pyogrio/tests/fixtures/README.md +32 -13
- pyogrio/tests/fixtures/curve.gpkg +0 -0
- pyogrio/tests/fixtures/{test_multisurface.gpkg → curvepolygon.gpkg} +0 -0
- pyogrio/tests/fixtures/line_zm.gpkg +0 -0
- pyogrio/tests/fixtures/multisurface.gpkg +0 -0
- pyogrio/tests/test_arrow.py +178 -24
- pyogrio/tests/test_core.py +162 -72
- pyogrio/tests/test_geopandas_io.py +341 -96
- pyogrio/tests/test_path.py +30 -17
- pyogrio/tests/test_raw_io.py +165 -54
- pyogrio/tests/test_util.py +56 -0
- pyogrio/util.py +55 -31
- pyogrio-0.10.0.dist-info/DELVEWHEEL +2 -0
- {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/LICENSE +1 -1
- {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/METADATA +37 -8
- {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/RECORD +74 -89
- {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/WHEEL +1 -1
- pyogrio.libs/{Lerc-62a2c1c74500e7815994b3e49b36750c.dll → Lerc-089e3fef3df84b17326dcddbf1dedaa4.dll} +0 -0
- pyogrio.libs/{gdal-2bfc6a9f962a8953b0640db9a272d797.dll → gdal-debee5933f0da7bb90b4bcd009023377.dll} +0 -0
- pyogrio.libs/{geos-289d7171bd083dfed1f8a90e4ae57442.dll → geos-ace4c5b5c1f569bb4213e7bbd0b0322e.dll} +0 -0
- pyogrio.libs/{geos_c-2a12859cd876719c648f1eb950b7d94c.dll → geos_c-7478ca0a86136b280d9b2d245c6f6627.dll} +0 -0
- pyogrio.libs/geotiff-c8fe8a095520a4ea4e465d27e06add3a.dll +0 -0
- pyogrio.libs/{iconv-2-f2d9304f8dc4cdd981024b520b73a099.dll → iconv-2-27352d156a5467ca5383d3951093ea5a.dll} +0 -0
- pyogrio.libs/{jpeg62-a67b2bf7fd32d34c565ae5bb6d47c224.dll → jpeg62-e56b6f95a95af498f4623b8da4cebd46.dll} +0 -0
- pyogrio.libs/{json-c-79a8df7e59952f5c5d594620e4b66c13.dll → json-c-c84940e2654a4f8468bfcf2ce992aa93.dll} +0 -0
- pyogrio.libs/libcurl-d69cfd4ad487d53d58743b6778ec85e7.dll +0 -0
- pyogrio.libs/{libexpat-fa55f107b678de136400c6d953c3cdde.dll → libexpat-6576a8d02641b6a3dbad35901ec200a7.dll} +0 -0
- pyogrio.libs/liblzma-9ee4accb476ec1ae24e924953140273d.dll +0 -0
- pyogrio.libs/{libpng16-6227e9a35c2a350ae6b0586079c10b9e.dll → libpng16-7c36142dda59f186f6bb683e8dae2bfe.dll} +0 -0
- pyogrio.libs/{msvcp140-46db46e967c8db2cb7a20fc75872a57e.dll → msvcp140-98b3e5b80de1e5e9d1703b786d795623.dll} +0 -0
- pyogrio.libs/proj-a408c5327f3fd2f5fabe8c56815beed7.dll +0 -0
- pyogrio.libs/{qhull_r-d8840f4ed1f7d452ff9a30237320bcfd.dll → qhull_r-516897f855568caab1ab1fe37912766c.dll} +0 -0
- pyogrio.libs/sqlite3-9bc109d8536d5ed9666332fec94485fc.dll +0 -0
- pyogrio.libs/{tiff-ffca1ff19d0e95dad39df0078fb037af.dll → tiff-9b3f605fffe0bccc0a964c374ee4f820.dll} +0 -0
- pyogrio.libs/{zlib1-aaba6ea052f6d3fa3d84a301e3eb3d30.dll → zlib1-e5af16a15c63f05bd82d90396807ae5b.dll} +0 -0
- pyogrio/_err.pxd +0 -4
- pyogrio/_err.pyx +0 -250
- pyogrio/_geometry.pxd +0 -4
- pyogrio/_geometry.pyx +0 -129
- pyogrio/_io.pxd +0 -0
- pyogrio/_io.pyx +0 -2738
- pyogrio/_ogr.pxd +0 -441
- pyogrio/_ogr.pyx +0 -346
- pyogrio/_vsi.pxd +0 -4
- pyogrio/_vsi.pyx +0 -140
- pyogrio/arrow_bridge.h +0 -115
- pyogrio/gdal_data/bag_template.xml +0 -201
- pyogrio/gdal_data/gmlasconf.xml +0 -169
- pyogrio/gdal_data/gmlasconf.xsd +0 -1066
- pyogrio/gdal_data/netcdf_config.xsd +0 -143
- pyogrio/gdal_data/template_tiles.mapml +0 -28
- pyogrio/tests/fixtures/test_datetime.geojson +0 -7
- pyogrio/tests/fixtures/test_datetime_tz.geojson +0 -8
- pyogrio/tests/fixtures/test_fgdb.gdb.zip +0 -0
- pyogrio/tests/fixtures/test_nested.geojson +0 -18
- pyogrio/tests/fixtures/test_ogr_types_list.geojson +0 -12
- pyogrio-0.8.0.dist-info/DELVEWHEEL +0 -2
- pyogrio.libs/geotiff-d1c0fcc3c454409ad8be61ff04a7422c.dll +0 -0
- pyogrio.libs/libcurl-7fef9869f6520a5fbdb2bc9ce4c496cc.dll +0 -0
- pyogrio.libs/liblzma-5a1f648afc3d4cf36e3aef2266d55143.dll +0 -0
- pyogrio.libs/proj-74051a73897c9fa6d7bfef4561688568.dll +0 -0
- pyogrio.libs/sqlite3-fe7a86058d1c5658d1f9106228a7fd83.dll +0 -0
- {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/top_level.txt +0 -0
pyogrio/tests/conftest.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
from io import BytesIO
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from zipfile import ZIP_DEFLATED, ZipFile
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
+
import numpy as np
|
|
5
6
|
|
|
6
7
|
from pyogrio import (
|
|
7
8
|
__gdal_version_string__,
|
|
@@ -13,10 +14,14 @@ from pyogrio._compat import (
|
|
|
13
14
|
HAS_ARROW_WRITE_API,
|
|
14
15
|
HAS_GDAL_GEOS,
|
|
15
16
|
HAS_PYARROW,
|
|
17
|
+
HAS_PYPROJ,
|
|
16
18
|
HAS_SHAPELY,
|
|
17
19
|
)
|
|
20
|
+
from pyogrio.core import vsi_rmtree
|
|
18
21
|
from pyogrio.raw import read, write
|
|
19
22
|
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
20
25
|
_data_dir = Path(__file__).parent.resolve() / "fixtures"
|
|
21
26
|
|
|
22
27
|
# mapping of driver extension to driver name for well-supported drivers
|
|
@@ -34,6 +39,15 @@ DRIVER_EXT = {driver: ext for ext, driver in DRIVERS.items()}
|
|
|
34
39
|
|
|
35
40
|
ALL_EXTS = [".fgb", ".geojson", ".geojsonl", ".gpkg", ".shp"]
|
|
36
41
|
|
|
42
|
+
START_FID = {
|
|
43
|
+
".fgb": 0,
|
|
44
|
+
".geojson": 0,
|
|
45
|
+
".geojsonl": 0,
|
|
46
|
+
".geojsons": 0,
|
|
47
|
+
".gpkg": 1,
|
|
48
|
+
".shp": 0,
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
|
|
38
52
|
def pytest_report_header(config):
|
|
39
53
|
drivers = ", ".join(
|
|
@@ -53,6 +67,8 @@ requires_pyarrow_api = pytest.mark.skipif(
|
|
|
53
67
|
not HAS_ARROW_API or not HAS_PYARROW, reason="GDAL>=3.6 and pyarrow required"
|
|
54
68
|
)
|
|
55
69
|
|
|
70
|
+
requires_pyproj = pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj required")
|
|
71
|
+
|
|
56
72
|
requires_arrow_write_api = pytest.mark.skipif(
|
|
57
73
|
not HAS_ARROW_WRITE_API or not HAS_PYARROW,
|
|
58
74
|
reason="GDAL>=3.8 required for Arrow write API",
|
|
@@ -110,7 +126,7 @@ def naturalearth_lowres_all_ext(tmp_path, naturalearth_lowres, request):
|
|
|
110
126
|
|
|
111
127
|
@pytest.fixture(scope="function")
|
|
112
128
|
def naturalearth_lowres_vsi(tmp_path, naturalearth_lowres):
|
|
113
|
-
"""Wrap naturalearth_lowres as a zip file for
|
|
129
|
+
"""Wrap naturalearth_lowres as a zip file for VSI tests"""
|
|
114
130
|
|
|
115
131
|
path = tmp_path / f"{naturalearth_lowres.name}.zip"
|
|
116
132
|
with ZipFile(path, mode="w", compression=ZIP_DEFLATED, compresslevel=5) as out:
|
|
@@ -121,29 +137,182 @@ def naturalearth_lowres_vsi(tmp_path, naturalearth_lowres):
|
|
|
121
137
|
return path, f"/vsizip/{path}/{naturalearth_lowres.name}"
|
|
122
138
|
|
|
123
139
|
|
|
140
|
+
@pytest.fixture(scope="function")
|
|
141
|
+
def naturalearth_lowres_vsimem(naturalearth_lowres):
|
|
142
|
+
"""Write naturalearth_lowres to a vsimem file for VSI tests"""
|
|
143
|
+
|
|
144
|
+
meta, _, geometry, field_data = read(naturalearth_lowres)
|
|
145
|
+
name = f"pyogrio_fixture_{naturalearth_lowres.stem}"
|
|
146
|
+
dst_path = Path(f"/vsimem/{name}/{name}.gpkg")
|
|
147
|
+
meta["spatial_index"] = False
|
|
148
|
+
meta["geometry_type"] = "MultiPolygon"
|
|
149
|
+
|
|
150
|
+
write(dst_path, geometry, field_data, layer="naturalearth_lowres", **meta)
|
|
151
|
+
yield dst_path
|
|
152
|
+
|
|
153
|
+
vsi_rmtree(dst_path.parent)
|
|
154
|
+
|
|
155
|
+
|
|
124
156
|
@pytest.fixture(scope="session")
|
|
125
|
-
def
|
|
126
|
-
return
|
|
157
|
+
def line_zm_file():
|
|
158
|
+
return _data_dir / "line_zm.gpkg"
|
|
127
159
|
|
|
128
160
|
|
|
129
161
|
@pytest.fixture(scope="session")
|
|
130
|
-
def
|
|
131
|
-
return _data_dir / "
|
|
162
|
+
def curve_file():
|
|
163
|
+
return _data_dir / "curve.gpkg"
|
|
132
164
|
|
|
133
165
|
|
|
134
166
|
@pytest.fixture(scope="session")
|
|
135
|
-
def
|
|
136
|
-
return _data_dir / "
|
|
167
|
+
def curve_polygon_file():
|
|
168
|
+
return _data_dir / "curvepolygon.gpkg"
|
|
137
169
|
|
|
138
170
|
|
|
139
171
|
@pytest.fixture(scope="session")
|
|
140
|
-
def
|
|
141
|
-
return _data_dir / "
|
|
172
|
+
def multisurface_file():
|
|
173
|
+
return _data_dir / "multisurface.gpkg"
|
|
142
174
|
|
|
143
175
|
|
|
144
176
|
@pytest.fixture(scope="session")
|
|
145
|
-
def
|
|
146
|
-
return _data_dir / "
|
|
177
|
+
def test_gpkg_nulls():
|
|
178
|
+
return _data_dir / "test_gpkg_nulls.gpkg"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@pytest.fixture(scope="function")
|
|
182
|
+
def no_geometry_file(tmp_path):
|
|
183
|
+
# create a GPKG layer that does not include geometry
|
|
184
|
+
filename = tmp_path / "test_no_geometry.gpkg"
|
|
185
|
+
write(
|
|
186
|
+
filename,
|
|
187
|
+
layer="no_geometry",
|
|
188
|
+
geometry=None,
|
|
189
|
+
field_data=[np.array(["a", "b", "c"])],
|
|
190
|
+
fields=["col"],
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return filename
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@pytest.fixture(scope="function")
|
|
197
|
+
def list_field_values_file(tmp_path):
|
|
198
|
+
# Create a GeoJSON file with list values in a property
|
|
199
|
+
list_geojson = """{
|
|
200
|
+
"type": "FeatureCollection",
|
|
201
|
+
"features": [
|
|
202
|
+
{
|
|
203
|
+
"type": "Feature",
|
|
204
|
+
"properties": { "int64": 1, "list_int64": [0, 1] },
|
|
205
|
+
"geometry": { "type": "Point", "coordinates": [0, 2] }
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"type": "Feature",
|
|
209
|
+
"properties": { "int64": 2, "list_int64": [2, 3] },
|
|
210
|
+
"geometry": { "type": "Point", "coordinates": [1, 2] }
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"type": "Feature",
|
|
214
|
+
"properties": { "int64": 3, "list_int64": [4, 5] },
|
|
215
|
+
"geometry": { "type": "Point", "coordinates": [2, 2] }
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"type": "Feature",
|
|
219
|
+
"properties": { "int64": 4, "list_int64": [6, 7] },
|
|
220
|
+
"geometry": { "type": "Point", "coordinates": [3, 2] }
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"type": "Feature",
|
|
224
|
+
"properties": { "int64": 5, "list_int64": [8, 9] },
|
|
225
|
+
"geometry": { "type": "Point", "coordinates": [4, 2] }
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}"""
|
|
229
|
+
|
|
230
|
+
filename = tmp_path / "test_ogr_types_list.geojson"
|
|
231
|
+
with open(filename, "w") as f:
|
|
232
|
+
_ = f.write(list_geojson)
|
|
233
|
+
|
|
234
|
+
return filename
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
@pytest.fixture(scope="function")
|
|
238
|
+
def nested_geojson_file(tmp_path):
|
|
239
|
+
# create GeoJSON file with nested properties
|
|
240
|
+
nested_geojson = """{
|
|
241
|
+
"type": "FeatureCollection",
|
|
242
|
+
"features": [
|
|
243
|
+
{
|
|
244
|
+
"type": "Feature",
|
|
245
|
+
"geometry": {
|
|
246
|
+
"type": "Point",
|
|
247
|
+
"coordinates": [0, 0]
|
|
248
|
+
},
|
|
249
|
+
"properties": {
|
|
250
|
+
"top_level": "A",
|
|
251
|
+
"intermediate_level": {
|
|
252
|
+
"bottom_level": "B"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
}"""
|
|
258
|
+
|
|
259
|
+
filename = tmp_path / "test_nested.geojson"
|
|
260
|
+
with open(filename, "w") as f:
|
|
261
|
+
_ = f.write(nested_geojson)
|
|
262
|
+
|
|
263
|
+
return filename
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@pytest.fixture(scope="function")
|
|
267
|
+
def datetime_file(tmp_path):
|
|
268
|
+
# create GeoJSON file with millisecond precision
|
|
269
|
+
datetime_geojson = """{
|
|
270
|
+
"type": "FeatureCollection",
|
|
271
|
+
"features": [
|
|
272
|
+
{
|
|
273
|
+
"type": "Feature",
|
|
274
|
+
"properties": { "col": "2020-01-01T09:00:00.123" },
|
|
275
|
+
"geometry": { "type": "Point", "coordinates": [1, 1] }
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"type": "Feature",
|
|
279
|
+
"properties": { "col": "2020-01-01T10:00:00" },
|
|
280
|
+
"geometry": { "type": "Point", "coordinates": [2, 2] }
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
}"""
|
|
284
|
+
|
|
285
|
+
filename = tmp_path / "test_datetime.geojson"
|
|
286
|
+
with open(filename, "w") as f:
|
|
287
|
+
_ = f.write(datetime_geojson)
|
|
288
|
+
|
|
289
|
+
return filename
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@pytest.fixture(scope="function")
|
|
293
|
+
def datetime_tz_file(tmp_path):
|
|
294
|
+
# create GeoJSON file with datetimes with timezone
|
|
295
|
+
datetime_tz_geojson = """{
|
|
296
|
+
"type": "FeatureCollection",
|
|
297
|
+
"features": [
|
|
298
|
+
{
|
|
299
|
+
"type": "Feature",
|
|
300
|
+
"properties": { "datetime_col": "2020-01-01T09:00:00.123-05:00" },
|
|
301
|
+
"geometry": { "type": "Point", "coordinates": [1, 1] }
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"type": "Feature",
|
|
305
|
+
"properties": { "datetime_col": "2020-01-01T10:00:00-05:00" },
|
|
306
|
+
"geometry": { "type": "Point", "coordinates": [2, 2] }
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
}"""
|
|
310
|
+
|
|
311
|
+
filename = tmp_path / "test_datetime_tz.geojson"
|
|
312
|
+
with open(filename, "w") as f:
|
|
313
|
+
f.write(datetime_tz_geojson)
|
|
314
|
+
|
|
315
|
+
return filename
|
|
147
316
|
|
|
148
317
|
|
|
149
318
|
@pytest.fixture(scope="function")
|
|
@@ -178,6 +347,31 @@ def geojson_filelike(tmp_path):
|
|
|
178
347
|
yield f
|
|
179
348
|
|
|
180
349
|
|
|
350
|
+
@pytest.fixture(scope="function")
|
|
351
|
+
def nonseekable_bytes(tmp_path):
|
|
352
|
+
# mock a non-seekable byte stream, such as a zstandard handle
|
|
353
|
+
class NonSeekableBytesIO(BytesIO):
|
|
354
|
+
def seekable(self):
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
def seek(self, *args, **kwargs):
|
|
358
|
+
raise OSError("cannot seek")
|
|
359
|
+
|
|
360
|
+
# wrap GeoJSON into a non-seekable BytesIO
|
|
361
|
+
geojson = """{
|
|
362
|
+
"type": "FeatureCollection",
|
|
363
|
+
"features": [
|
|
364
|
+
{
|
|
365
|
+
"type": "Feature",
|
|
366
|
+
"properties": { },
|
|
367
|
+
"geometry": { "type": "Point", "coordinates": [1, 1] }
|
|
368
|
+
}
|
|
369
|
+
]
|
|
370
|
+
}"""
|
|
371
|
+
|
|
372
|
+
return NonSeekableBytesIO(geojson.encode("UTF-8"))
|
|
373
|
+
|
|
374
|
+
|
|
181
375
|
@pytest.fixture(
|
|
182
376
|
scope="session",
|
|
183
377
|
params=[
|
pyogrio/tests/fixtures/README.md
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
# Test datasets
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Obtaining / creating test datasets
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
If a test dataset can be created in code, do that instead. If it is used in a
|
|
6
|
+
single test, create the test dataset as part of that test. If it is used in
|
|
7
|
+
more than a single test, add it to `pyogrio/tests/conftest.py` instead, as a
|
|
8
|
+
function-scoped test fixture.
|
|
9
|
+
|
|
10
|
+
If you need to obtain 3rd party test files:
|
|
11
|
+
|
|
12
|
+
- add a section below that describes the source location and processing steps
|
|
13
|
+
to derive that dataset
|
|
14
|
+
- make sure the license is compatible with including in Pyogrio (public domain or open-source)
|
|
15
|
+
and record that license below
|
|
16
|
+
|
|
17
|
+
Please keep the test files no larger than necessary to use in tests.
|
|
6
18
|
|
|
7
|
-
##
|
|
19
|
+
## Included test datasets
|
|
20
|
+
|
|
21
|
+
### Natural Earth lowres
|
|
22
|
+
|
|
23
|
+
`naturalearth_lowres.shp` was copied from GeoPandas.
|
|
8
24
|
|
|
9
|
-
|
|
10
|
-
Downloaded from http://trac.osgeo.org/gdal/raw-attachment/wiki/FileGDB/test_fgdb.gdb.zip
|
|
25
|
+
License: public domain
|
|
11
26
|
|
|
12
27
|
### GPKG test dataset with null values
|
|
13
28
|
|
|
@@ -75,15 +90,19 @@ NOTE: Reading boolean values into GeoPandas using Fiona backend treats those
|
|
|
75
90
|
values as `None` and column dtype as `object`; Pyogrio treats those values as
|
|
76
91
|
`np.nan` and column dtype as `float64`.
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
This was extracted from https://prd-tnm.s3.amazonaws.com/StagedProducts/Hydrography/NHDPlusHR/Beta/GDB/NHDPLUS_H_0308_HU4_GDB.zip
|
|
81
|
-
`NHDWaterbody` layer using ogr2ogr:
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
ogr2ogr test_mixed_surface.gpkg NHDPLUS_H_0308_HU4_GDB.gdb NHDWaterbody -where '"NHDPlusID" = 15000300070477' -select "NHDPlusID"
|
|
85
|
-
```
|
|
93
|
+
License: same as Pyogrio
|
|
86
94
|
|
|
87
95
|
### OSM PBF test
|
|
88
96
|
|
|
89
97
|
This was downloaded from https://github.com/openstreetmap/OSM-binary/blob/master/resources/sample.pbf
|
|
98
|
+
|
|
99
|
+
License: [Open Data Commons Open Database License (ODbL)](https://opendatacommons.org/licenses/odbl/)
|
|
100
|
+
|
|
101
|
+
### Test files for geometry types that are downgraded on read
|
|
102
|
+
|
|
103
|
+
`line_zm.gpkg` was created using QGIS to digitize a LineString GPKG layer with Z and M enabled. Downgraded to LineString Z on read.
|
|
104
|
+
`curve.gpkg` was created using QGIS to digitize a Curve GPKG layer. Downgraded to LineString on read.
|
|
105
|
+
`curvepolygon.gpkg` was created using QGIS to digitize a CurvePolygon GPKG layer. Downgraded to Polygon on read.
|
|
106
|
+
`multisurface.gpkg` was created using QGIS to digitize a MultiSurface GPKG layer. Downgraded to MultiPolygon on read.
|
|
107
|
+
|
|
108
|
+
License: same as Pyogrio
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
pyogrio/tests/test_arrow.py
CHANGED
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
-
from io import BytesIO
|
|
3
2
|
import json
|
|
4
3
|
import math
|
|
5
4
|
import os
|
|
6
|
-
from packaging.version import Version
|
|
7
5
|
import sys
|
|
6
|
+
from io import BytesIO
|
|
7
|
+
from packaging.version import Version
|
|
8
|
+
from zipfile import ZipFile
|
|
8
9
|
|
|
9
|
-
import pytest
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
12
|
import pyogrio
|
|
13
13
|
from pyogrio import (
|
|
14
14
|
__gdal_version__,
|
|
15
|
+
get_gdal_config_option,
|
|
16
|
+
list_layers,
|
|
15
17
|
read_dataframe,
|
|
16
18
|
read_info,
|
|
17
|
-
list_layers,
|
|
18
|
-
get_gdal_config_option,
|
|
19
19
|
set_gdal_config_options,
|
|
20
|
+
vsi_listtree,
|
|
20
21
|
)
|
|
22
|
+
from pyogrio.errors import DataLayerError, DataSourceError, FieldError
|
|
21
23
|
from pyogrio.raw import open_arrow, read_arrow, write, write_arrow
|
|
22
|
-
from pyogrio.errors import DataSourceError, FieldError, DataLayerError
|
|
23
24
|
from pyogrio.tests.conftest import (
|
|
24
25
|
ALL_EXTS,
|
|
25
|
-
DRIVERS,
|
|
26
26
|
DRIVER_EXT,
|
|
27
|
+
DRIVERS,
|
|
27
28
|
requires_arrow_write_api,
|
|
28
29
|
requires_pyarrow_api,
|
|
29
30
|
)
|
|
30
31
|
|
|
32
|
+
import pytest
|
|
33
|
+
|
|
31
34
|
try:
|
|
32
35
|
import pandas as pd
|
|
33
|
-
from pandas.testing import assert_frame_equal, assert_index_equal
|
|
34
|
-
from geopandas.testing import assert_geodataframe_equal
|
|
35
|
-
|
|
36
36
|
import pyarrow
|
|
37
|
+
|
|
38
|
+
from geopandas.testing import assert_geodataframe_equal
|
|
39
|
+
from pandas.testing import assert_frame_equal, assert_index_equal
|
|
37
40
|
except ImportError:
|
|
38
41
|
pass
|
|
39
42
|
|
|
@@ -130,24 +133,24 @@ def test_read_arrow_ignore_geometry(naturalearth_lowres):
|
|
|
130
133
|
assert_frame_equal(result, expected)
|
|
131
134
|
|
|
132
135
|
|
|
133
|
-
def test_read_arrow_nested_types(
|
|
136
|
+
def test_read_arrow_nested_types(list_field_values_file):
|
|
134
137
|
# with arrow, list types are supported
|
|
135
|
-
result = read_dataframe(
|
|
138
|
+
result = read_dataframe(list_field_values_file, use_arrow=True)
|
|
136
139
|
assert "list_int64" in result.columns
|
|
137
140
|
assert result["list_int64"][0].tolist() == [0, 1]
|
|
138
141
|
|
|
139
142
|
|
|
140
|
-
def test_read_arrow_to_pandas_kwargs(
|
|
143
|
+
def test_read_arrow_to_pandas_kwargs(no_geometry_file):
|
|
141
144
|
# with arrow, list types are supported
|
|
142
145
|
arrow_to_pandas_kwargs = {"strings_to_categorical": True}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
df = read_dataframe(
|
|
147
|
+
no_geometry_file,
|
|
148
|
+
read_geometry=False,
|
|
146
149
|
use_arrow=True,
|
|
147
150
|
arrow_to_pandas_kwargs=arrow_to_pandas_kwargs,
|
|
148
151
|
)
|
|
149
|
-
assert "
|
|
150
|
-
assert
|
|
152
|
+
assert df.col.dtype.name == "category"
|
|
153
|
+
assert np.array_equal(df.col.values.categories, ["a", "b", "c"])
|
|
151
154
|
|
|
152
155
|
|
|
153
156
|
def test_read_arrow_raw(naturalearth_lowres):
|
|
@@ -160,6 +163,10 @@ def test_read_arrow_vsi(naturalearth_lowres_vsi):
|
|
|
160
163
|
table = read_arrow(naturalearth_lowres_vsi[1])[1]
|
|
161
164
|
assert len(table) == 177
|
|
162
165
|
|
|
166
|
+
# Check temp file was cleaned up. Filter to files created by pyogrio, as GDAL keeps
|
|
167
|
+
# cache files in /vsimem/.
|
|
168
|
+
assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
|
|
169
|
+
|
|
163
170
|
|
|
164
171
|
def test_read_arrow_bytes(geojson_bytes):
|
|
165
172
|
meta, table = read_arrow(geojson_bytes)
|
|
@@ -167,6 +174,18 @@ def test_read_arrow_bytes(geojson_bytes):
|
|
|
167
174
|
assert meta["fields"].shape == (5,)
|
|
168
175
|
assert len(table) == 3
|
|
169
176
|
|
|
177
|
+
# Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
|
|
178
|
+
assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_read_arrow_nonseekable_bytes(nonseekable_bytes):
|
|
182
|
+
meta, table = read_arrow(nonseekable_bytes)
|
|
183
|
+
assert meta["fields"].shape == (0,)
|
|
184
|
+
assert len(table) == 1
|
|
185
|
+
|
|
186
|
+
# Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
|
|
187
|
+
assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
|
|
188
|
+
|
|
170
189
|
|
|
171
190
|
def test_read_arrow_filelike(geojson_filelike):
|
|
172
191
|
meta, table = read_arrow(geojson_filelike)
|
|
@@ -174,6 +193,9 @@ def test_read_arrow_filelike(geojson_filelike):
|
|
|
174
193
|
assert meta["fields"].shape == (5,)
|
|
175
194
|
assert len(table) == 3
|
|
176
195
|
|
|
196
|
+
# Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
|
|
197
|
+
assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
|
|
198
|
+
|
|
177
199
|
|
|
178
200
|
def test_open_arrow_pyarrow(naturalearth_lowres):
|
|
179
201
|
with open_arrow(naturalearth_lowres, use_pyarrow=True) as (meta, reader):
|
|
@@ -183,7 +205,7 @@ def test_open_arrow_pyarrow(naturalearth_lowres):
|
|
|
183
205
|
|
|
184
206
|
|
|
185
207
|
def test_open_arrow_batch_size(naturalearth_lowres):
|
|
186
|
-
|
|
208
|
+
_, table = read_arrow(naturalearth_lowres)
|
|
187
209
|
batch_size = math.ceil(len(table) / 2)
|
|
188
210
|
|
|
189
211
|
with open_arrow(naturalearth_lowres, batch_size=batch_size, use_pyarrow=True) as (
|
|
@@ -289,14 +311,15 @@ def use_arrow_context():
|
|
|
289
311
|
del os.environ["PYOGRIO_USE_ARROW"]
|
|
290
312
|
|
|
291
313
|
|
|
292
|
-
def test_enable_with_environment_variable(
|
|
314
|
+
def test_enable_with_environment_variable(list_field_values_file):
|
|
293
315
|
# list types are only supported with arrow, so don't work by default and work
|
|
294
316
|
# when arrow is enabled through env variable
|
|
295
|
-
result = read_dataframe(
|
|
317
|
+
result = read_dataframe(list_field_values_file)
|
|
296
318
|
assert "list_int64" not in result.columns
|
|
297
319
|
|
|
298
320
|
with use_arrow_context():
|
|
299
|
-
result = read_dataframe(
|
|
321
|
+
result = read_dataframe(list_field_values_file)
|
|
322
|
+
|
|
300
323
|
assert "list_int64" in result.columns
|
|
301
324
|
|
|
302
325
|
|
|
@@ -483,6 +506,88 @@ def test_write_geojson(tmp_path, naturalearth_lowres):
|
|
|
483
506
|
)
|
|
484
507
|
|
|
485
508
|
|
|
509
|
+
@requires_arrow_write_api
|
|
510
|
+
@pytest.mark.skipif(
|
|
511
|
+
__gdal_version__ < (3, 6, 0),
|
|
512
|
+
reason="OpenFileGDB write support only available for GDAL >= 3.6.0",
|
|
513
|
+
)
|
|
514
|
+
@pytest.mark.parametrize(
|
|
515
|
+
"write_int64",
|
|
516
|
+
[
|
|
517
|
+
False,
|
|
518
|
+
pytest.param(
|
|
519
|
+
True,
|
|
520
|
+
marks=pytest.mark.skipif(
|
|
521
|
+
__gdal_version__ < (3, 9, 0),
|
|
522
|
+
reason="OpenFileGDB write support for int64 values for GDAL >= 3.9.0",
|
|
523
|
+
),
|
|
524
|
+
),
|
|
525
|
+
],
|
|
526
|
+
)
|
|
527
|
+
def test_write_openfilegdb(tmp_path, write_int64):
|
|
528
|
+
expected_field_data = [
|
|
529
|
+
np.array([True, False, True], dtype="bool"),
|
|
530
|
+
np.array([1, 2, 3], dtype="int16"),
|
|
531
|
+
np.array([1, 2, 3], dtype="int32"),
|
|
532
|
+
np.array([1, 2, 3], dtype="int64"),
|
|
533
|
+
np.array([1, 2, 3], dtype="float32"),
|
|
534
|
+
np.array([1, 2, 3], dtype="float64"),
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
table = pa.table(
|
|
538
|
+
{
|
|
539
|
+
"geometry": points,
|
|
540
|
+
**{field.dtype.name: field for field in expected_field_data},
|
|
541
|
+
}
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
filename = tmp_path / "test.gdb"
|
|
545
|
+
|
|
546
|
+
expected_meta = {"crs": "EPSG:4326"}
|
|
547
|
+
|
|
548
|
+
# int64 is not supported without additional config: https://gdal.org/en/latest/drivers/vector/openfilegdb.html#bit-integer-field-support
|
|
549
|
+
# it is converted to float64 by default and raises a warning
|
|
550
|
+
# (for GDAL >= 3.9.0 only)
|
|
551
|
+
write_params = (
|
|
552
|
+
{"TARGET_ARCGIS_VERSION": "ARCGIS_PRO_3_2_OR_LATER"} if write_int64 else {}
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
if write_int64 or __gdal_version__ < (3, 9, 0):
|
|
556
|
+
ctx = contextlib.nullcontext()
|
|
557
|
+
else:
|
|
558
|
+
ctx = pytest.warns(
|
|
559
|
+
RuntimeWarning, match="Integer64 will be written as a Float64"
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
with ctx:
|
|
563
|
+
write_arrow(
|
|
564
|
+
table,
|
|
565
|
+
filename,
|
|
566
|
+
driver="OpenFileGDB",
|
|
567
|
+
geometry_type="Point",
|
|
568
|
+
geometry_name="geometry",
|
|
569
|
+
**expected_meta,
|
|
570
|
+
**write_params,
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
meta, table = read_arrow(filename)
|
|
574
|
+
|
|
575
|
+
if not write_int64:
|
|
576
|
+
expected_field_data[3] = expected_field_data[3].astype("float64")
|
|
577
|
+
|
|
578
|
+
# bool types are converted to int32
|
|
579
|
+
expected_field_data[0] = expected_field_data[0].astype("int32")
|
|
580
|
+
|
|
581
|
+
assert meta["crs"] == expected_meta["crs"]
|
|
582
|
+
|
|
583
|
+
# NOTE: geometry name is set to "SHAPE" by GDAL
|
|
584
|
+
assert np.array_equal(table[meta["geometry_name"]], points)
|
|
585
|
+
for i in range(len(expected_field_data)):
|
|
586
|
+
values = table[table.schema.names[i]].to_numpy()
|
|
587
|
+
assert values.dtype == expected_field_data[i].dtype
|
|
588
|
+
assert np.array_equal(values, expected_field_data[i])
|
|
589
|
+
|
|
590
|
+
|
|
486
591
|
@pytest.mark.parametrize(
|
|
487
592
|
"driver",
|
|
488
593
|
{
|
|
@@ -877,6 +982,9 @@ def test_write_memory_driver_required(naturalearth_lowres):
|
|
|
877
982
|
geometry_name=meta["geometry_name"] or "wkb_geometry",
|
|
878
983
|
)
|
|
879
984
|
|
|
985
|
+
# Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
|
|
986
|
+
assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
|
|
987
|
+
|
|
880
988
|
|
|
881
989
|
@requires_arrow_write_api
|
|
882
990
|
@pytest.mark.parametrize("driver", ["ESRI Shapefile", "OpenFileGDB"])
|
|
@@ -945,6 +1053,48 @@ def test_write_memory_existing_unsupported(naturalearth_lowres):
|
|
|
945
1053
|
)
|
|
946
1054
|
|
|
947
1055
|
|
|
1056
|
+
@requires_arrow_write_api
|
|
1057
|
+
def test_write_open_file_handle(tmp_path, naturalearth_lowres):
|
|
1058
|
+
"""Verify that writing to an open file handle is not currently supported"""
|
|
1059
|
+
|
|
1060
|
+
meta, table = read_arrow(naturalearth_lowres, max_features=1)
|
|
1061
|
+
meta["geometry_type"] = "MultiPolygon"
|
|
1062
|
+
|
|
1063
|
+
# verify it fails for regular file handle
|
|
1064
|
+
with pytest.raises(
|
|
1065
|
+
NotImplementedError, match="writing to an open file handle is not yet supported"
|
|
1066
|
+
):
|
|
1067
|
+
with open(tmp_path / "test.geojson", "wb") as f:
|
|
1068
|
+
write_arrow(
|
|
1069
|
+
table,
|
|
1070
|
+
f,
|
|
1071
|
+
driver="GeoJSON",
|
|
1072
|
+
layer="test",
|
|
1073
|
+
crs=meta["crs"],
|
|
1074
|
+
geometry_type=meta["geometry_type"],
|
|
1075
|
+
geometry_name=meta["geometry_name"] or "wkb_geometry",
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
# verify it fails for ZipFile
|
|
1079
|
+
with pytest.raises(
|
|
1080
|
+
NotImplementedError, match="writing to an open file handle is not yet supported"
|
|
1081
|
+
):
|
|
1082
|
+
with ZipFile(tmp_path / "test.geojson.zip", "w") as z:
|
|
1083
|
+
with z.open("test.geojson", "w") as f:
|
|
1084
|
+
write_arrow(
|
|
1085
|
+
table,
|
|
1086
|
+
f,
|
|
1087
|
+
driver="GeoJSON",
|
|
1088
|
+
layer="test",
|
|
1089
|
+
crs=meta["crs"],
|
|
1090
|
+
geometry_type=meta["geometry_type"],
|
|
1091
|
+
geometry_name=meta["geometry_name"] or "wkb_geometry",
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
# Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
|
|
1095
|
+
assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
|
|
1096
|
+
|
|
1097
|
+
|
|
948
1098
|
@requires_arrow_write_api
|
|
949
1099
|
def test_non_utf8_encoding_io_shapefile(tmp_path, encoded_text):
|
|
950
1100
|
encoding, text = encoded_text
|
|
@@ -1004,13 +1154,17 @@ def test_non_utf8_encoding_io_shapefile(tmp_path, encoded_text):
|
|
|
1004
1154
|
|
|
1005
1155
|
@requires_arrow_write_api
|
|
1006
1156
|
def test_encoding_write_layer_option_collision_shapefile(tmp_path, naturalearth_lowres):
|
|
1007
|
-
"""Providing both encoding parameter and ENCODING layer creation option
|
|
1157
|
+
"""Providing both encoding parameter and ENCODING layer creation option
|
|
1158
|
+
(even if blank) is not allowed."""
|
|
1008
1159
|
|
|
1009
1160
|
meta, table = read_arrow(naturalearth_lowres)
|
|
1010
1161
|
|
|
1011
1162
|
with pytest.raises(
|
|
1012
1163
|
ValueError,
|
|
1013
|
-
match=
|
|
1164
|
+
match=(
|
|
1165
|
+
'cannot provide both encoding parameter and "ENCODING" layer creation '
|
|
1166
|
+
"option"
|
|
1167
|
+
),
|
|
1014
1168
|
):
|
|
1015
1169
|
write_arrow(
|
|
1016
1170
|
table,
|