File size: 5,889 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
145
146
"""
Downloads BDCavités (BRGM's national underground cavity inventory --
karst sinkholes, quarries, marl pits, natural cavities) for the Eure/
Risle area, via Géorisques' WFS.

Directly relevant to the bétoire investigation: this is an independent,
purpose-built dataset for exactly this phenomenon, unlike inferring it
from Hub'Eau station naming or BD TOPO's provisional karst attribute.

CONFIRMED (fetched GetCapabilities directly): endpoint
https://georisques.gouv.fr/services, WFS 1.1.0, typeName
CAVITE_LOCALISEE ("Cavités souterraines abandonnées d'origine non
minière"), GeoJSON output supported directly. Axis order for this
specific server is NOT separately confirmed -- reusing the same
lon,lat-then-lat,lon retry that scripts/download_bdtopo_hydro.py needed
for a different WFS server, since different servers have behaved
differently on this before and there's no reason to assume this one
won't too.

Usage:
    python -m scripts.download_bdcavites --check
    python -m scripts.download_bdcavites
"""
import argparse
import json
import sys
from pathlib import Path

import requests

WFS_URL = "https://georisques.gouv.fr/services"
TYPE_NAME = "CAVITE_LOCALISEE"

# Same widened bbox as scripts/download_bdtopo_hydro.py's BBOX (updated
# after confirmed real evidence of truncation on the original, tighter
# box) -- keeping both scripts scoped to the same area.
BBOX = (-0.1, 47.7, 2.1, 49.9)  # (min_lon, min_lat, max_lon, max_lat)


def check_typename() -> bool:
    params = {
        "SERVICE": "WFS", "VERSION": "1.1.0", "REQUEST": "GetFeature",
        "TYPENAME": TYPE_NAME, "MAXFEATURES": 1, "OUTPUTFORMAT": "application/json; subtype=geojson; charset=utf-8",
    }
    resp = requests.get(WFS_URL, params=params, timeout=30)
    ok, detail = _parse_response(resp)
    print(f"  {TYPE_NAME}: {'OK' if ok else 'FAILED'}")
    if not ok:
        print(f"    {detail}")
    return ok


def _parse_response(resp: "requests.Response"):
    if resp.status_code != 200:
        return False, f"HTTP {resp.status_code}: {resp.text[:400]}"
    try:
        data = resp.json()
    except ValueError:
        return False, f"Response was not JSON (likely a WFS ExceptionReport): {resp.text[:400]}"
    if isinstance(data, dict) and data.get("type") == "FeatureCollection":
        return True, ""
    return False, f"Response was JSON but not a FeatureCollection: {str(data)[:400]}"


def fetch_all_pages(bbox_param: str, page_size: int = 1000) -> list:
    all_features = []
    start_index = 0
    while True:
        params = {
            "SERVICE": "WFS", "VERSION": "1.1.0", "REQUEST": "GetFeature",
            "TYPENAME": TYPE_NAME, "BBOX": bbox_param,
            "OUTPUTFORMAT": "application/json; subtype=geojson; charset=utf-8",
            "MAXFEATURES": page_size, "STARTINDEX": start_index,
        }
        resp = requests.get(WFS_URL, params=params, timeout=60)
        ok, detail = _parse_response(resp)
        if not ok:
            print(f"    ERROR at startIndex={start_index}: {detail}")
            break
        data = resp.json()
        features = data.get("features", [])
        if not features:
            break
        all_features.extend(features)
        if len(features) < page_size:
            break
        start_index += page_size
    return all_features


def fetch_with_axis_retry(bbox: tuple, out_path: Path) -> None:
    min_lon, min_lat, max_lon, max_lat = bbox
    orderings = [
        ("lon,lat", f"{min_lon},{min_lat},{max_lon},{max_lat},urn:ogc:def:crs:EPSG::4326"),
        ("lat,lon", f"{min_lat},{min_lon},{max_lat},{max_lon},urn:ogc:def:crs:EPSG::4326"),
    ]
    features = []
    for label, bbox_param in orderings:
        print(f"  trying axis order {label}...")
        features = fetch_all_pages(bbox_param)
        if features:
            print(f"  -> {label} worked ({len(features)} feature(s))")
            break
        print(f"  -> {label} returned 0 features")

    geojson = {"type": "FeatureCollection", "features": features}
    out_path.write_text(json.dumps(geojson))
    if features:
        print(f"Saved {len(features)} feature(s) to {out_path}")
    else:
        print(f"Saved an EMPTY file to {out_path} -- both axis orders returned nothing. "
              f"Run --check first, or this area may genuinely have zero recorded cavities "
              f"(plausible -- BDCavités coverage is built department-by-department and "
              f"isn't uniformly complete everywhere).")


def main() -> None:
    parser = argparse.ArgumentParser(description="Download BDCavités for the Eure/Risle area")
    parser.add_argument("--check", action="store_true")
    parser.add_argument("--output-dir", type=Path, default=Path("datasets/bdcavites"))
    args = parser.parse_args()

    if args.check:
        print("Testing typeName against the live service...")
        ok = check_typename()
        if not ok:
            print("\nFailed -- the typeName or service details may have changed since this "
                  "script was written. Fetch GetCapabilities directly to check:")
            print(f"  {WFS_URL}?SERVICE=WFS&VERSION=1.1.0&REQUEST=GetCapabilities")
        else:
            print("\nOK. Re-run without --check to download.")
        return

    args.output_dir.mkdir(parents=True, exist_ok=True)
    print("Fetching CAVITE_LOCALISEE (BDCavités)...")
    fetch_with_axis_retry(BBOX, args.output_dir / "cavite_localisee.geojson")
    print()
    print("Once downloaded, cross-reference against the amont/aval bétoire stations "
          "(H605641101 at 48.98492,0.78902 and H605641201 at 49.04707,0.79984) the same "
          "way scripts/analyze_bdtopo_hydro.py's check_karst_near_betoire did for the "
          "BD TOPO karst attribute -- this is a genuinely independent second check, "
          "not a re-run of the same one.")


if __name__ == "__main__":
    main()