pyogrio 0.9.0__cp310-cp310-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.

Potentially problematic release.


This version of pyogrio might be problematic. Click here for more details.

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