diff options
| author | gdamms <damguillotin@gmail.com> | 2026-07-24 13:25:21 +0200 |
|---|---|---|
| committer | gdamms <damguillotin@gmail.com> | 2026-07-24 13:25:21 +0200 |
| commit | 9daf8e0fa5db84988679606c9b4c8f26ef463db1 (patch) | |
| tree | 0e2a0df8912f65c0b22f9efab017312df9218c76 /src/n2000.py | |
| parent | 77aed43a99a295f3202063edefcb7ce01e011958 (diff) | |
| download | dog-friendly-data-9daf8e0fa5db84988679606c9b4c8f26ef463db1.tar.gz dog-friendly-data-9daf8e0fa5db84988679606c9b4c8f26ef463db1.zip | |
clean the repo
Diffstat (limited to 'src/n2000.py')
| -rw-r--r-- | src/n2000.py | 174 |
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() |
