|
| 1 | +"""Utilities for GDAL to access remote files via virtual file system.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import logging |
| 6 | +from pathlib import Path |
| 7 | +from urllib.parse import urlparse |
| 8 | + |
| 9 | +logger = logging.getLogger(__name__) |
| 10 | + |
| 11 | + |
| 12 | +def to_gdal_path(file_path: str | Path) -> str: |
| 13 | + """Convert a file path to GDAL-compatible format, handling remote URLs. |
| 14 | +
|
| 15 | + GDAL can access remote files using virtual file systems: |
| 16 | + - /vsicurl/ for HTTP(S) URLs |
| 17 | + - /vsis3/ for S3 URLs (requires AWS credentials) |
| 18 | +
|
| 19 | + Parameters |
| 20 | + ---------- |
| 21 | + file_path : str | Path |
| 22 | + File path (local or remote URL). |
| 23 | +
|
| 24 | + Returns |
| 25 | + ------- |
| 26 | + str |
| 27 | + GDAL-compatible path string. |
| 28 | +
|
| 29 | + Examples |
| 30 | + -------- |
| 31 | + >>> to_gdal_path("/local/path/file.h5") |
| 32 | + '/local/path/file.h5' |
| 33 | + >>> to_gdal_path("https://example.com/file.h5") |
| 34 | + '/vsicurl/https://example.com/file.h5' |
| 35 | + >>> to_gdal_path("s3://bucket/path/file.h5") |
| 36 | + '/vsis3/bucket/path/file.h5' |
| 37 | +
|
| 38 | + """ |
| 39 | + path_str = str(file_path) |
| 40 | + parsed = urlparse(path_str) |
| 41 | + |
| 42 | + if parsed.scheme == "": |
| 43 | + # Local file |
| 44 | + return path_str |
| 45 | + |
| 46 | + elif parsed.scheme in ("http", "https"): |
| 47 | + # HTTP(S) URL - use /vsicurl/ |
| 48 | + return f"/vsicurl/{path_str}" |
| 49 | + |
| 50 | + elif parsed.scheme == "s3": |
| 51 | + # S3 URL - use /vsis3/ |
| 52 | + # Convert s3://bucket/path to /vsis3/bucket/path |
| 53 | + s3_path = f"{parsed.netloc}{parsed.path}" |
| 54 | + return f"/vsis3/{s3_path}" |
| 55 | + |
| 56 | + else: |
| 57 | + # Unknown scheme, return as-is |
| 58 | + logger.warning(f"Unknown URL scheme '{parsed.scheme}' for {path_str}") |
| 59 | + return path_str |
| 60 | + |
| 61 | + |
| 62 | +def to_gdal_netcdf_path(file_path: str | Path, dataset: str) -> str: |
| 63 | + """Convert file path and dataset to GDAL NETCDF format. |
| 64 | +
|
| 65 | + Parameters |
| 66 | + ---------- |
| 67 | + file_path : str | Path |
| 68 | + HDF5/NetCDF file path (local or remote). |
| 69 | + dataset : str |
| 70 | + Dataset path within the file. |
| 71 | +
|
| 72 | + Returns |
| 73 | + ------- |
| 74 | + str |
| 75 | + GDAL NETCDF path: "NETCDF:file:dataset" |
| 76 | +
|
| 77 | + Examples |
| 78 | + -------- |
| 79 | + >>> to_gdal_netcdf_path("/local/file.h5", "/data/HH") |
| 80 | + 'NETCDF:/local/file.h5:/data/HH' |
| 81 | + >>> to_gdal_netcdf_path("https://example.com/file.h5", "/data/HH") |
| 82 | + 'NETCDF:/vsicurl/https://example.com/file.h5:/data/HH' |
| 83 | + >>> to_gdal_netcdf_path("s3://bucket/file.h5", "/data/HH") |
| 84 | + 'NETCDF:/vsis3/bucket/file.h5:/data/HH' |
| 85 | +
|
| 86 | + """ |
| 87 | + gdal_path = to_gdal_path(file_path) |
| 88 | + return f"NETCDF:{gdal_path}:{dataset}" |
| 89 | + |
| 90 | + |
| 91 | +def configure_gdal_for_remote( |
| 92 | + max_retry: int = 3, |
| 93 | + timeout: int = 60, |
| 94 | + chunk_size: int = 4 * 1024 * 1024, |
| 95 | +) -> None: |
| 96 | + """Configure GDAL for optimal remote file access. |
| 97 | +
|
| 98 | + Parameters |
| 99 | + ---------- |
| 100 | + max_retry : int, optional |
| 101 | + Maximum number of retries for failed requests, by default 3. |
| 102 | + timeout : int, optional |
| 103 | + Timeout in seconds for HTTP requests, by default 60. |
| 104 | + chunk_size : int, optional |
| 105 | + Chunk size for HTTP range requests in bytes, by default 4MB. |
| 106 | +
|
| 107 | + """ |
| 108 | + from osgeo import gdal |
| 109 | + |
| 110 | + # Enable GDAL exceptions |
| 111 | + gdal.UseExceptions() |
| 112 | + |
| 113 | + # Configure HTTP/HTTPS access |
| 114 | + gdal.SetConfigOption("GDAL_HTTP_MAX_RETRY", str(max_retry)) |
| 115 | + gdal.SetConfigOption("GDAL_HTTP_RETRY_DELAY", "1") |
| 116 | + gdal.SetConfigOption("CPL_VSIL_CURL_ALLOWED_EXTENSIONS", ".h5,.hdf5,.nc,.tif") |
| 117 | + gdal.SetConfigOption("GDAL_HTTP_TIMEOUT", str(timeout)) |
| 118 | + |
| 119 | + # Enable caching |
| 120 | + gdal.SetConfigOption("VSI_CACHE", "YES") |
| 121 | + gdal.SetConfigOption("VSI_CACHE_SIZE", str(chunk_size)) |
| 122 | + |
| 123 | + # For S3 access (requires AWS credentials in environment) |
| 124 | + # Set these if you have AWS credentials: |
| 125 | + # gdal.SetConfigOption("AWS_NO_SIGN_REQUEST", "YES") # For public buckets |
| 126 | + # Or configure credentials via AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY |
| 127 | + |
| 128 | + logger.info("GDAL configured for remote file access") |
| 129 | + logger.debug(f" Max retry: {max_retry}") |
| 130 | + logger.debug(f" Timeout: {timeout}s") |
| 131 | + logger.debug(f" Chunk size: {chunk_size / 1024 / 1024:.1f} MB") |
0 commit comments