pyogrio 0.12.0__cp314-cp314t-macosx_12_0_arm64.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.
Files changed (231) hide show
  1. pyogrio/.dylibs/libgdal.37.3.11.4.dylib +0 -0
  2. pyogrio/__init__.py +57 -0
  3. pyogrio/_compat.py +54 -0
  4. pyogrio/_env.py +59 -0
  5. pyogrio/_err.cpython-314t-darwin.so +0 -0
  6. pyogrio/_geometry.cpython-314t-darwin.so +0 -0
  7. pyogrio/_io.cpython-314t-darwin.so +0 -0
  8. pyogrio/_ogr.cpython-314t-darwin.so +0 -0
  9. pyogrio/_version.py +21 -0
  10. pyogrio/_vsi.cpython-314t-darwin.so +0 -0
  11. pyogrio/core.py +387 -0
  12. pyogrio/errors.py +25 -0
  13. pyogrio/gdal_data/GDAL-targets-release.cmake +19 -0
  14. pyogrio/gdal_data/GDAL-targets.cmake +106 -0
  15. pyogrio/gdal_data/GDALConfig.cmake +24 -0
  16. pyogrio/gdal_data/GDALConfigVersion.cmake +65 -0
  17. pyogrio/gdal_data/GDALLogoBW.svg +138 -0
  18. pyogrio/gdal_data/GDALLogoColor.svg +126 -0
  19. pyogrio/gdal_data/GDALLogoGS.svg +126 -0
  20. pyogrio/gdal_data/LICENSE.TXT +467 -0
  21. pyogrio/gdal_data/MM_m_idofic.csv +321 -0
  22. pyogrio/gdal_data/copyright +467 -0
  23. pyogrio/gdal_data/cubewerx_extra.wkt +48 -0
  24. pyogrio/gdal_data/default.rsc +0 -0
  25. pyogrio/gdal_data/ecw_cs.wkt +1453 -0
  26. pyogrio/gdal_data/eedaconf.json +23 -0
  27. pyogrio/gdal_data/epsg.wkt +1 -0
  28. pyogrio/gdal_data/esri_StatePlane_extra.wkt +631 -0
  29. pyogrio/gdal_data/gdal_algorithm.schema.json +220 -0
  30. pyogrio/gdal_data/gdalg.schema.json +36 -0
  31. pyogrio/gdal_data/gdalicon.png +0 -0
  32. pyogrio/gdal_data/gdalinfo_output.schema.json +390 -0
  33. pyogrio/gdal_data/gdalmdiminfo_output.schema.json +326 -0
  34. pyogrio/gdal_data/gdaltileindex.xsd +253 -0
  35. pyogrio/gdal_data/gdalvrt.xsd +927 -0
  36. pyogrio/gdal_data/gfs.xsd +246 -0
  37. pyogrio/gdal_data/gml_registry.xml +117 -0
  38. pyogrio/gdal_data/gml_registry.xsd +66 -0
  39. pyogrio/gdal_data/grib2_center.csv +251 -0
  40. pyogrio/gdal_data/grib2_process.csv +102 -0
  41. pyogrio/gdal_data/grib2_subcenter.csv +63 -0
  42. pyogrio/gdal_data/grib2_table_4_2_0_0.csv +261 -0
  43. pyogrio/gdal_data/grib2_table_4_2_0_1.csv +261 -0
  44. pyogrio/gdal_data/grib2_table_4_2_0_13.csv +261 -0
  45. pyogrio/gdal_data/grib2_table_4_2_0_14.csv +261 -0
  46. pyogrio/gdal_data/grib2_table_4_2_0_15.csv +261 -0
  47. pyogrio/gdal_data/grib2_table_4_2_0_16.csv +261 -0
  48. pyogrio/gdal_data/grib2_table_4_2_0_17.csv +11 -0
  49. pyogrio/gdal_data/grib2_table_4_2_0_18.csv +261 -0
  50. pyogrio/gdal_data/grib2_table_4_2_0_19.csv +261 -0
  51. pyogrio/gdal_data/grib2_table_4_2_0_190.csv +261 -0
  52. pyogrio/gdal_data/grib2_table_4_2_0_191.csv +261 -0
  53. pyogrio/gdal_data/grib2_table_4_2_0_2.csv +261 -0
  54. pyogrio/gdal_data/grib2_table_4_2_0_20.csv +261 -0
  55. pyogrio/gdal_data/grib2_table_4_2_0_21.csv +261 -0
  56. pyogrio/gdal_data/grib2_table_4_2_0_3.csv +261 -0
  57. pyogrio/gdal_data/grib2_table_4_2_0_4.csv +261 -0
  58. pyogrio/gdal_data/grib2_table_4_2_0_5.csv +261 -0
  59. pyogrio/gdal_data/grib2_table_4_2_0_6.csv +261 -0
  60. pyogrio/gdal_data/grib2_table_4_2_0_7.csv +261 -0
  61. pyogrio/gdal_data/grib2_table_4_2_10_0.csv +261 -0
  62. pyogrio/gdal_data/grib2_table_4_2_10_1.csv +261 -0
  63. pyogrio/gdal_data/grib2_table_4_2_10_191.csv +261 -0
  64. pyogrio/gdal_data/grib2_table_4_2_10_2.csv +261 -0
  65. pyogrio/gdal_data/grib2_table_4_2_10_3.csv +261 -0
  66. pyogrio/gdal_data/grib2_table_4_2_10_4.csv +261 -0
  67. pyogrio/gdal_data/grib2_table_4_2_1_0.csv +261 -0
  68. pyogrio/gdal_data/grib2_table_4_2_1_1.csv +261 -0
  69. pyogrio/gdal_data/grib2_table_4_2_1_2.csv +261 -0
  70. pyogrio/gdal_data/grib2_table_4_2_20_0.csv +261 -0
  71. pyogrio/gdal_data/grib2_table_4_2_20_1.csv +261 -0
  72. pyogrio/gdal_data/grib2_table_4_2_20_2.csv +261 -0
  73. pyogrio/gdal_data/grib2_table_4_2_2_0.csv +261 -0
  74. pyogrio/gdal_data/grib2_table_4_2_2_3.csv +261 -0
  75. pyogrio/gdal_data/grib2_table_4_2_2_4.csv +261 -0
  76. pyogrio/gdal_data/grib2_table_4_2_2_5.csv +261 -0
  77. pyogrio/gdal_data/grib2_table_4_2_2_6.csv +261 -0
  78. pyogrio/gdal_data/grib2_table_4_2_3_0.csv +261 -0
  79. pyogrio/gdal_data/grib2_table_4_2_3_1.csv +261 -0
  80. pyogrio/gdal_data/grib2_table_4_2_3_2.csv +28 -0
  81. pyogrio/gdal_data/grib2_table_4_2_3_3.csv +8 -0
  82. pyogrio/gdal_data/grib2_table_4_2_3_4.csv +14 -0
  83. pyogrio/gdal_data/grib2_table_4_2_3_5.csv +11 -0
  84. pyogrio/gdal_data/grib2_table_4_2_3_6.csv +11 -0
  85. pyogrio/gdal_data/grib2_table_4_2_4_0.csv +261 -0
  86. pyogrio/gdal_data/grib2_table_4_2_4_1.csv +261 -0
  87. pyogrio/gdal_data/grib2_table_4_2_4_10.csv +261 -0
  88. pyogrio/gdal_data/grib2_table_4_2_4_2.csv +261 -0
  89. pyogrio/gdal_data/grib2_table_4_2_4_3.csv +261 -0
  90. pyogrio/gdal_data/grib2_table_4_2_4_4.csv +261 -0
  91. pyogrio/gdal_data/grib2_table_4_2_4_5.csv +261 -0
  92. pyogrio/gdal_data/grib2_table_4_2_4_6.csv +261 -0
  93. pyogrio/gdal_data/grib2_table_4_2_4_7.csv +261 -0
  94. pyogrio/gdal_data/grib2_table_4_2_4_8.csv +261 -0
  95. pyogrio/gdal_data/grib2_table_4_2_4_9.csv +261 -0
  96. pyogrio/gdal_data/grib2_table_4_2_local_Canada.csv +5 -0
  97. pyogrio/gdal_data/grib2_table_4_2_local_HPC.csv +2 -0
  98. pyogrio/gdal_data/grib2_table_4_2_local_MRMS.csv +175 -0
  99. pyogrio/gdal_data/grib2_table_4_2_local_NCEP.csv +401 -0
  100. pyogrio/gdal_data/grib2_table_4_2_local_NDFD.csv +38 -0
  101. pyogrio/gdal_data/grib2_table_4_2_local_index.csv +7 -0
  102. pyogrio/gdal_data/grib2_table_4_5.csv +261 -0
  103. pyogrio/gdal_data/grib2_table_versions.csv +3 -0
  104. pyogrio/gdal_data/gt_datum.csv +229 -0
  105. pyogrio/gdal_data/gt_ellips.csv +24 -0
  106. pyogrio/gdal_data/header.dxf +1124 -0
  107. pyogrio/gdal_data/inspire_cp_BasicPropertyUnit.gfs +57 -0
  108. pyogrio/gdal_data/inspire_cp_CadastralBoundary.gfs +60 -0
  109. pyogrio/gdal_data/inspire_cp_CadastralParcel.gfs +81 -0
  110. pyogrio/gdal_data/inspire_cp_CadastralZoning.gfs +161 -0
  111. pyogrio/gdal_data/jpfgdgml_AdmArea.gfs +59 -0
  112. pyogrio/gdal_data/jpfgdgml_AdmBdry.gfs +49 -0
  113. pyogrio/gdal_data/jpfgdgml_AdmPt.gfs +59 -0
  114. pyogrio/gdal_data/jpfgdgml_BldA.gfs +54 -0
  115. pyogrio/gdal_data/jpfgdgml_BldL.gfs +54 -0
  116. pyogrio/gdal_data/jpfgdgml_Cntr.gfs +54 -0
  117. pyogrio/gdal_data/jpfgdgml_CommBdry.gfs +49 -0
  118. pyogrio/gdal_data/jpfgdgml_CommPt.gfs +59 -0
  119. pyogrio/gdal_data/jpfgdgml_Cstline.gfs +54 -0
  120. pyogrio/gdal_data/jpfgdgml_ElevPt.gfs +54 -0
  121. pyogrio/gdal_data/jpfgdgml_GCP.gfs +94 -0
  122. pyogrio/gdal_data/jpfgdgml_LeveeEdge.gfs +49 -0
  123. pyogrio/gdal_data/jpfgdgml_RailCL.gfs +54 -0
  124. pyogrio/gdal_data/jpfgdgml_RdASL.gfs +44 -0
  125. pyogrio/gdal_data/jpfgdgml_RdArea.gfs +54 -0
  126. pyogrio/gdal_data/jpfgdgml_RdCompt.gfs +59 -0
  127. pyogrio/gdal_data/jpfgdgml_RdEdg.gfs +59 -0
  128. pyogrio/gdal_data/jpfgdgml_RdMgtBdry.gfs +49 -0
  129. pyogrio/gdal_data/jpfgdgml_RdSgmtA.gfs +59 -0
  130. pyogrio/gdal_data/jpfgdgml_RvrMgtBdry.gfs +49 -0
  131. pyogrio/gdal_data/jpfgdgml_SBAPt.gfs +49 -0
  132. pyogrio/gdal_data/jpfgdgml_SBArea.gfs +54 -0
  133. pyogrio/gdal_data/jpfgdgml_SBBdry.gfs +44 -0
  134. pyogrio/gdal_data/jpfgdgml_WA.gfs +54 -0
  135. pyogrio/gdal_data/jpfgdgml_WL.gfs +54 -0
  136. pyogrio/gdal_data/jpfgdgml_WStrA.gfs +54 -0
  137. pyogrio/gdal_data/jpfgdgml_WStrL.gfs +54 -0
  138. pyogrio/gdal_data/leaflet_template.html +102 -0
  139. pyogrio/gdal_data/nitf_spec.xml +3288 -0
  140. pyogrio/gdal_data/nitf_spec.xsd +171 -0
  141. pyogrio/gdal_data/ogr_fields_override.schema.json +125 -0
  142. pyogrio/gdal_data/ogrinfo_output.schema.json +528 -0
  143. pyogrio/gdal_data/ogrvrt.xsd +528 -0
  144. pyogrio/gdal_data/osmconf.ini +134 -0
  145. pyogrio/gdal_data/ozi_datum.csv +131 -0
  146. pyogrio/gdal_data/ozi_ellips.csv +35 -0
  147. pyogrio/gdal_data/pci_datum.txt +530 -0
  148. pyogrio/gdal_data/pci_ellips.txt +129 -0
  149. pyogrio/gdal_data/pdfcomposition.xsd +703 -0
  150. pyogrio/gdal_data/pds4_template.xml +65 -0
  151. pyogrio/gdal_data/plscenesconf.json +1985 -0
  152. pyogrio/gdal_data/ruian_vf_ob_v1.gfs +1455 -0
  153. pyogrio/gdal_data/ruian_vf_st_uvoh_v1.gfs +86 -0
  154. pyogrio/gdal_data/ruian_vf_st_v1.gfs +1489 -0
  155. pyogrio/gdal_data/ruian_vf_v1.gfs +2126 -0
  156. pyogrio/gdal_data/s57agencies.csv +249 -0
  157. pyogrio/gdal_data/s57attributes.csv +484 -0
  158. pyogrio/gdal_data/s57expectedinput.csv +1008 -0
  159. pyogrio/gdal_data/s57objectclasses.csv +287 -0
  160. pyogrio/gdal_data/seed_2d.dgn +0 -0
  161. pyogrio/gdal_data/seed_3d.dgn +0 -0
  162. pyogrio/gdal_data/stateplane.csv +259 -0
  163. pyogrio/gdal_data/template_tiles.mapml +28 -0
  164. pyogrio/gdal_data/tms_LINZAntarticaMapTileGrid.json +190 -0
  165. pyogrio/gdal_data/tms_MapML_APSTILE.json +268 -0
  166. pyogrio/gdal_data/tms_MapML_CBMTILE.json +346 -0
  167. pyogrio/gdal_data/tms_NZTM2000.json +243 -0
  168. pyogrio/gdal_data/trailer.dxf +434 -0
  169. pyogrio/gdal_data/usage +4 -0
  170. pyogrio/gdal_data/vcpkg-cmake-wrapper.cmake +23 -0
  171. pyogrio/gdal_data/vcpkg.spdx.json +291 -0
  172. pyogrio/gdal_data/vcpkg_abi_info.txt +45 -0
  173. pyogrio/gdal_data/vdv452.xml +349 -0
  174. pyogrio/gdal_data/vdv452.xsd +45 -0
  175. pyogrio/gdal_data/vicar.json +164 -0
  176. pyogrio/geopandas.py +978 -0
  177. pyogrio/proj_data/CH +22 -0
  178. pyogrio/proj_data/GL27 +23 -0
  179. pyogrio/proj_data/ITRF2000 +24 -0
  180. pyogrio/proj_data/ITRF2008 +94 -0
  181. pyogrio/proj_data/ITRF2014 +55 -0
  182. pyogrio/proj_data/ITRF2020 +91 -0
  183. pyogrio/proj_data/copyright +34 -0
  184. pyogrio/proj_data/deformation_model.schema.json +582 -0
  185. pyogrio/proj_data/nad.lst +142 -0
  186. pyogrio/proj_data/nad27 +810 -0
  187. pyogrio/proj_data/nad83 +745 -0
  188. pyogrio/proj_data/other.extra +53 -0
  189. pyogrio/proj_data/proj-config-version.cmake +44 -0
  190. pyogrio/proj_data/proj-config.cmake +79 -0
  191. pyogrio/proj_data/proj-targets-release.cmake +19 -0
  192. pyogrio/proj_data/proj-targets.cmake +107 -0
  193. pyogrio/proj_data/proj.db +0 -0
  194. pyogrio/proj_data/proj.ini +59 -0
  195. pyogrio/proj_data/proj4-targets-release.cmake +19 -0
  196. pyogrio/proj_data/proj4-targets.cmake +107 -0
  197. pyogrio/proj_data/projjson.schema.json +1174 -0
  198. pyogrio/proj_data/triangulation.schema.json +214 -0
  199. pyogrio/proj_data/usage +9 -0
  200. pyogrio/proj_data/vcpkg.spdx.json +203 -0
  201. pyogrio/proj_data/vcpkg_abi_info.txt +28 -0
  202. pyogrio/proj_data/world +214 -0
  203. pyogrio/raw.py +897 -0
  204. pyogrio/tests/__init__.py +0 -0
  205. pyogrio/tests/conftest.py +588 -0
  206. pyogrio/tests/fixtures/README.md +108 -0
  207. pyogrio/tests/fixtures/curve.gpkg +0 -0
  208. pyogrio/tests/fixtures/curvepolygon.gpkg +0 -0
  209. pyogrio/tests/fixtures/line_zm.gpkg +0 -0
  210. pyogrio/tests/fixtures/list_field_values_file.parquet +0 -0
  211. pyogrio/tests/fixtures/list_nested_struct_file.parquet +0 -0
  212. pyogrio/tests/fixtures/multisurface.gpkg +0 -0
  213. pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.cpg +1 -0
  214. pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.dbf +0 -0
  215. pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.prj +1 -0
  216. pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shp +0 -0
  217. pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shx +0 -0
  218. pyogrio/tests/fixtures/sample.osm.pbf +0 -0
  219. pyogrio/tests/fixtures/test_gpkg_nulls.gpkg +0 -0
  220. pyogrio/tests/test_arrow.py +1160 -0
  221. pyogrio/tests/test_core.py +702 -0
  222. pyogrio/tests/test_geopandas_io.py +3218 -0
  223. pyogrio/tests/test_path.py +374 -0
  224. pyogrio/tests/test_raw_io.py +1473 -0
  225. pyogrio/tests/test_util.py +56 -0
  226. pyogrio/util.py +258 -0
  227. pyogrio-0.12.0.dist-info/METADATA +125 -0
  228. pyogrio-0.12.0.dist-info/RECORD +231 -0
  229. pyogrio-0.12.0.dist-info/WHEEL +6 -0
  230. pyogrio-0.12.0.dist-info/licenses/LICENSE +21 -0
  231. pyogrio-0.12.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,702 @@
