""" Fetches fresh IDPR (Indice de Développement et de Persistance des Réseaux) values directly from BRGM's live geoservice, replacing/ supplementing the manually-uploaded idpr.csv. CONFIRMED (fetched GetCapabilities directly): endpoint http://geoservices.brgm.fr/geologie, WMS 1.3.0, live and responding. IDPR is raster/grid data at five resolutions (IDPR_50M down to IDPR_5000M) -- not a vector feature type, so WFS GetFeature (the pattern used everywhere else in this project) doesn't apply. The correct WMS operation for extracting a value at a specific point from a raster layer is GetFeatureInfo: request a small map image centered on the point, then ask "what's the value at this pixel." This queries ONE point at a time (GetFeatureInfo has no bulk/vectorized equivalent the way BD TOPO's WFS BBOX queries did) -- scoped to the 27 real gauge stations, matching what "redownload IDPR" means (fresh values at known station points), not the full ~4,500-node reach graph (which would mean thousands of individual HTTP requests -- a different, much larger undertaking than this). Usage: python -m scripts.fetch_idpr_brgm --check python -m scripts.fetch_idpr_brgm --stations datasets/station_elevations.csv """ import argparse import re from pathlib import Path import pandas as pd import requests WMS_URL = "http://geoservices.brgm.fr/geologie" LAYER = "IDPR_50M" # highest resolution; use --layer to try a coarser one if this fails def get_feature_info_at_point(lat: float, lon: float, layer: str = LAYER, half_extent_deg: float = 0.001) -> "float | None": """ Query IDPR value at a single point via WMS GetFeatureInfo: request a tiny 3x3 pixel map centered on (lat, lon), then ask for the value at the center pixel. """ minx, miny = lon - half_extent_deg, lat - half_extent_deg maxx, maxy = lon + half_extent_deg, lat + half_extent_deg params = { "SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetFeatureInfo", "LAYERS": layer, "QUERY_LAYERS": layer, "STYLES": "", "BBOX": f"{minx},{miny},{maxx},{maxy}", "SRS": "EPSG:4326", "WIDTH": 3, "HEIGHT": 3, "X": 1, "Y": 1, "INFO_FORMAT": "text/plain", } try: resp = requests.get(WMS_URL, params=params, timeout=30) except requests.RequestException as e: print(f" request failed: {e}") return None if resp.status_code != 200: print(f" HTTP {resp.status_code}") return None return parse_getfeatureinfo_value(resp.text) def parse_getfeatureinfo_value(text: str) -> "float | None": """ MapServer's text/plain GetFeatureInfo output is typically a small block like: Layer 'IDPR_50M' Feature 0 value = '885' Parsing defensively: find any line with 'value' (case-insensitive) and extract the first number on it, rather than assuming an exact format -- MapServer's plain-text output format has enough real variation across deployments that a strict parser is more likely to silently return nothing than a permissive one is to return a wrong number. """ for line in text.splitlines(): if "value" in line.lower(): match = re.search(r"[-+]?\d*\.?\d+", line) if match: return float(match.group()) return None def check_service() -> bool: # A real point known to be in mainland France, well within IDPR coverage test_lat, test_lon = 49.03, 0.79 print(f"Testing {LAYER} at ({test_lat}, {test_lon})...") value = get_feature_info_at_point(test_lat, test_lon) if value is not None: print(f" OK: value = {value}") return True print(" FAILED: no value returned. Try --layer IDPR_100M or IDPR_500M " "(coarser resolutions), or check GetCapabilities directly:") print(f" {WMS_URL}?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities") return False def fetch_for_stations(stations_path: Path, layer: str, output_path: Path) -> None: stations = pd.read_csv(stations_path) lat_col = "latitude" if "latitude" in stations.columns else "lat" lon_col = "longitude" if "longitude" in stations.columns else "lon" rows = [] for _, row in stations.iterrows(): code = row["station_code"] value = get_feature_info_at_point(row[lat_col], row[lon_col], layer=layer) print(f" {code}: {value}") rows.append({"station_code": code, "idpr_value_brgm_live": value, "latitude": row[lat_col], "longitude": row[lon_col]}) out = pd.DataFrame(rows) n_ok = out["idpr_value_brgm_live"].notna().sum() print() print(f"{n_ok}/{len(out)} stations got a real value") out.to_csv(output_path, index=False) print(f"Saved to {output_path}") if n_ok < len(out): print("Some stations got no value -- this could mean they're just outside " "IDPR's coverage, or the layer/resolution needs adjusting. Compare " "against the existing idpr.csv for those specific stations before " "assuming the live fetch is wrong.") def main() -> None: parser = argparse.ArgumentParser(description="Fetch fresh IDPR values from BRGM's live geoservice") parser.add_argument("--check", action="store_true") parser.add_argument("--stations", type=Path, default=Path("datasets/station_elevations.csv")) parser.add_argument("--layer", type=str, default=LAYER) parser.add_argument("--output", type=Path, default=Path("datasets/idpr_brgm_live.csv")) args = parser.parse_args() if args.check: check_service() return fetch_for_stations(args.stations, args.layer, args.output) if __name__ == "__main__": main()