tilearray 0.1.1__tar.gz → 0.2.0__tar.gz

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 (112) hide show
  1. {tilearray-0.1.1 → tilearray-0.2.0}/CHANGELOG.md +43 -1
  2. {tilearray-0.1.1 → tilearray-0.2.0}/PKG-INFO +12 -9
  3. {tilearray-0.1.1 → tilearray-0.2.0}/README.md +7 -4
  4. tilearray-0.2.0/docs/_static/guides/how-it-works-05-lazy-compute-cache.png +0 -0
  5. tilearray-0.2.0/docs/api/index.md +160 -0
  6. {tilearray-0.1.1 → tilearray-0.2.0}/docs/building-docs.md +15 -0
  7. {tilearray-0.1.1 → tilearray-0.2.0}/docs/example-sources.md +22 -6
  8. {tilearray-0.1.1 → tilearray-0.2.0}/docs/guides/ea-lidar.md +6 -5
  9. {tilearray-0.1.1 → tilearray-0.2.0}/docs/guides/gibs-bluemarble.md +4 -3
  10. {tilearray-0.1.1 → tilearray-0.2.0}/docs/guides/osm-london.md +9 -5
  11. tilearray-0.2.0/docs/how-it-works.md +168 -0
  12. {tilearray-0.1.1 → tilearray-0.2.0}/docs/index.md +1 -0
  13. {tilearray-0.1.1 → tilearray-0.2.0}/docs/install.md +1 -1
  14. {tilearray-0.1.1 → tilearray-0.2.0}/docs/quickstart.md +21 -7
  15. tilearray-0.2.0/docs/roadmap.md +107 -0
  16. tilearray-0.2.0/docs/troubleshooting.md +123 -0
  17. {tilearray-0.1.1 → tilearray-0.2.0}/pyproject.toml +19 -6
  18. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/__init__.py +12 -12
  19. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/_version.py +1 -1
  20. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/array.py +492 -47
  21. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/decode.py +21 -16
  22. tilearray-0.2.0/src/tilearray/error_messages.py +359 -0
  23. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/fetch.py +154 -47
  24. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/fetch_presets.py +19 -1
  25. tilearray-0.2.0/src/tilearray/progress.py +135 -0
  26. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/base.py +68 -32
  27. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/config.py +31 -52
  28. tilearray-0.2.0/src/tilearray/service/http.py +105 -0
  29. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/wcs.py +122 -39
  30. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/wms.py +8 -6
  31. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/wmts.py +50 -25
  32. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/xyz.py +5 -4
  33. tilearray-0.2.0/src/tilearray/tile_errors.py +88 -0
  34. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/tiles.py +41 -2
  35. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/types.py +3 -1
  36. {tilearray-0.1.1 → tilearray-0.2.0}/tests/conftest.py +4 -0
  37. tilearray-0.2.0/tests/integration/test_preset_soak.py +267 -0
  38. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_array.py +323 -10
  39. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_compute_with_policy.py +28 -0
  40. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_decode.py +91 -0
  41. tilearray-0.2.0/tests/unit/test_error_messages_stage_gate.py +259 -0
  42. tilearray-0.2.0/tests/unit/test_error_taxonomy_boundary.py +140 -0
  43. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_fetch.py +154 -1
  44. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_fetch_presets.py +16 -6
  45. tilearray-0.2.0/tests/unit/test_progress.py +415 -0
  46. tilearray-0.2.0/tests/unit/test_public_exports.py +60 -0
  47. tilearray-0.2.0/tests/unit/test_service_http.py +63 -0
  48. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_service_registry.py +2 -1
  49. tilearray-0.2.0/tests/unit/test_soft_fail.py +682 -0
  50. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_tiles.py +46 -4
  51. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_types.py +2 -1
  52. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_wcs_service.py +234 -12
  53. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_wms_service.py +2 -1
  54. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_wmts_service.py +39 -0
  55. tilearray-0.1.1/docs/_static/guides/how-it-works-05-lazy-compute-cache.png +0 -0
  56. tilearray-0.1.1/docs/api/index.md +0 -107
  57. tilearray-0.1.1/docs/how-it-works.md +0 -112
  58. tilearray-0.1.1/docs/troubleshooting.md +0 -51
  59. {tilearray-0.1.1 → tilearray-0.2.0}/.gitignore +0 -0
  60. {tilearray-0.1.1 → tilearray-0.2.0}/.pre-commit-config.yaml +0 -0
  61. {tilearray-0.1.1 → tilearray-0.2.0}/CONTRIBUTING.md +0 -0
  62. {tilearray-0.1.1 → tilearray-0.2.0}/LICENSE +0 -0
  63. {tilearray-0.1.1 → tilearray-0.2.0}/Makefile +0 -0
  64. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/.gitkeep +0 -0
  65. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/ea-lidar-elevation.json +0 -0
  66. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/ea-lidar-elevation.png +0 -0
  67. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/gibs-bluemarble.json +0 -0
  68. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/gibs-bluemarble.png +0 -0
  69. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/how-it-works-01-plan.png +0 -0
  70. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/how-it-works-02-fetch.png +0 -0
  71. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/how-it-works-03-decode.png +0 -0
  72. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/how-it-works-04-mosaic.png +0 -0
  73. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/how-it-works-06-downstream.png +0 -0
  74. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/how-it-works-pipeline.png +0 -0
  75. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/manifest.json +0 -0
  76. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/osm-london.json +0 -0
  77. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/guides/osm-london.png +0 -0
  78. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/logo/tilearray-mark-128.png +0 -0
  79. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/logo/tilearray-mark-favicon-32.png +0 -0
  80. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/logo/tilearray-mark-favicon.svg +0 -0
  81. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/logo/tilearray-mark-on-dark.svg +0 -0
  82. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_static/logo/tilearray-mark.svg +0 -0
  83. {tilearray-0.1.1 → tilearray-0.2.0}/docs/_templates/.gitkeep +0 -0
  84. {tilearray-0.1.1 → tilearray-0.2.0}/docs/conf.py +0 -0
  85. {tilearray-0.1.1 → tilearray-0.2.0}/docs/guides/index.md +0 -0
  86. {tilearray-0.1.1 → tilearray-0.2.0}/example-sources.md +0 -0
  87. {tilearray-0.1.1 → tilearray-0.2.0}/guidelines.md +0 -0
  88. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/errors.py +0 -0
  89. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/pressure.py +0 -0
  90. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/py.typed +0 -0
  91. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/__init__.py +0 -0
  92. {tilearray-0.1.1 → tilearray-0.2.0}/src/tilearray/service/requests.py +0 -0
  93. {tilearray-0.1.1 → tilearray-0.2.0}/tests/__init__.py +0 -0
  94. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/README.md +0 -0
  95. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/wcs/cassettes/TestEALidarWCSContract.test_describe_coverage.yaml +0 -0
  96. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/wcs/cassettes/TestEALidarWCSContract.test_get_capabilities.yaml +0 -0
  97. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/wcs/cassettes/TestEALidarWCSContract.test_get_coverage.yaml +0 -0
  98. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/wcs/cassettes/TestUSGS3DEPWCSContract.test_describe_coverage_axis_labels.yaml +0 -0
  99. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/wcs/cassettes/TestUSGS3DEPWCSContract.test_get_coverage_multipart_geotiff.yaml +0 -0
  100. {tilearray-0.1.1 → tilearray-0.2.0}/tests/contract/wcs/test_wcs_contract.py +0 -0
  101. {tilearray-0.1.1 → tilearray-0.2.0}/tests/data/ogc_schema_examples/lidar-composite-digital-terrain-model-dtm-1m.xml +0 -0
  102. {tilearray-0.1.1 → tilearray-0.2.0}/tests/data/wcs_fixtures/ea_lidar_describe_coverage.xml +0 -0
  103. {tilearray-0.1.1 → tilearray-0.2.0}/tests/data/wcs_fixtures/usgs_3dep_describe_coverage.xml +0 -0
  104. {tilearray-0.1.1 → tilearray-0.2.0}/tests/data/wcs_tiles/ea_lidar_64x64.tif +0 -0
  105. {tilearray-0.1.1 → tilearray-0.2.0}/tests/integration/test_live_endpoints.py +0 -0
  106. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_errors.py +0 -0
  107. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_fetch_abort_array.py +0 -0
  108. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_fetch_bench.py +0 -0
  109. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_pressure.py +0 -0
  110. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_service_requests.py +0 -0
  111. {tilearray-0.1.1 → tilearray-0.2.0}/tests/unit/test_xyz_service.py +0 -0
  112. {tilearray-0.1.1 → tilearray-0.2.0}/tox.ini +0 -0
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-11-03
11
+
12
+ Trustworthy-core release: clean-env GeoTIFF install, analysis-ready mosaic attrs, one preferred compute habit, typed public errors, default tile progress, and actionable stage-gate DX — without holding the cut for multi-AOI orchestration or later uniqueness work.
13
+
14
+ ### Documentation
15
+
16
+ - Maintainer stage-gate notebook (`notebooks/stage_gate_0_2.ipynb`) + run notes in `notebooks/README.md`: local editable install → live OSM / EA DSP mosaics → politeness knobs, soft-fail attrs, and a human checklist before the 0.2.0 cut
17
+ - Document private/keyed hosts via existing config `params=` / `headers=` (query keys, legacy username/password query params, Bearer/Basic headers); OS Maps XYZ recipe uses env-backed `params` instead of `?key=` in the URL template; soft-fail still hard-aborts 401 (related #58). Caps credential parity for WCS/WMTS is under Changed (#57)
18
+ - Default tile progress + quiet opt-out documented in how-it-works, troubleshooting (“looks hung”), and API index (#86)
19
+ - Troubleshooting: stage-gate symptom→cause table for wrong format / mistyped URL / bad `coverage_id`, and httpx (not `requests`) wording for caps failures (#88)
20
+
21
+ ### Fixed
22
+
23
+ - Soft-fail observability: `compute_with_policy` refreshes `attrs["tilearray_tile_errors"]` (`soft_failed`, `aborted`, `examples`) from `FetchProgress` after lazy `create_array` — same counters as the eager `compute=True` path (#73)
24
+
25
+ ### Breaking
26
+
27
+ - Removed `WCSConfig.for_ea_dsp` and `XYZConfig.for_openstreetmap`. Protocol configs stay on `from_url`; host politeness comes from `tilearray.fetch_presets` — compose with `WCSConfig.from_url(url, coverage_id, **ea_dsp_fetch_defaults())` and `XYZConfig.from_url(OSM_TILE_TEMPLATE, zoom=…, **osm_fetch_defaults())`. Docs, soak scripts, and tests updated to the compose path (alpha; no deprecation shim).
28
+
29
+ ### Changed
30
+
31
+ - Stage-gate errors are more actionable: unsupported formats list library (and cached DescribeCoverage) MIME values; DescribeCoverage / GetCapabilities / `build_service` messages keep status or httpx type, distinguish DNS vs HTML/login vs unknown CoverageId, and redact secret query keys; soft-fail / 401 hard-abort behaviour unchanged (#88)
32
+ - WCS GeoTIFF decode now uses ``tifffile`` directly (bytes or path); dropped the ``geotiff`` → ``zarr<2.13`` / ``numcodecs<0.16`` pin cage so clean-env installs no longer need that dependency gymnastics. XYZ-without-GDAL posture unchanged (no hard rasterio/GDAL runtime dep) (#53)
33
+ - Public-boundary failures now raise the typed `TileArrayError` hierarchy instead of bare `ValueError` / `RuntimeError`: bad inputs → `ValidationError`, caps/XML parse → `ParseError`, service detection / missing decoder → `ConfigurationError`, WCS/WMTS capabilities HTTP (`httpx`) → `NetworkError`. **Compat:** callers that catch bare `ValueError` for library validation must switch to `TileArrayError` / the specific subclass (or catch both during transition). Pydantic model field checks (`BoundingBox` coordinates, etc.) still surface via pydantic as before (#72)
34
+ - WCS/WMTS capabilities and DescribeCoverage (plus direct `WCSService.get_coverage`) now use sync **httpx** with one shared UA/timeout policy and inherit config `headers`/`params` — private-server credentials reach plan-time GETs the same way they reach tiles. Drops the runtime `requests` dependency (#57)
35
+ - Docs: one compute habit on primary paths — OSM/GIBS guides, how-it-works, troubleshooting, and API index use `compute_with_policy`; `load_array` documented as eager sugar for `create_array(..., compute=True)`; quickstart WMTS honesty note (`WMTSConfig` supported, host guide TBD / #60) (#55)
36
+
37
+ ### Added
38
+
39
+ - Default tqdm-style tile progress for multi-tile mosaics via existing `on_progress` / `FetchProgress` (scripts + notebooks). Quiet opt-out: `show_progress=False`, `TILEARRAY_PROGRESS=0`, or non-TTY/non-Jupyter display. Custom `on_progress=` still replaces the default bar. Runtime dependency: `tqdm`. No second compute API (#86)
40
+ - Analysis-ready mosaic attrs for xarray/rioxarray handoff: Affine `transform` + `transform_source="mosaic_bbox"`, `band_policy`, and elevation GeoTIFF `nodata`/`_FillValue` (NaN after decode). Documented north-up + optional rioxarray recipe; mosaic CRS remains the request `crs=` (never silently replaced by per-tile GeoTIFF tags) (#54)
41
+ - Large-AOI politeness evidence scaffold (#56): `@pytest.mark.soak` multi-tile cold mosaics under EA DSP + OSM preset ceilings (nightly via `@net`/`@slow`, deselected on PRs); offline fail-loud abort under concurrent pressure; maintainer script `scripts/soak_large_aoi_presets.py` for county-/valley-scale soaks; how-it-works / troubleshooting / building-docs note on nightly vs maintainer split
42
+ - Opt-in plan-time tile error policy for mosaics (`on_tile_error=` / `soft_fail=True`) with library preset `soft_fail_exhausted_tiles`: after retries, soft-fill tile-local exhausted failures (404 / 5xx / transport) as NaN blocks without aborting siblings; auth (401), exhausted 429, decode errors, and soft-fail fraction above `soft_fail_max_fraction` still hard-abort. Default remains hard-fail + `FetchProgress.abort` (#65)
43
+ - Top-level `FetchPolicy` export (`from tilearray import FetchPolicy`) so docs/API reference match `__all__` (#70)
44
+
45
+ ### Changed
46
+
47
+ - Public `__all__` no longer advertises parsers (`WCSParser`, `WMTSParser`), concrete services (`WCSService`, `WMSService`, `WMTSService`, `XYZService`), or `BaseService`. Prefer configs + `create_array`; low-level clients remain importable from `tilearray.service` / submodules during alpha (#70)
48
+ - Mosaic `y`/`x` coordinates are **pixel centers** consistent with `attrs["transform"]` (north-up Affine from planned bbox + shape), instead of edge-span `linspace` endpoints (#54)
49
+ - Docs: `**service_options` key families (fetch / planner / identity) and `max_concurrent*` one-job-per-name map in how-it-works, with API + troubleshooting cross-links — no public signature change (#71)
50
+
10
51
  ## [0.1.1] - 2026-11-01
11
52
 
12
53
  ### Fixed
@@ -69,6 +110,7 @@ Initial alpha release focused on lazy WCS- and XYZ-backed xarray arrays.
69
110
  - Package renamed from scaffold `ogc-array` to `tilearray`; README, CI, and metadata aligned with shipped API
70
111
  - EA Lidar WCS GeoTIFF decode and non-divisible tile resize for native-resolution tiles
71
112
 
72
- [Unreleased]: https://github.com/MatthewJWhittle/tilearray/compare/v0.1.1...HEAD
113
+ [Unreleased]: https://github.com/MatthewJWhittle/tilearray/compare/v0.2.0...HEAD
114
+ [0.2.0]: https://github.com/MatthewJWhittle/tilearray/compare/v0.1.1...v0.2.0
73
115
  [0.1.1]: https://github.com/MatthewJWhittle/tilearray/compare/v0.1.0...v0.1.1
74
116
  [0.1.0]: https://github.com/MatthewJWhittle/tilearray/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tilearray
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: A Python library for loading geospatial data from remote tile servers as xarray objects
5
5
  Project-URL: Homepage, https://github.com/MatthewJWhittle/tilearray
6
6
  Project-URL: Repository, https://github.com/MatthewJWhittle/tilearray
@@ -226,17 +226,15 @@ Classifier: Typing :: Typed
226
226
  Requires-Python: >=3.9
227
227
  Requires-Dist: dask>=2024.8.0
228
228
  Requires-Dist: eval-type-backport>=0.2.0; python_version < '3.10'
229
- Requires-Dist: geotiff>=0.2.10
230
229
  Requires-Dist: httpx>=0.28.1
231
- Requires-Dist: numcodecs<0.16,>=0.12.1
232
230
  Requires-Dist: numpy>=2.0.2
233
231
  Requires-Dist: pillow>=11.3.0
234
232
  Requires-Dist: pydantic>=2.12.3
235
233
  Requires-Dist: pyproj>=3.6.1
236
- Requires-Dist: requests>=2.32.5
237
234
  Requires-Dist: tenacity>=9.0.0
235
+ Requires-Dist: tifffile>=2024.8.30
236
+ Requires-Dist: tqdm>=4.70.1
238
237
  Requires-Dist: xarray>=2024.7.0
239
- Requires-Dist: zarr<2.13,>=2.12.0
240
238
  Provides-Extra: dev
241
239
  Requires-Dist: coverage>=7.10.7; extra == 'dev'
242
240
  Requires-Dist: hypothesis>=6.141.1; extra == 'dev'
@@ -253,6 +251,8 @@ Requires-Dist: pytest-xdist>=3.8.0; extra == 'dev'
253
251
  Requires-Dist: pytest>=7.0.0; extra == 'dev'
254
252
  Requires-Dist: respx>=0.22.0; extra == 'dev'
255
253
  Requires-Dist: ruff>=0.1.0; extra == 'dev'
254
+ Requires-Dist: types-tqdm>=4.67.3.20260205; (python_version >= '3.10') and extra == 'dev'
255
+ Requires-Dist: vcrpy>=7.0.0; extra == 'dev'
256
256
  Provides-Extra: docs
257
257
  Requires-Dist: matplotlib>=3.9.0; extra == 'docs'
258
258
  Requires-Dist: myst-parser>=3.0.0; extra == 'docs'
@@ -298,9 +298,10 @@ Prefer **`compute_with_policy`** over plain `.compute()` so Dask workers match t
298
298
 
299
299
  ```python
300
300
  from tilearray import XYZConfig, compute_with_policy, create_array
301
+ from tilearray.fetch_presets import OSM_TILE_TEMPLATE, osm_fetch_defaults
301
302
  from tilearray.types import CRS
302
303
 
303
- config = XYZConfig.for_openstreetmap(zoom=16)
304
+ config = XYZConfig.from_url(OSM_TILE_TEMPLATE, zoom=16, **osm_fetch_defaults())
304
305
  da = create_array(
305
306
  config,
306
307
  bbox=(-0.005, 51.495, 0.005, 51.505), # small window near central London
@@ -317,16 +318,18 @@ Include © OpenStreetMap contributors when displaying map imagery.
317
318
 
318
319
  ```python
319
320
  from tilearray import WCSConfig, compute_with_policy, create_array
321
+ from tilearray.fetch_presets import ea_dsp_fetch_defaults
320
322
  from tilearray.types import CRS, Format
321
323
 
322
- config = WCSConfig.for_ea_dsp(
324
+ config = WCSConfig.from_url(
323
325
  "https://environment.data.gov.uk/spatialdata/"
324
326
  "lidar-composite-digital-terrain-model-dtm-1m/wcs",
325
- coverage_id="13787b9a-26a4-4775-8523-806d13af58fc__Lidar_Composite_Elevation_DTM_1m",
327
+ "13787b9a-26a4-4775-8523-806d13af58fc__Lidar_Composite_Elevation_DTM_1m",
326
328
  crs=CRS.EPSG_27700,
327
329
  output_format=Format.GEOTIFF,
328
330
  chunk_size=(800, 800),
329
331
  grid_shape=(1, 1),
332
+ **ea_dsp_fetch_defaults(),
330
333
  )
331
334
  bbox = (431900.0, 382700.0, 432700.0, 383500.0) # British National Grid
332
335
  da = create_array(config, bbox, CRS.EPSG_27700)
@@ -334,7 +337,7 @@ elevation = compute_with_policy(da)
334
337
  print(elevation.shape, float(elevation.mean()))
335
338
  ```
336
339
 
337
- The EA Lidar preset caps sustained concurrency at **10** (AIMD ceiling). More hosts, WMS/WMTS recipes, and polite-use notes live in the [docs](https://matthewjwhittle.github.io/tilearray/).
340
+ EA Lidar fetch defaults (`tilearray.fetch_presets.ea_dsp_fetch_defaults`) cap sustained concurrency at **10** (AIMD ceiling). More hosts, WMS recipes, and polite-use notes live in the [docs](https://matthewjwhittle.github.io/tilearray/). WMTS is supported via `WMTSConfig`; a host guide is still TBD.
338
341
 
339
342
  ## Development
340
343
 
@@ -34,9 +34,10 @@ Prefer **`compute_with_policy`** over plain `.compute()` so Dask workers match t
34
34
 
35
35
  ```python
36
36
  from tilearray import XYZConfig, compute_with_policy, create_array
37
+ from tilearray.fetch_presets import OSM_TILE_TEMPLATE, osm_fetch_defaults
37
38
  from tilearray.types import CRS
38
39
 
39
- config = XYZConfig.for_openstreetmap(zoom=16)
40
+ config = XYZConfig.from_url(OSM_TILE_TEMPLATE, zoom=16, **osm_fetch_defaults())
40
41
  da = create_array(
41
42
  config,
42
43
  bbox=(-0.005, 51.495, 0.005, 51.505), # small window near central London
@@ -53,16 +54,18 @@ Include © OpenStreetMap contributors when displaying map imagery.
53
54
 
54
55
  ```python
55
56
  from tilearray import WCSConfig, compute_with_policy, create_array
57
+ from tilearray.fetch_presets import ea_dsp_fetch_defaults
56
58
  from tilearray.types import CRS, Format
57
59
 
58
- config = WCSConfig.for_ea_dsp(
60
+ config = WCSConfig.from_url(
59
61
  "https://environment.data.gov.uk/spatialdata/"
60
62
  "lidar-composite-digital-terrain-model-dtm-1m/wcs",
61
- coverage_id="13787b9a-26a4-4775-8523-806d13af58fc__Lidar_Composite_Elevation_DTM_1m",
63
+ "13787b9a-26a4-4775-8523-806d13af58fc__Lidar_Composite_Elevation_DTM_1m",
62
64
  crs=CRS.EPSG_27700,
63
65
  output_format=Format.GEOTIFF,
64
66
  chunk_size=(800, 800),
65
67
  grid_shape=(1, 1),
68
+ **ea_dsp_fetch_defaults(),
66
69
  )
67
70
  bbox = (431900.0, 382700.0, 432700.0, 383500.0) # British National Grid
68
71
  da = create_array(config, bbox, CRS.EPSG_27700)
@@ -70,7 +73,7 @@ elevation = compute_with_policy(da)
70
73
  print(elevation.shape, float(elevation.mean()))
71
74
  ```
72
75
 
73
- The EA Lidar preset caps sustained concurrency at **10** (AIMD ceiling). More hosts, WMS/WMTS recipes, and polite-use notes live in the [docs](https://matthewjwhittle.github.io/tilearray/).
76
+ EA Lidar fetch defaults (`tilearray.fetch_presets.ea_dsp_fetch_defaults`) cap sustained concurrency at **10** (AIMD ceiling). More hosts, WMS recipes, and polite-use notes live in the [docs](https://matthewjwhittle.github.io/tilearray/). WMTS is supported via `WMTSConfig`; a host guide is still TBD.
74
77
 
75
78
  ## Development
76
79
 
@@ -0,0 +1,160 @@
1
+ # API reference
2
+
3
+ Public entry points for building lazy mosaics, configuring services, and handling failures.
4
+
5
+ ## Array
6
+
7
+ ```{eval-rst}
8
+ .. currentmodule:: tilearray
9
+
10
+ .. autosummary::
11
+ :toctree: generated
12
+ :nosignatures:
13
+
14
+ create_array
15
+ load_array
16
+ compute_with_policy
17
+ array.compute_thread_pool_size
18
+ ```
19
+
20
+ `create_array` builds a lazy Dask-backed `xarray.DataArray`. Prefer {py:func}`~tilearray.compute_with_policy` over plain `.compute()` so Dask workers match the fetch ceiling. {py:func}`~tilearray.load_array` is eager sugar (`create_array(..., compute=True)` by default) — useful for tiny windows, not a second primary habit. Pass a service **config** (or URL where supported) as the first argument — not only a bare string for every service type.
21
+
22
+ Multi-tile mosaics attach a default tqdm-style progress bar through `on_progress` during compute. Opt out with `show_progress=False` or `TILEARRAY_PROGRESS=0`; pass a custom `on_progress=` to replace the bar. There is no separate `compute_with_progress` helper — use {py:func}`~tilearray.compute_with_policy` as usual. Details: {doc}`../how-it-works` · {doc}`../troubleshooting`.
23
+
24
+ `**service_options` on `create_array` / `load_array` is one bag with three key families (**fetch** politeness, **planner** geometry, **identity** / protocol). Concurrency names each have one job (`max_concurrent_requests` host ceiling vs `FetchPolicy.max_concurrent` shared gate vs `call_max_concurrent` per-call vs `compute_with_policy(..., max_concurrent=)` Dask workers). Name map: {doc}`../how-it-works`.
25
+
26
+ ## Configs
27
+
28
+ ```{eval-rst}
29
+ .. currentmodule:: tilearray
30
+
31
+ .. autosummary::
32
+ :toctree: generated
33
+ :nosignatures:
34
+
35
+ ServiceConfig
36
+ WCSConfig
37
+ WMSConfig
38
+ WMTSConfig
39
+ XYZConfig
40
+ ```
41
+
42
+ Configs stay protocol-shaped (`from_url`, …). Host politeness lives in `tilearray.fetch_presets` — compose rather than a second config habit:
43
+
44
+ ```python
45
+ from tilearray import WCSConfig, XYZConfig
46
+ from tilearray.fetch_presets import (
47
+ OSM_TILE_TEMPLATE,
48
+ ea_dsp_fetch_defaults,
49
+ osm_fetch_defaults,
50
+ )
51
+
52
+ WCSConfig.from_url(url, coverage_id, **ea_dsp_fetch_defaults())
53
+ XYZConfig.from_url(OSM_TILE_TEMPLATE, zoom=16, **osm_fetch_defaults())
54
+ ```
55
+
56
+ Custom hosts: `from_url` alone (no preset unpack). `WCSConfig.build_service()` validates coverage via live DescribeCoverage (network on first `create_array` path that needs capabilities, not only at import time).
57
+
58
+ ## Fetch presets
59
+
60
+ ```{eval-rst}
61
+ .. currentmodule:: tilearray.fetch_presets
62
+
63
+ .. autosummary::
64
+ :toctree: generated
65
+ :nosignatures:
66
+
67
+ ea_dsp_fetch_defaults
68
+ osm_fetch_defaults
69
+ tilearray_user_agent
70
+ OSM_TILE_TEMPLATE
71
+ EA_LIDAR_WCS_URL
72
+ EA_LIDAR_COVERAGE_ID
73
+ ```
74
+
75
+ Import from `tilearray.fetch_presets` (not re-exported on the package root — keeps host folklore off the primary config surface). Constants such as `EA_LIDAR_BENCH_BBOX` are for tests/benches; prefer the helpers above for callers.
76
+
77
+ ## Types
78
+
79
+ ```{eval-rst}
80
+ .. currentmodule:: tilearray
81
+
82
+ .. autosummary::
83
+ :toctree: generated
84
+ :nosignatures:
85
+
86
+ types.CRS
87
+ types.Format
88
+ types.BoundingBox
89
+ types.TileRequest
90
+ types.TileResponse
91
+ ```
92
+
93
+ ## Errors
94
+
95
+ ```{eval-rst}
96
+ .. currentmodule:: tilearray
97
+
98
+ .. autosummary::
99
+ :toctree: generated
100
+ :nosignatures:
101
+
102
+ TileArrayError
103
+ ServiceError
104
+ ValidationError
105
+ NetworkError
106
+ ParseError
107
+ ConfigurationError
108
+ ```
109
+
110
+ {py:exc}`NetworkError` means a tile failed after retries — mosaics do not leave silent NaN holes **by default**. Caps/capabilities HTTP failures (WCS/WMTS `requests` paths) also raise {py:exc}`NetworkError`.
111
+
112
+ Prefer catching {py:exc}`TileArrayError` (or a subclass) at public boundaries: bad inputs → {py:exc}`ValidationError`, XML/capabilities parse → {py:exc}`ParseError`, service detection / missing decoder → {py:exc}`ConfigurationError`. Do not rely on bare `ValueError` / `RuntimeError` from library entry points.
113
+
114
+ ## Tile errors (opt-in soft-fail)
115
+
116
+ ```{eval-rst}
117
+ .. currentmodule:: tilearray
118
+
119
+ .. autosummary::
120
+ :toctree: generated
121
+ :nosignatures:
122
+
123
+ TileErrorAction
124
+ TileErrorContext
125
+ soft_fail_exhausted_tiles
126
+ ```
127
+
128
+ Pass plan-time ``on_tile_error=`` (or ``soft_fail=True``) into {py:func}`~tilearray.create_array` / {py:func}`~tilearray.load_array`. The library preset {py:func}`~tilearray.soft_fail_exhausted_tiles` soft-fills exhausted tile-local fetch failures (404 / 5xx / transport) as NaN (or ``soft_fill_value``) blocks, keeps retries, hard-fails auth / exhausted 429 / decode errors, and aborts when soft-failed fraction exceeds ``soft_fail_max_fraction``. Default remains hard-fail + sibling abort.
129
+
130
+ ## Fetch
131
+
132
+ ```{eval-rst}
133
+ .. currentmodule:: tilearray
134
+
135
+ .. autosummary::
136
+ :toctree: generated
137
+ :nosignatures:
138
+
139
+ FetchPolicy
140
+ ```
141
+
142
+ `FetchPolicy` controls concurrency, retries, timeouts, rate limits, and optional AIMD / circuit-breaker behaviour. Service configs expose the same knobs and build a policy via `.fetch_policy()`. Advanced callers may also import it as `tilearray.fetch.FetchPolicy`.
143
+
144
+ `FetchPolicy.max_concurrent` is the **shared process-gate** ceiling. Per-call politeness is `call_max_concurrent` (or a lower `max_concurrent_requests` override on `create_array`). `compute_with_policy(..., max_concurrent=)` sizes Dask workers only — see {doc}`../how-it-works`.
145
+
146
+ ## Registry
147
+
148
+ ```{eval-rst}
149
+ .. currentmodule:: tilearray
150
+
151
+ .. autosummary::
152
+ :toctree: generated
153
+ :nosignatures:
154
+
155
+ get_service
156
+ register_service
157
+ detect_service_type
158
+ ```
159
+
160
+ Concrete service clients and parsers (`WCSService`, `WMSService`, `WMTSService`, `XYZService`, `BaseService`, `WCSParser`, `WMTSParser`) are **not** part of the advertised public surface — import them from `tilearray.service` / `tilearray.service.wcs` (etc.) only if you need low-level access. Prefer configs + `create_array`. Arrays from `create_array` are north-up (row 0 = north). Mosaic attrs include `crs`, Affine `transform` (`transform_source="mosaic_bbox"`), `band_policy`, and elevation `nodata`/`_FillValue` when the format is GeoTIFF — see {doc}`../how-it-works` for the rioxarray handoff.
@@ -24,6 +24,21 @@ make docs-artifacts
24
24
 
25
25
  That rebuilds How it works diagrams (offline) and live usage-guide mosaics (network, polite presets). Commit updated assets when they change.
26
26
 
27
+ ## Politeness soak / large-AOI evidence
28
+
29
+ PR CI stays offline (`not slow and not net`). Live preset soaks are opt-in:
30
+
31
+ ```bash
32
+ # Nightly-scale multi-tile soak (EA + OSM ceilings + fail-loud contract)
33
+ uv run pytest -m soak --timeout=300 --cov-fail-under=0
34
+
35
+ # Maintainer large-AOI soak (hundreds–thousands of tiles; feeds 0.3 evidence)
36
+ uv run python scripts/soak_large_aoi_presets.py --host both --profile medium
37
+ uv run python scripts/soak_large_aoi_presets.py --host ea --profile large
38
+ ```
39
+
40
+ Design notes live in {doc}`how-it-works` (Politeness evidence). Do not wire large profiles into always-on PR jobs.
41
+
27
42
  ## First open release checklist (`0.1.0`)
28
43
 
29
44
  Do **not** push the release tag until the items below that need human secrets/clicks are done.
@@ -1,11 +1,11 @@
1
1
  # Example sources
2
2
 
3
- Public endpoints and thin fetch presets for testing host quirks — **not** a product catalogue. Prefer the presets in {doc}`quickstart` where they apply; use `from_url` for everything else. Stay polite: identifiable User-Agent where required, and do not raise concurrency past a host's documented ceiling.
3
+ Public endpoints and thin fetch presets for testing host quirks — **not** a product catalogue. Prefer `from_url` + `tilearray.fetch_presets` helpers in {doc}`quickstart` where they apply; use bare `from_url` for everything else. Stay polite: identifiable User-Agent where required, and do not raise concurrency past a host's documented ceiling.
4
4
 
5
5
  ## OpenStreetMap raster tiles (XYZ)
6
6
 
7
- - Template: `https://tile.openstreetmap.org/{z}/{x}/{y}.png`
8
- - Prefer `XYZConfig.for_openstreetmap(zoom=...)`.
7
+ - Template: `https://tile.openstreetmap.org/{z}/{x}/{y}.png` (`OSM_TILE_TEMPLATE`)
8
+ - Prefer `XYZConfig.from_url(OSM_TILE_TEMPLATE, zoom=..., **osm_fetch_defaults())`.
9
9
  - OSMF wants an identifiable User-Agent and polite use — the preset applies fixed limits (max 2 in-flight, ~2 req/s, no AIMD).
10
10
  - Open data; include © OpenStreetMap contributors when displaying.
11
11
 
@@ -13,7 +13,7 @@ Public endpoints and thin fetch presets for testing host quirks — **not** a pr
13
13
 
14
14
  - URL: `https://environment.data.gov.uk/spatialdata/lidar-composite-digital-terrain-model-dtm-1m/wcs`
15
15
  - CoverageId: `13787b9a-26a4-4775-8523-806d13af58fc__Lidar_Composite_Elevation_DTM_1m`
16
- - Prefer `WCSConfig.for_ea_dsp(...)` with this coverage id.
16
+ - Prefer `WCSConfig.from_url(url, coverage_id, **ea_dsp_fetch_defaults())` with this coverage id.
17
17
  - The CoverageId comes from GetCapabilities (often `UUID__LayerName`) — never assume the URL path slug is the id.
18
18
  - Preset AIMD: start 8, ceiling **10**, floor 4, ×0.75 pressure, sustained-403 circuit breaker. For county-scale cold mosaics, keep `max_concurrent_requests` ≤ **10**.
19
19
 
@@ -40,8 +40,24 @@ Public endpoints and thin fetch presets for testing host quirks — **not** a pr
40
40
 
41
41
  ## OS Maps API ZXY (needs key)
42
42
 
43
- - Template: `https://api.os.uk/maps/raster/v1/zxy/Road_3857/{z}/{x}/{y}.png?key=…`
44
- - Useful for real HTTP 429 throttle behaviour. Attribution required. Do not commit keys.
43
+ - Template (no secret in the URL): `https://api.os.uk/maps/raster/v1/zxy/Road_3857/{z}/{x}/{y}.png`
44
+ - Pass the key via config `params` from the environment — prefer this over `?key=…` in the template:
45
+
46
+ ```python
47
+ import os
48
+ from tilearray import XYZConfig, compute_with_policy, create_array
49
+ from tilearray.types import CRS
50
+
51
+ config = XYZConfig.from_url(
52
+ "https://api.os.uk/maps/raster/v1/zxy/Road_3857/{z}/{x}/{y}.png",
53
+ zoom=10,
54
+ params={"key": os.environ["OS_MAPS_API_KEY"]},
55
+ )
56
+ da = create_array(config, bbox=(-0.1, 51.4, 0.0, 51.5), crs=CRS.EPSG_4326)
57
+ arr = compute_with_policy(da)
58
+ ```
59
+
60
+ - Useful for real HTTP 429 throttle behaviour. Attribution required. Do not commit keys. Same `params=` / `headers=` pattern covers other keyed or private tile hosts — see {doc}`troubleshooting`.
45
61
 
46
62
  ## Other XYZ (restricted)
47
63
 
@@ -2,13 +2,13 @@
2
2
 
3
3
  Fetch a small British National Grid window from the Environment Agency Lidar composite DTM (WCS), plot elevation, and summarise the distribution.
4
4
 
5
- Uses `WCSConfig.for_ea_dsp` (AIMD under a hard ceiling, pressure circuit breaker). Keep large cold mosaics at or below the preset ceiling — higher limits can trip gateway pressure.
5
+ Uses `WCSConfig.from_url(..., **ea_dsp_fetch_defaults())` (AIMD under a hard ceiling, pressure circuit breaker). Keep large cold mosaics at or below the preset ceiling — higher limits can trip gateway pressure.
6
6
 
7
7
  ## Code
8
8
 
9
9
  ```python
10
- from tilearray import compute_with_policy, create_array
11
- from tilearray.service import WCSConfig
10
+ from tilearray import WCSConfig, compute_with_policy, create_array
11
+ from tilearray.fetch_presets import ea_dsp_fetch_defaults
12
12
  from tilearray.types import CRS, Format
13
13
 
14
14
  wcs_url = (
@@ -19,13 +19,14 @@ coverage_id = (
19
19
  "13787b9a-26a4-4775-8523-806d13af58fc__Lidar_Composite_Elevation_DTM_1m"
20
20
  )
21
21
 
22
- config = WCSConfig.for_ea_dsp(
22
+ config = WCSConfig.from_url(
23
23
  wcs_url,
24
- coverage_id=coverage_id,
24
+ coverage_id,
25
25
  crs=CRS.EPSG_27700,
26
26
  output_format=Format.GEOTIFF,
27
27
  chunk_size=(64, 64),
28
28
  resolution=(1.0, 1.0),
29
+ **ea_dsp_fetch_defaults(),
29
30
  )
30
31
 
31
32
  # ~256 m OSGB window
@@ -5,7 +5,7 @@ Load a small mid-Atlantic window from NASA GIBS as JPEG RGB tiles. Uses `XYZConf
5
5
  ## Code
6
6
 
7
7
  ```python
8
- from tilearray import XYZConfig, create_array
8
+ from tilearray import XYZConfig, compute_with_policy, create_array
9
9
  from tilearray.types import CRS, Format
10
10
 
11
11
  template = (
@@ -27,8 +27,9 @@ config = XYZConfig.from_url(
27
27
  )
28
28
 
29
29
  bbox = (-40.0, 20.0, -10.0, 45.0)
30
- da = create_array(config, bbox, CRS.EPSG_4326, compute=True)
31
- print(da.shape, da.dims) # expect (y, x, band) for RGB JPEG
30
+ da = create_array(config, bbox, CRS.EPSG_4326)
31
+ arr = compute_with_policy(da)
32
+ print(arr.shape, arr.dims) # expect (y, x, band) for RGB JPEG
32
33
  ```
33
34
 
34
35
  ## Result
@@ -2,7 +2,7 @@
2
2
 
3
3
  Load a small slippy-map window, compute it, and plot the mosaic.
4
4
 
5
- Uses `XYZConfig.for_openstreetmap` (identifiable User-Agent, max 2 in-flight, ~2 req/s). Include **© OpenStreetMap contributors** when displaying the imagery.
5
+ Uses `XYZConfig.from_url(OSM_TILE_TEMPLATE, …, **osm_fetch_defaults())` (identifiable User-Agent, max 2 in-flight, ~2 req/s). Include **© OpenStreetMap contributors** when displaying the imagery.
6
6
 
7
7
  ```{note}
8
8
  OSM raster tiles are often **palette PNGs**, so the mosaic may be single-band (grayscale) today. For a full-colour RGB XYZ example see {doc}`gibs-bluemarble`.
@@ -11,18 +11,22 @@ OSM raster tiles are often **palette PNGs**, so the mosaic may be single-band (g
11
11
  ## Code
12
12
 
13
13
  ```python
14
- from tilearray import XYZConfig, create_array
14
+ from tilearray import XYZConfig, compute_with_policy, create_array
15
+ from tilearray.fetch_presets import OSM_TILE_TEMPLATE, osm_fetch_defaults
15
16
  from tilearray.types import CRS, Format
16
17
 
17
- config = XYZConfig.for_openstreetmap(
18
+ config = XYZConfig.from_url(
19
+ OSM_TILE_TEMPLATE,
18
20
  zoom=16,
19
21
  output_format=Format.PNG,
20
22
  chunk_size=(256, 256),
23
+ **osm_fetch_defaults(),
21
24
  )
22
25
 
23
26
  bbox = (-0.005, 51.495, 0.005, 51.505) # ~central London
24
- da = create_array(config, bbox, CRS.EPSG_4326, compute=True)
25
- print(da.shape, da.dims, da.attrs["service_type"])
27
+ da = create_array(config, bbox, CRS.EPSG_4326)
28
+ arr = compute_with_policy(da)
29
+ print(arr.shape, arr.dims, da.attrs["service_type"])
26
30
  ```
27
31
 
28
32
  ## Result