File size: 5,757 Bytes
4bb7968
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""
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()