aboutsummaryrefslogtreecommitdiff
path: root/src/n2000.py
diff options
context:
space:
mode:
authorgdamms <damguillotin@gmail.com>2026-07-24 13:25:21 +0200
committergdamms <damguillotin@gmail.com>2026-07-24 13:25:21 +0200
commit9daf8e0fa5db84988679606c9b4c8f26ef463db1 (patch)
tree0e2a0df8912f65c0b22f9efab017312df9218c76 /src/n2000.py
parent77aed43a99a295f3202063edefcb7ce01e011958 (diff)
downloaddog-friendly-data-9daf8e0fa5db84988679606c9b4c8f26ef463db1.tar.gz
dog-friendly-data-9daf8e0fa5db84988679606c9b4c8f26ef463db1.zip
clean the repo
Diffstat (limited to 'src/n2000.py')
-rw-r--r--src/n2000.py174
1 files changed, 102 insertions, 72 deletions
diff --git a/src/n2000.py b/src/n2000.py
index 357f744..a0741a0 100644
--- a/src/n2000.py
+++ b/src/n2000.py
@@ -1,96 +1,126 @@
+import argparse
+import json
import re
+import xml.etree.ElementTree as ET
+
import requests
+from kml_utils import add_multi_polygon, add_placemark, new_document, write_kml
+
+MAP_URL = "https://www.natura2000.fr/carte-natura2000"
+POPUP_URL_TEMPLATE = (
+ "https://www.natura2000.fr/leaflet-ajax-popup/node/{entity_id}"
+ "/pop_up_n2000_field/und?_wrapper_format=drupal_ajax"
+)
+SITE_URL_TEMPLATE = "https://www.natura2000.fr/site-natura/{slug}"
-reponse = requests.get("https://www.natura2000.fr/carte-natura2000")
-regex = r"\"entity_id\":\"(\d+)\""
-matches = re.findall(regex, reponse.text)
+def get_entity_ids(map_url: str = MAP_URL) -> list[str]:
+ """Fetch the Natura 2000 map page and extract every site's numeric entity id.
-kml_file = open("natura_2000.kml", "w", encoding="utf-8")
-kml_file.write("""<?xml version="1.0" encoding="UTF-8"?>
-<kml xmlns="http://www.opengis.net/kml/2.2">
- <Document>
- <name>Natura 2000</name>
-""")
+ Args:
+ map_url (str, optional): URL of the Natura 2000 map page. Defaults to MAP_URL.
+ Returns:
+ list[str]: Entity ids found on the page.
+ """
+ response = requests.get(map_url)
+ return re.findall(r"\"entity_id\":\"(\d+)\"", response.text)
-for i, match in enumerate(matches):
- url = f"https://www.natura2000.fr/leaflet-ajax-popup/node/{match}/pop_up_n2000_field/und?_wrapper_format=drupal_ajax"
- response = requests.get(url)
+def get_site_slug(entity_id: str) -> str | None:
+ """Resolve an entity id to its site page URL slug via the popup endpoint.
- regex = r"href=\\u0022\\/site-natura\\/(.*?)\\u0022"
- match = re.search(regex, response.text)
+ Args:
+ entity_id (str): Numeric entity id, as found by get_entity_ids.
+ Returns:
+ str | None: The site URL slug, or None if it could not be found.
+ """
+ response = requests.get(POPUP_URL_TEMPLATE.format(entity_id=entity_id))
+ match = re.search(r"href=\\u0022\\/site-natura\\/(.*?)\\u0022", response.text)
+ return match.group(1) if match else None
- if not match:
- print(f"No match found for entity_id {match}.")
- continue
- site_url = f"https://www.natura2000.fr/site-natura/{match.group(1)}"
+def get_site_details(site_url: str) -> tuple[str, list[list[tuple[float, float]]]] | None:
+ """Fetch a site page and extract its name and outer polygon rings.
+
+ Args:
+ site_url (str): URL of the site page.
+ Returns:
+ tuple[str, list[list[tuple[float, float]]]] | None: (name, rings), where each ring is
+ a list of (longitude, latitude) points, or None if the name, geometry type, or
+ coordinates could not be found on the page.
+ """
response = requests.get(site_url)
- regex = r"<title>(.*?) \| Natura 2000</title>"
- match = re.search(regex, response.text)
- if not match:
+ name_match = re.search(r"<title>(.*?) \| Natura 2000</title>", response.text)
+ if not name_match:
print(f"No name found for {site_url}.")
- continue
- name = match.group(1)
+ return None
+ name = name_match.group(1)
- regex = r"\"type\":\"(multipolygon|polygon)\""
- match = re.search(regex, response.text)
- if not match:
+ type_match = re.search(r"\"type\":\"(multipolygon|polygon)\"", response.text)
+ if not type_match:
print(f"No geometry type found for {site_url}.")
- continue
- geometry_type = match.group(1)
-
+ return None
+ geometry_type = type_match.group(1)
+
if geometry_type == "polygon":
- regex = r"\"points\":(\[\[.*?\]\])"
- elif geometry_type == "multipolygon":
- regex = r"\"points\":(\[\[\[.*?\]\]\])"
+ points_regex = r"\"points\":(\[\[.*?\]\])"
else:
- print(f"Unsupported geometry type {geometry_type} for {site_url}.")
- continue
+ points_regex = r"\"points\":(\[\[\[.*?\]\]\])"
- match = re.search(regex, response.text)
- if not match:
+ points_match = re.search(points_regex, response.text)
+ if not points_match:
print(f"No coordinates found for {site_url}.")
- continue
- polygons = eval(match.group(1))
-
+ return None
+
+ polygons = json.loads(points_match.group(1))
if geometry_type == "polygon":
- # Wrap single polygon in a list to treat it as multipolygon
- polygons = [polygons]
+ polygons = [polygons] # normalize to a list of polygons, like multipolygon
+
+ rings = [[(point["lon"], point["lat"]) for point in polygon[0]] for polygon in polygons]
+ return name, rings
+
+
+def scrape_natura2000(map_url: str = MAP_URL) -> ET.Element:
+ """Scrape natura2000.fr and build a KML tree with one Placemark per site.
+
+ Args:
+ map_url (str, optional): URL of the Natura 2000 map page. Defaults to MAP_URL.
+ Returns:
+ ET.Element: The root <kml> element.
+ """
+ root, document = new_document("Natura 2000")
+
+ entity_ids = get_entity_ids(map_url)
+ for i, entity_id in enumerate(entity_ids):
+ slug = get_site_slug(entity_id)
+ if slug is None:
+ print(f"No site URL found for entity_id {entity_id}.")
+ continue
+
+ site_url = SITE_URL_TEMPLATE.format(slug=slug)
+ details = get_site_details(site_url)
+ if details is None:
+ continue
+ name, rings = details
+
+ placemark = add_placemark(document, name=name, description=f"{name}<br/>{site_url}")
+ add_multi_polygon(placemark, rings)
+
+ print(f"Processed: {name} ({i + 1:5d}/{len(entity_ids)})")
+
+ return root
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Scrape natura2000.fr and export sites to KML")
+ parser.add_argument("-o", "--output", default="natura_2000.kml", help="Path to the output KML file")
+ args = parser.parse_args()
- kml_file.write(f"""
- <Placemark>
- <name>{name}</name>
- <description>
- {name}<br/>{site_url}
- </description>
- <MultiGeometry>
- """)
+ root = scrape_natura2000()
+ write_kml(root, args.output)
- for polygon in polygons:
- kml_file.write("""
- <Polygon>
- <outerBoundaryIs>
- <LinearRing>
- <coordinates>
- """)
- for point in polygon[0]:
- kml_file.write(f"{point['lon']},{point['lat']},0\n")
- kml_file.write("""
- </coordinates>
- </LinearRing>
- </outerBoundaryIs>
- </Polygon>
- """)
- kml_file.write(f"""
- </MultiGeometry>
- </Placemark>
- """)
-
- print(f"Processed: {name} ({i+1: 5d}/{len(matches)})")
-kml_file.write(""" </Document>
-</kml>""")
+if __name__ == "__main__":
+ main()