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
|
import argparse
import json
import re
import xml.etree.ElementTree as ET
import requests
from kml_utils import add_placemark, add_polygon, new_document, write_kml
LIST_URL = "https://reserves-naturelles.org/reserves-naturelles/"
def get_coordinates_from_url(url: str) -> list[tuple[float, float]] | None:
"""Fetch a reserve page and extract its outer polygon ring.
Args:
url (str): URL of the reserve page.
Returns:
list[tuple[float, float]] | None: (longitude, latitude) points, or None if no
coordinates could be found on the page.
"""
response = requests.get(url)
match = re.search(
r"\[\[-?\d+\.\d+,-?\d+\.\d+\](?:,\[-?\d+\.\d+,-?\d+\.\d+\])+\]", response.text
)
if not match:
return None
return [tuple(point) for point in json.loads(match.group(0))]
def get_reserves(list_url: str = LIST_URL) -> list[dict]:
"""Fetch the reserves list page and extract each reserve's name and URL.
Args:
list_url (str, optional): URL of the reserves list page. Defaults to LIST_URL.
Returns:
list[dict]: One {"full_name", "name", "url"} entry per reserve found on the page.
"""
response = requests.get(list_url)
items = re.findall(r"<li class=\"reserve\"[\s\S]*?</li>", response.text)
reserves = []
for item in items:
full_name_match = re.search(r"title=\"([^\"]+)\"", item)
name_match = re.search(r"class=\"rsv_nom\">([^<]+)<", item)
url_match = re.search(r"href=\"([^\"]+)\"", item)
if not full_name_match or not name_match or not url_match:
print(f"Skipping incomplete list entry: {item}")
continue
reserves.append(
{
"full_name": full_name_match.group(1),
"name": name_match.group(1),
"url": url_match.group(1),
}
)
return reserves
def scrape_reserves(list_url: str = LIST_URL) -> ET.Element:
"""Scrape reserves-naturelles.org and build a KML tree with one Placemark per reserve.
Args:
list_url (str, optional): URL of the reserves list page. Defaults to LIST_URL.
Returns:
ET.Element: The root <kml> element.
"""
root, document = new_document("RN")
reserves = get_reserves(list_url)
for i, reserve in enumerate(reserves):
coordinates = get_coordinates_from_url(reserve["url"])
if not coordinates:
print(f"No coordinates found for URL: {reserve['url']}")
continue
placemark = add_placemark(
document,
name=reserve["name"],
description=f"{reserve['full_name']}<br/>{reserve['url']}",
)
add_polygon(placemark, coordinates)
print(f"Processed: {reserve['name']} ({i + 1:3d}/{len(reserves)})")
return root
def main():
parser = argparse.ArgumentParser(description="Scrape reserves-naturelles.org and export reserves to KML")
parser.add_argument("-o", "--output", default="reserves_naturelles.kml", help="Path to the output KML file")
args = parser.parse_args()
root = scrape_reserves()
write_kml(root, args.output)
if __name__ == "__main__":
main()
|