1
+ from pathlib import Path
2
+
3
+ import numpy as np
4
+ from numpy import allclose, array_equal
5
+
6
+ from pyogrio import (
7
+ __gdal_geos_version__,
8
+ __gdal_version__,
9
+ detect_write_driver,
10
+ get_gdal_config_option,
11
+ get_gdal_data_path,
12
+ list_drivers,
13
+ list_layers,
14
+ read_bounds,
15
+ read_info,
16
+ set_gdal_config_options,
17
+ vsi_listtree,
18
+ vsi_rmtree,
19
+ vsi_unlink,
20
+ )
21
+ from pyogrio._compat import GDAL_GE_38
22
+ from pyogrio._env import GDALEnv
23
+ from pyogrio.errors import DataLayerError, DataSourceError
24
+ from pyogrio.raw import read, write
25
+ from pyogrio.tests.conftest import (
26
+ DRIVERS,
27
+ START_FID,
28
+ prepare_testfile,
29
+ requires_shapely,
30
+ )
31
+
32
+ import pytest
33
+
34
+ with GDALEnv():
35
+ # NOTE: this must be AFTER above imports, which init the GDAL and PROJ data
36
+ # search paths
37
+ from pyogrio._ogr import has_gdal_data, has_proj_data, ogr_driver_supports_write
38
+
39
+
40
+ try:
41
+ import shapely
42
+ except ImportError:
43
+ pass
44
+
45
+
46
+ def test_gdal_data():
47
+ # test will fail if GDAL data files cannot be found, indicating an
48
+ # installation error
49
+ assert has_gdal_data()
50
+
51
+
52
+ def test_proj_data():
53
+ # test will fail if PROJ data files cannot be found, indicating an
54
+ # installation error
55
+ assert has_proj_data()
56
+
57
+
58
+ def test_get_gdal_data_path():
59
+ # test will fail if the function returns None, which means that GDAL
60
+ # cannot find data files, indicating an installation error
61
+ assert isinstance(get_gdal_data_path(), str)
62
+
63
+
64
+ def test_gdal_geos_version():
65
+ assert __gdal_geos_version__ is None or isinstance(__gdal_geos_version__, tuple)
66
+
67
+
68
+ @pytest.mark.parametrize(
69
+ "path,expected",
70
+ [
71
+ ("test.shp", "ESRI Shapefile"),
72
+ ("test.shp.zip", "ESRI Shapefile"),
73
+ ("test.geojson", "GeoJSON"),
74
+ ("test.geojsonl", "GeoJSONSeq"),
75
+ ("test.gpkg", "GPKG"),
76
+ pytest.param(
77
+ "test.gpkg.zip",
78
+ "GPKG",
79
+ marks=pytest.mark.skipif(
80
+ __gdal_version__ < (3, 7, 0),
81
+ reason="writing *.gpkg.zip requires GDAL >= 3.7.0",
82
+ ),
83
+ ),
84
+ # postgres can be detected by prefix instead of extension
85
+ pytest.param(
86
+ "PG:dbname=test",
87
+ "PostgreSQL",
88
+ marks=pytest.mark.skipif(
89
+ "PostgreSQL" not in list_drivers(),
90
+ reason="PostgreSQL path test requires PostgreSQL driver",
91
+ ),
92
+ ),
93
+ ],
94
+ )
95
+ def test_detect_write_driver(path, expected):
96
+ assert detect_write_driver(path) == expected
97
+
98
+
99
+ @pytest.mark.parametrize(
100
+ "path",
101
+ [
102
+ "test.svg", # only supports read
103
+ "test.", # not a valid extension
104
+ "test", # no extension or prefix
105
+ "test.foo", # not a valid extension
106
+ "FOO:test", # not a valid prefix
107
+ ],
108
+ )
109
+ def test_detect_write_driver_unsupported(path):
110
+ with pytest.raises(ValueError, match="Could not infer driver from path"):
111
+ detect_write_driver(path)
112
+
113
+
114
+ @pytest.mark.parametrize("path", ["test.xml"])
115
+ def test_detect_write_driver_multiple_unsupported(path):
116
+ with pytest.raises(ValueError, match="multiple drivers are available "):
117
+ detect_write_driver(path)
118
+
119
+
120
+ @pytest.mark.parametrize(
121
+ "driver,expected",
122
+ [
123
+ # drivers known to be well-supported by pyogrio
124
+ ("ESRI Shapefile", True),
125
+ ("GeoJSON", True),
126
+ ("GeoJSONSeq", True),
127
+ ("GPKG", True),
128
+ # drivers not supported for write by GDAL
129
+ ("HTTP", False),
130
+ ("OAPIF", False),
131
+ ],
132
+ )
133
+ def test_ogr_driver_supports_write(driver, expected):
134
+ assert ogr_driver_supports_write(driver) == expected
135
+
136
+
137
+ def test_list_drivers():
138
+ all_drivers = list_drivers()
139
+
140
+ # verify that the core drivers are present
141
+ for name in ("ESRI Shapefile", "GeoJSON", "GeoJSONSeq", "GPKG", "OpenFileGDB"):
142
+ assert name in all_drivers
143
+ expected_capability = "rw"
144
+ assert all_drivers[name] == expected_capability
145
+
146
+ drivers = list_drivers(read=True)
147
+ expected = {k: v for k, v in all_drivers.items() if v.startswith("r")}
148
+ assert len(drivers) == len(expected)
149
+
150
+ drivers = list_drivers(write=True)
151
+ expected = {k: v for k, v in all_drivers.items() if v.endswith("w")}
152
+ assert len(drivers) == len(expected)
153
+
154
+ drivers = list_drivers(read=True, write=True)
155
+ expected = {
156
+ k: v for k, v in all_drivers.items() if v.startswith("r") and v.endswith("w")
157
+ }
158
+ assert len(drivers) == len(expected)
159
+
160
+
161
+ def test_list_layers(
162
+ naturalearth_lowres,
163
+ naturalearth_lowres_vsi,
164
+ naturalearth_lowres_vsimem,
165
+ line_zm_file,
166
+ curve_file,
167
+ curve_polygon_file,
168
+ multisurface_file,
169
+ no_geometry_file,
170
+ ):
171
+ assert array_equal(
172
+ list_layers(naturalearth_lowres), [["naturalearth_lowres", "Polygon"]]
173
+ )
174
+
175
+ assert array_equal(
176
+ list_layers(naturalearth_lowres_vsi[1]), [["naturalearth_lowres", "Polygon"]]
177
+ )
178
+
179
+ assert array_equal(
180
+ list_layers(naturalearth_lowres_vsimem),
181
+ [["naturalearth_lowres", "MultiPolygon"]],
182
+ )
183
+
184
+ # Measured 3D is downgraded to plain 3D during read
185
+ # Make sure this warning is raised
186
+ with pytest.warns(
187
+ UserWarning, match=r"Measured \(M\) geometry types are not supported"
188
+ ):
189
+ assert array_equal(list_layers(line_zm_file), [["line_zm", "LineString Z"]])
190
+
191
+ # Curve / surface types are downgraded to plain types
192
+ assert array_equal(list_layers(curve_file), [["curve", "LineString"]])
193
+ assert array_equal(list_layers(curve_polygon_file), [["curvepolygon", "Polygon"]])
194
+ assert array_equal(
195
+ list_layers(multisurface_file), [["multisurface", "MultiPolygon"]]
196
+ )
197
+
198
+ # Make sure that nonspatial layer has None for geometry
199
+ assert array_equal(list_layers(no_geometry_file), [["no_geometry", None]])
200
+
201
+
202
+ def test_list_layers_bytes(geojson_bytes):
203
+ layers = list_layers(geojson_bytes)
204
+
205
+ assert layers.shape == (1, 2)
206
+ assert layers[0, 0] == "test"
207
+
208
+
209
+ def test_list_layers_nonseekable_bytes(nonseekable_bytes):
210
+ layers = list_layers(nonseekable_bytes)
211
+
212
+ assert layers.shape == (1, 2)
213
+ assert layers[0, 1] == "Point"
214
+
215
+
216
+ def test_list_layers_filelike(geojson_filelike):
217
+ layers = list_layers(geojson_filelike)
218
+
219
+ assert layers.shape == (1, 2)
220
+ assert layers[0, 0] == "test"
221
+
222
+
223
+ @pytest.mark.parametrize(
224
+ "testfile",
225
+ ["naturalearth_lowres", "naturalearth_lowres_vsimem", "naturalearth_lowres_vsi"],
226
+ )
227
+ def test_read_bounds(testfile, request):
228
+ path = request.getfixturevalue(testfile)
229
+ path = path if not isinstance(path, tuple) else path[1]
230
+
231
+ fids, bounds = read_bounds(path)
232
+ assert fids.shape == (177,)
233
+ assert bounds.shape == (4, 177)
234
+ assert fids[0] == START_FID[Path(path).suffix]
235
+ # Fiji; wraps antimeridian
236
+ assert allclose(bounds[:, 0], [-180.0, -18.28799, 180.0, -16.02088])
237
+
238
+
239
+ def test_read_bounds_bytes(geojson_bytes):
240
+ fids, bounds = read_bounds(geojson_bytes)
241
+ assert fids.shape == (3,)
242
+ assert bounds.shape == (4, 3)
243
+ assert allclose(bounds[:, 0], [-180.0, -18.28799, 180.0, -16.02088])
244
+
245
+
246
+ def test_read_bounds_nonseekable_bytes(nonseekable_bytes):
247
+ fids, bounds = read_bounds(nonseekable_bytes)
248
+ assert fids.shape == (1,)
249
+ assert bounds.shape == (4, 1)
250
+ assert allclose(bounds[:, 0], [1, 1, 1, 1])
251
+
252
+
253
+ def test_read_bounds_filelike(geojson_filelike):
254
+ fids, bounds = read_bounds(geojson_filelike)
255
+ assert fids.shape == (3,)
256
+ assert bounds.shape == (4, 3)
257
+ assert allclose(bounds[:, 0], [-180.0, -18.28799, 180.0, -16.02088])
258
+
259
+
260
+ def test_read_bounds_max_features(naturalearth_lowres):
261
+ bounds = read_bounds(naturalearth_lowres, max_features=2)[1]
262
+ assert bounds.shape == (4, 2)
263
+
264
+
265
+ def test_read_bounds_unspecified_layer_warning(data_dir):
266
+ """Reading a multi-layer file without specifying a layer gives a warning."""
267
+ with pytest.warns(UserWarning, match="More than one layer found "):
268
+ read_bounds(data_dir / "sample.osm.pbf")
269
+
270
+
271
+ def test_read_bounds_negative_max_features(naturalearth_lowres):
272
+ with pytest.raises(ValueError, match="'max_features' must be >= 0"):
273
+ read_bounds(naturalearth_lowres, max_features=-1)
274
+
275
+
276
+ def test_read_bounds_skip_features(naturalearth_lowres):
277
+ expected_bounds = read_bounds(naturalearth_lowres, max_features=11)[1][:, 10]
278
+ fids, bounds = read_bounds(naturalearth_lowres, skip_features=10)
279
+ assert bounds.shape == (4, 167)
280
+ assert allclose(bounds[:, 0], expected_bounds)
281
+ assert fids[0] == 10
282
+
283
+
284
+ def test_read_bounds_negative_skip_features(naturalearth_lowres):
285
+ with pytest.raises(ValueError, match="'skip_features' must be >= 0"):
286
+ read_bounds(naturalearth_lowres, skip_features=-1)
287
+
288
+
289
+ def test_read_bounds_where_invalid(naturalearth_lowres_all_ext):
290
+ if naturalearth_lowres_all_ext.suffix == ".gpkg" and __gdal_version__ >= (3, 11, 0):
291
+ with pytest.raises(DataLayerError, match="no such column"):
292
+ read_bounds(naturalearth_lowres_all_ext, where="invalid")
293
+ else:
294
+ with pytest.raises(ValueError, match="Invalid SQL"):
295
+ read_bounds(naturalearth_lowres_all_ext, where="invalid")
296
+
297
+
298
+ def test_read_bounds_where(naturalearth_lowres):
299
+ fids, bounds = read_bounds(naturalearth_lowres, where="iso_a3 = 'CAN'")
300
+ assert fids.shape == (1,)
301
+ assert bounds.shape == (4, 1)
302
+ assert fids[0] == 3
303
+ assert allclose(bounds[:, 0], [-140.99778, 41.675105, -52.648099, 83.23324])
304
+
305
+
306
+ @pytest.mark.parametrize("bbox", [(1,), (1, 2), (1, 2, 3)])
307
+ def test_read_bounds_bbox_invalid(naturalearth_lowres, bbox):
308
+ with pytest.raises(ValueError, match="Invalid bbox"):
309
+ read_bounds(naturalearth_lowres, bbox=bbox)
310
+
311
+
312
+ def test_read_bounds_bbox(naturalearth_lowres_all_ext):
313
+ # should return no features
314
+ fids, bounds = read_bounds(
315
+ naturalearth_lowres_all_ext, bbox=(0, 0, 0.00001, 0.00001)
316
+ )
317
+
318
+ assert fids.shape == (0,)
319
+ assert bounds.shape == (4, 0)
320
+
321
+ fids, bounds = read_bounds(naturalearth_lowres_all_ext, bbox=(-85, 8, -80, 10))
322
+
323
+ assert fids.shape == (2,)
324
+ fids_expected = np.array([33, 34]) # PAN, CRI
325
+ fids_expected += START_FID[naturalearth_lowres_all_ext.suffix]
326
+ assert array_equal(fids, fids_expected)
327
+
328
+ assert bounds.shape == (4, 2)
329
+ assert allclose(
330
+ bounds.T,
331
+ [
332
+ [-82.96578305, 7.22054149, -77.24256649, 9.61161001],
333
+ [-85.94172543, 8.22502798, -82.54619626, 11.21711925],
334
+ ],
335
+ )
336
+
337
+
338
+ @requires_shapely
339
+ @pytest.mark.parametrize(
340
+ "mask",
341
+ [
342
+ {"type": "Point", "coordinates": [0, 0]},
343
+ '{"type": "Point", "coordinates": [0, 0]}',
344
+ "invalid",
345
+ ],
346
+ )
347
+ def test_read_bounds_mask_invalid(naturalearth_lowres, mask):
348
+ with pytest.raises(ValueError, match="'mask' parameter must be a Shapely geometry"):
349
+ read_bounds(naturalearth_lowres, mask=mask)
350
+
351
+
352
+ @requires_shapely
353
+ def test_read_bounds_bbox_mask_invalid(naturalearth_lowres):
354
+ with pytest.raises(ValueError, match="cannot set both 'bbox' and 'mask'"):
355
+ read_bounds(
356
+ naturalearth_lowres, bbox=(-85, 8, -80, 10), mask=shapely.Point(-105, 55)
357
+ )
358
+
359
+
360
+ @requires_shapely
361
+ @pytest.mark.parametrize(
362
+ "mask,expected",
363
+ [
364
+ ("POINT (-105 55)", [3]),
365
+ ("POLYGON ((-80 8, -80 10, -85 10, -85 8, -80 8))", [33, 34]),
366
+ (
367
+ """POLYGON ((
368
+ 6.101929 50.97085,
369
+ 5.773002 50.906611,
370
+ 5.593156 50.642649,
371
+ 6.059271 50.686052,
372
+ 6.374064 50.851481,
373
+ 6.101929 50.97085
374
+ ))""",
375
+ [121, 129, 130],
376
+ ),
377
+ (
378
+ """GEOMETRYCOLLECTION (
379
+ POINT (-7.7 53),
380
+ POLYGON ((-80 8, -80 10, -85 10, -85 8, -80 8))
381
+ )""",
382
+ [33, 34, 133],
383
+ ),
384
+ ],
385
+ )
386
+ def test_read_bounds_mask(naturalearth_lowres_all_ext, mask, expected):
387
+ mask = shapely.from_wkt(mask)
388
+
389
+ fids = read_bounds(naturalearth_lowres_all_ext, mask=mask)[0]
390
+
391
+ fids_expected = np.array(expected) + START_FID[naturalearth_lowres_all_ext.suffix]
392
+ assert array_equal(fids, fids_expected)
393
+
394
+
395
+ def test_read_bounds_bbox_intersects_vs_envelope_overlaps(naturalearth_lowres_all_ext):
396
+ # If GEOS is present and used by GDAL, bbox filter will be based on intersection
397
+ # of bbox and actual geometries; if GEOS is absent or not used by GDAL, it
398
+ # will be based on overlap of bounding boxes instead
399
+ fids, _ = read_bounds(naturalearth_lowres_all_ext, bbox=(-140, 20, -100, 45))
400
+
401
+ if __gdal_geos_version__ is None:
402
+ # bboxes for CAN, RUS overlap but do not intersect geometries
403
+ assert fids.shape == (4,)
404
+ fids_expected = np.array([3, 4, 18, 27]) # CAN, USA, RUS, MEX
405
+ fids_expected += START_FID[naturalearth_lowres_all_ext.suffix]
406
+ assert array_equal(fids, fids_expected)
407
+
408
+ else:
409
+ assert fids.shape == (2,)
410
+ fids_expected = np.array([4, 27]) # USA, MEX
411
+ fids_expected += START_FID[naturalearth_lowres_all_ext.suffix]
412
+ assert array_equal(fids, fids_expected)
413
+
414
+
415
+ @pytest.mark.parametrize(
416
+ "naturalearth_lowres", [".shp", ".shp.zip", ".gpkg", ".gpkg.zip"], indirect=True
417
+ )
418
+ def test_read_info(naturalearth_lowres):
419
+ meta = read_info(naturalearth_lowres)
420
+
421
+ assert meta["layer_name"] == "naturalearth_lowres"
422
+ assert meta["crs"] == "EPSG:4326"
423
+ assert meta["encoding"] == "UTF-8"
424
+ assert meta["fields"].shape == (5,)
425
+ assert meta["dtypes"].tolist() == ["int64", "object", "object", "object", "float64"]
426
+ assert meta["features"] == 177
427
+ assert allclose(meta["total_bounds"], (-180, -90, 180, 83.64513))
428
+ assert meta["capabilities"]["random_read"] is True
429
+ # The GPKG test files are created without spatial index
430
+ assert meta["capabilities"]["fast_spatial_filter"] is False
431
+ assert meta["capabilities"]["fast_feature_count"] is True
432
+ assert meta["capabilities"]["fast_total_bounds"] is True
433
+
434
+ if naturalearth_lowres.name.endswith((".gpkg", ".gpkg.zip")):
435
+ assert meta["fid_column"] == "fid"
436
+ assert meta["geometry_name"] == "geom"
437
+ assert meta["geometry_type"] == "MultiPolygon"
438
+ assert meta["driver"] == "GPKG"
439
+ if GDAL_GE_38:
440
+ # this capability is only True for GPKG if GDAL >= 3.8
441
+ assert meta["capabilities"]["fast_set_next_by_index"] is True
442
+ elif naturalearth_lowres.name.endswith((".shp", ".shp.zip")):
443
+ # fid_column == "" for formats where fid is not physically stored
444
+ assert meta["fid_column"] == ""
445
+ # geometry_name == "" for formats where geometry column name cannot be
446
+ # customized
447
+ assert meta["geometry_name"] == ""
448
+ assert meta["geometry_type"] == "Polygon"
449
+ assert meta["driver"] == "ESRI Shapefile"
450
+ assert meta["capabilities"]["fast_set_next_by_index"] is True
451
+ else:
452
+ raise ValueError(f"test not implemented for ext {naturalearth_lowres.suffix}")
453
+
454
+
455
+ @pytest.mark.parametrize(
456
+ "naturalearth_lowres", [*DRIVERS.keys(), ".sqlite"], indirect=True
457
+ )
458
+ def test_read_info_encoding(naturalearth_lowres):
459
+ meta = read_info(naturalearth_lowres)
460
+ assert meta["encoding"].upper() == "UTF-8"
461
+
462
+
463
+ @pytest.mark.parametrize(
464
+ "testfile", ["naturalearth_lowres_vsimem", "naturalearth_lowres_vsi"]
465
+ )
466
+ def test_read_info_vsi(testfile, request):
467
+ path = request.getfixturevalue(testfile)
468
+ path = path if not isinstance(path, tuple) else path[1]
469
+
470
+ meta = read_info(path)
471
+
472
+ assert meta["fields"].shape == (5,)
473
+ assert meta["features"] == 177
474
+
475
+
476
+ def test_read_info_bytes(geojson_bytes):
477
+ meta = read_info(geojson_bytes)
478
+
479
+ assert meta["fields"].shape == (5,)
480
+ assert meta["features"] == 3
481
+
482
+
483
+ def test_read_info_nonseekable_bytes(nonseekable_bytes):
484
+ meta = read_info(nonseekable_bytes)
485
+
486
+ assert meta["fields"].shape == (0,)
487
+ assert meta["features"] == 1
488
+
489
+
490
+ def test_read_info_filelike(geojson_filelike):
491
+ meta = read_info(geojson_filelike)
492
+
493
+ assert meta["fields"].shape == (5,)
494
+ assert meta["features"] == 3
495
+
496
+
497
+ @pytest.mark.parametrize(
498
+ "dataset_kwargs,fields",
499
+ [
500
+ ({}, ["top_level", "intermediate_level"]),
501
+ (
502
+ {"FLATTEN_NESTED_ATTRIBUTES": "YES"},
503
+ [
504
+ "top_level",
505
+ "intermediate_level_bottom_level",
506
+ ],
507
+ ),
508
+ (
509
+ {"flatten_nested_attributes": "yes"},
510
+ [
511
+ "top_level",
512
+ "intermediate_level_bottom_level",
513
+ ],
514
+ ),
515
+ (
516
+ {"flatten_nested_attributes": True},
517
+ [
518
+ "top_level",
519
+ "intermediate_level_bottom_level",
520
+ ],
521
+ ),
522
+ ],
523
+ )
524
+ def test_read_info_dataset_kwargs(nested_geojson_file, dataset_kwargs, fields):
525
+ meta = read_info(nested_geojson_file, **dataset_kwargs)
526
+ assert meta["fields"].tolist() == fields
527
+
528
+
529
+ def test_read_info_invalid_dataset_kwargs(naturalearth_lowres):
530
+ with pytest.warns(RuntimeWarning, match="does not support open option INVALID"):
531
+ read_info(naturalearth_lowres, INVALID="YES")
532
+
533
+
534
+ def test_read_info_force_feature_count_exception(data_dir):
535
+ with pytest.raises(DataLayerError, match="Could not iterate over features"):
536
+ read_info(data_dir / "sample.osm.pbf", layer="lines", force_feature_count=True)
537
+
538
+
539
+ @pytest.mark.parametrize(
540
+ "layer, force, expected",
541
+ [
542
+ ("points", False, -1),
543
+ ("points", True, 8),
544
+ ("lines", False, -1),
545
+ ("lines", True, 36),
546
+ ],
547
+ )
548
+ def test_read_info_force_feature_count(data_dir, layer, force, expected):
549
+ # the sample OSM file has non-increasing node IDs which causes the default
550
+ # custom indexing to raise an exception iterating over features
551
+ meta = read_info(
552
+ data_dir / "sample.osm.pbf",
553
+ layer=layer,
554
+ force_feature_count=force,
555
+ USE_CUSTOM_INDEXING=False,
556
+ )
557
+ assert meta["features"] == expected
558
+
559
+
560
+ @pytest.mark.parametrize(
561
+ "force_total_bounds, expected_total_bounds",
562
+ [(True, (-180.0, -90.0, 180.0, 83.64513)), (False, None)],
563
+ )
564
+ def test_read_info_force_total_bounds(
565
+ tmp_path, naturalearth_lowres, force_total_bounds, expected_total_bounds
566
+ ):
567
+ geojson_path = prepare_testfile(
568
+ naturalearth_lowres, dst_dir=tmp_path, ext=".geojsonl"
569
+ )
570
+
571
+ info = read_info(geojson_path, force_total_bounds=force_total_bounds)
572
+ if expected_total_bounds is not None:
573
+ assert allclose(info["total_bounds"], expected_total_bounds)
574
+ else:
575
+ assert info["total_bounds"] is None
576
+
577
+
578
+ def test_read_info_jsonfield(nested_geojson_file):
579
+ """Test if JSON fields types are returned correctly."""
580
+ meta = read_info(nested_geojson_file)
581
+ assert meta["ogr_types"] == ["OFTString", "OFTString"]
582
+ assert meta["ogr_subtypes"] == ["OFSTNone", "OFSTJSON"]
583
+
584
+
585
+ def test_read_info_unspecified_layer_warning(data_dir):
586
+ """Reading a multi-layer file without specifying a layer gives a warning."""
587
+ with pytest.warns(UserWarning, match="More than one layer found "):
588
+ read_info(data_dir / "sample.osm.pbf")
589
+
590
+
591
+ def test_read_info_invalid_layer(naturalearth_lowres):
592
+ with pytest.raises(ValueError, match="'layer' parameter must be a str or int"):
593
+ read_bounds(naturalearth_lowres, layer=["list_arg_is_invalid"])
594
+
595
+
596
+ def test_read_info_without_geometry(no_geometry_file):
597
+ assert read_info(no_geometry_file)["total_bounds"] is None
598
+
599
+
600
+ @pytest.mark.parametrize(
601
+ "name,value,expected",
602
+ [
603
+ ("CPL_DEBUG", "ON", True),
604
+ ("CPL_DEBUG", True, True),
605
+ ("CPL_DEBUG", "OFF", False),
606
+ ("CPL_DEBUG", False, False),
607
+ ],
608
+ )
609
+ def test_set_config_options(name, value, expected):
610
+ set_gdal_config_options({name: value})
611
+ actual = get_gdal_config_option(name)
612
+ assert actual == expected
613
+
614
+
615
+ def test_reset_config_options():
616
+ set_gdal_config_options({"foo": "bar"})
617
+ assert get_gdal_config_option("foo") == "bar"
618
+
619
+ set_gdal_config_options({"foo": None})
620
+ assert get_gdal_config_option("foo") is None
621
+
622
+
623
+ def test_error_handling(capfd):
624
+ # an operation that triggers a GDAL Failure
625
+ # -> error translated into Python exception + not printed to stderr
626
+ with pytest.raises(DataSourceError, match="No such file or directory"):
627
+ read_info("non-existent.shp")
628
+
629
+ assert capfd.readouterr().err == ""
630
+
631
+
632
+ def test_error_handling_warning(capfd, naturalearth_lowres):
633
+ # an operation that triggers a GDAL Warning
634
+ # -> translated into a Python warning + not printed to stderr
635
+ with pytest.warns(RuntimeWarning, match="does not support open option INVALID"):
636
+ read_info(naturalearth_lowres, INVALID="YES")
637
+
638
+ assert capfd.readouterr().err == ""
639
+
640
+
641
+ def test_vsimem_listtree_rmtree_unlink(naturalearth_lowres):
642
+ """Test all basic functionalities of file handling in /vsimem/."""
643
+ # Prepare test data in /vsimem
644
+ meta, _, geometry, field_data = read(naturalearth_lowres)
645
+ meta["spatial_index"] = False
646
+ meta["geometry_type"] = "MultiPolygon"
647
+ test_file_path = Path("/vsimem/pyogrio_test_naturalearth_lowres.gpkg")
648
+ test_dir_path = Path(f"/vsimem/pyogrio_dir_test/{naturalearth_lowres.stem}.gpkg")
649
+
650
+ write(test_file_path, geometry, field_data, **meta)
651
+ write(test_dir_path, geometry, field_data, **meta)
652
+
653
+ # Check if everything was created properly with listtree
654
+ files = vsi_listtree("/vsimem/")
655
+ assert test_file_path.as_posix() in files
656
+ assert test_dir_path.as_posix() in files
657
+
658
+ # Check listtree with pattern
659
+ files = vsi_listtree("/vsimem/", pattern="pyogrio_dir_test*.gpkg")
660
+ assert test_file_path.as_posix() not in files
661
+ assert test_dir_path.as_posix() in files
662
+
663
+ files = vsi_listtree("/vsimem/", pattern="pyogrio_test*.gpkg")
664
+ assert test_file_path.as_posix() in files
665
+ assert test_dir_path.as_posix() not in files
666
+
667
+ # Remove test_dir and its contents
668
+ vsi_rmtree(test_dir_path.parent)
669
+ files = vsi_listtree("/vsimem/")
670
+ assert test_file_path.as_posix() in files
671
+ assert test_dir_path.as_posix() not in files
672
+
673
+ # Remove test_file
674
+ vsi_unlink(test_file_path)
675
+
676
+
677
+ def test_vsimem_rmtree_error(naturalearth_lowres_vsimem):
678
+ with pytest.raises(NotADirectoryError, match="Path is not a directory"):
679
+ vsi_rmtree(naturalearth_lowres_vsimem)
680
+
681
+ with pytest.raises(FileNotFoundError, match="Path does not exist"):
682
+ vsi_rmtree("/vsimem/non-existent")
683
+
684
+ with pytest.raises(
685
+ OSError, match="path to in-memory file or directory is required"
686
+ ):
687
+ vsi_rmtree("/vsimem")
688
+ with pytest.raises(
689
+ OSError, match="path to in-memory file or directory is required"
690
+ ):
691
+ vsi_rmtree("/vsimem/")
692
+
693
+ # Verify that naturalearth_lowres_vsimem still exists.
694
+ assert naturalearth_lowres_vsimem.as_posix() in vsi_listtree("/vsimem")
695
+
696
+
697
+ def test_vsimem_unlink_error(naturalearth_lowres_vsimem):
698
+ with pytest.raises(IsADirectoryError, match="Path is a directory"):
699
+ vsi_unlink(naturalearth_lowres_vsimem.parent)
700
+
701
+ with pytest.raises(FileNotFoundError, match="Path does not exist"):
702
+ vsi_unlink("/vsimem/non-existent.gpkg")