From 77aed43a99a295f3202063edefcb7ce01e011958 Mon Sep 17 00:00:00 2001 From: gdamms Date: Fri, 24 Jul 2026 12:44:20 +0200 Subject: first commit --- src/geojson2kml.py | 61 ++++++++++++++++++++++++++++++++ src/n2000.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/rn.py | 81 +++++++++++++++++++++++++++++++++++++++++++ src/split_kml.py | 73 ++++++++++++++++++++++++++++++++++++++ src/test_export.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 411 insertions(+) create mode 100644 src/geojson2kml.py create mode 100644 src/n2000.py create mode 100644 src/rn.py create mode 100644 src/split_kml.py create mode 100644 src/test_export.py (limited to 'src') diff --git a/src/geojson2kml.py b/src/geojson2kml.py new file mode 100644 index 0000000..b923d98 --- /dev/null +++ b/src/geojson2kml.py @@ -0,0 +1,61 @@ +import json +import xml.etree.ElementTree as ET + + +KML_NS = "http://www.opengis.net/kml/2.2" +ET.register_namespace("", KML_NS) + + +def geojson_to_kml(geojson: dict) -> ET.ElementTree[ET.Element]: + root = ET.Element(f"kml", xmlns=KML_NS) + document = ET.SubElement(root, f"Document") + + ET.SubElement(document, f"name").text = geojson.get("name", "Unnamed") + + for feature in geojson.get("features", []): + placemark = ET.SubElement(document, f"Placemark") + + ET.SubElement(placemark, f"name").text = "" + ET.SubElement(placemark, f"description").text = "" + + # TODO: Handle other geometry types if needed + if feature["geometry"]["type"] != "MultiPolygon": + print(f"Skipping feature with unsupported geometry type: {feature['geometry']['type']}") + continue + + for polygon in feature["geometry"]["coordinates"][0]: + polygon_el = ET.SubElement(placemark, f"Polygon") + + outer = ET.SubElement(polygon_el, f"outerBoundaryIs") + ring = ET.SubElement(outer, f"LinearRing") + + coords = ET.SubElement(ring, f"coordinates") + coords.text = "\n".join( + f"{lon},{lat}" + for lon, lat in polygon + ) + + ET.indent(root) + + return ET.ElementTree(root) + + +def main(): + import argparse + + parser = argparse.ArgumentParser(description="Convert GeoJSON to KML") + parser.add_argument("input_file", help="Path to the input GeoJSON file") + parser.add_argument("-o", "--output_file", help="Path to the output KML file") + args = parser.parse_args() + + with open(args.input_file, "r") as geojson_file: + geojson = json.load(geojson_file) + + output_file = args.output_file or args.input_file.rsplit(".", 1)[0] + ".kml" + + kml = geojson_to_kml(geojson) + kml.write(output_file, encoding="utf-8", xml_declaration=True) + + +if __name__ == "__main__": + main() diff --git a/src/n2000.py b/src/n2000.py new file mode 100644 index 0000000..357f744 --- /dev/null +++ b/src/n2000.py @@ -0,0 +1,96 @@ +import re +import requests + + +reponse = requests.get("https://www.natura2000.fr/carte-natura2000") +regex = r"\"entity_id\":\"(\d+)\"" +matches = re.findall(regex, reponse.text) + + +kml_file = open("natura_2000.kml", "w", encoding="utf-8") +kml_file.write(""" + + + Natura 2000 +""") + + +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) + + regex = r"href=\\u0022\\/site-natura\\/(.*?)\\u0022" + match = re.search(regex, response.text) + + 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)}" + response = requests.get(site_url) + + regex = r"(.*?) \| Natura 2000" + match = re.search(regex, response.text) + if not match: + print(f"No name found for {site_url}.") + continue + name = match.group(1) + + regex = r"\"type\":\"(multipolygon|polygon)\"" + match = re.search(regex, response.text) + if not match: + print(f"No geometry type found for {site_url}.") + continue + geometry_type = match.group(1) + + if geometry_type == "polygon": + regex = r"\"points\":(\[\[.*?\]\])" + elif geometry_type == "multipolygon": + regex = r"\"points\":(\[\[\[.*?\]\]\])" + else: + print(f"Unsupported geometry type {geometry_type} for {site_url}.") + continue + + match = re.search(regex, response.text) + if not match: + print(f"No coordinates found for {site_url}.") + continue + polygons = eval(match.group(1)) + + if geometry_type == "polygon": + # Wrap single polygon in a list to treat it as multipolygon + polygons = [polygons] + + kml_file.write(f""" + + {name} + + {name}
{site_url} +
+ + """) + + for polygon in polygons: + kml_file.write(""" + + + + + """) + for point in polygon[0]: + kml_file.write(f"{point['lon']},{point['lat']},0\n") + kml_file.write(""" + + + + + """) + kml_file.write(f""" + +
+ """) + + print(f"Processed: {name} ({i+1: 5d}/{len(matches)})") + +kml_file.write("""
+
""") diff --git a/src/rn.py b/src/rn.py new file mode 100644 index 0000000..0c6589c --- /dev/null +++ b/src/rn.py @@ -0,0 +1,81 @@ +import requests +import re + + +def get_coordinates_from_url(url): + response = requests.get(url) + regex = r"\[\[-?\d+\.\d+,-?\d+\.\d+\](?:,\[-?\d+\.\d+,-?\d+\.\d+\])+\]" + matche = re.search(regex, response.text) + if not matche: + return None + coordinates = eval(matche.group(0)) + return coordinates + + +response = requests.get("https://reserves-naturelles.org/reserves-naturelles/") +regex = r"
  • " +matches = re.findall(regex, response.text) + +kml_file = open("reserves_naturelles.kml", "w", encoding="utf-8") +kml_file.write(""" + + + RN +""") + + +for i, match in enumerate(matches): + + regex = r"title=\"([^\"]+)\"" + full_name = re.search(regex, match) + if not full_name: + print(f"Full name not found in the match: {match}") + continue + full_name = full_name.group(1) + + regex = r"class=\"rsv_nom\">([^<]+)<" + name = re.search(regex, match) + if not name: + print(f"Name not found in the match: {match}") + continue + name = name.group(1) + + regex = r"href=\"([^\"]+)\"" + url = re.search(regex, match) + if not url: + print(f"URL not found in the match: {match}") + continue + url = url.group(1) + + coordinates = get_coordinates_from_url(url) + if not coordinates: + print(f"Coordinates not found for URL: {url}") + continue + + kml_file.write(f""" + + {name} + + {full_name}
    {url} +
    + + + + +""") + for coordinate in coordinates: + kml_file.write(f" {coordinate[0]},{coordinate[1]},0\n") + kml_file.write(""" + + + + +
    +""") + + print(f"Processed: {name} ({i+1: 3d}/{len(matches)})") + + +kml_file.write(""" +
    +
    """) diff --git a/src/split_kml.py b/src/split_kml.py new file mode 100644 index 0000000..931704b --- /dev/null +++ b/src/split_kml.py @@ -0,0 +1,73 @@ +import copy +import os +import shutil +import xml.etree.ElementTree as ET +import pathlib +import tempfile + + +KML_NS = "http://www.opengis.net/kml/2.2" +ET.register_namespace("", KML_NS) + + +def split_kml(input_file: str, split_size: int = 5) -> list[str]: + input_path = pathlib.Path(input_file) + input_name = input_path.stem + + kml = ET.parse(input_path) + root = kml.getroot() + prefix = f"{{{root.tag.split('}')[0][1:]}}}" + document = root.find(f"{prefix}Document") + if document is None: + raise ValueError("No Document element found in the KML file.") + placemarks = document.findall(f"{prefix}Placemark") + + current_placemark = placemarks.pop(0) + + split_files = [] + + new_root = copy.deepcopy(root) + new_document = new_root.find(f"{prefix}Document") + if new_document is None: + raise ValueError("No Document element found in the new KML file.") + for placemark in new_document.findall(f"{prefix}Placemark"): + new_document.remove(placemark) + new_document.append(copy.deepcopy(current_placemark)) + current_tmp_file = tempfile.NamedTemporaryFile(suffix=".kml") + ET.ElementTree(new_root).write(current_tmp_file.name, encoding="utf-8", xml_declaration=True) + + while placemarks: + current_placemark = placemarks.pop(0) + + new_document.append(copy.deepcopy(current_placemark)) + + new_tmp_file = tempfile.NamedTemporaryFile(suffix=".kml") + ET.ElementTree(new_root).write(new_tmp_file.name, encoding="utf-8", xml_declaration=True) + + if os.path.getsize(new_tmp_file.name) < split_size * 1024 * 1024: + current_tmp_file = new_tmp_file + else: + n = len(split_files) + 1 + output_path = input_path.parent / f"{input_name}_{n}.kml" + shutil.copy(current_tmp_file.name, output_path) + split_files.append(str(output_path)) + + new_root = copy.deepcopy(root) + new_document = new_root.find(f"{prefix}Document") + if new_document is None: + raise ValueError("No Document element found in the new KML file.") + for placemark in new_document.findall(f"{prefix}Placemark"): + new_document.remove(placemark) + new_document.append(copy.deepcopy(current_placemark)) + + current_tmp_file = tempfile.NamedTemporaryFile(suffix=".kml") + ET.ElementTree(new_root).write(current_tmp_file.name, encoding="utf-8", xml_declaration=True) + + n = len(split_files) + 1 + output_path = input_path.parent / f"{input_name}_{n}.kml" + shutil.copy(current_tmp_file.name, output_path) + split_files.append(str(output_path)) + + return split_files + + diff --git a/src/test_export.py b/src/test_export.py new file mode 100644 index 0000000..8f5ff38 --- /dev/null +++ b/src/test_export.py @@ -0,0 +1,100 @@ +import re +import pathlib + + +def check_duplicate_names(kml_file: str, quiet: bool = False) -> dict: + """Check for duplicate names in the exported KML file. + + Args: + kml_file (str): Path to the KML file exported from Google My Maps. + quiet (bool, optional): If True, suppress output. Defaults to False. + Returns: + dict: A dictionary with names as keys and their counts as values. + """ + if not quiet: + print("\nChecking for duplicate names in the exported KML file...") + + with open(kml_file, "r", encoding="utf-8") as f: + content = f.read() + + regex = r"()?" + matches = re.findall(regex, content) + + names = {} + for match in matches: + name = match[1] + if name in names: + names[name] += 1 + else: + names[name] = 1 + + if not quiet: + if any(count > 1 for count in names.values()): + print("Duplicate names found in the exported KML file:") + for name, count in names.items(): + if count > 1: + print(f" - {name}: {count} times") + else: + print("No duplicate names found in the exported KML file.") + + return names + + +def check_missing_names(kml_file: str, kml_glob: str, quiet: bool = False) -> dict: + """Check for missing names in the exported KML file compared to other KML files. + + Args: + kml_file (str): Path to the KML file exported from Google My Maps. + kml_glob (str): Glob pattern to match KML files to check against the exported KML file. + quiet (bool, optional): If True, suppress output. Defaults to False. + Returns: + { 'file_name': { 'missing': [list of missing names], 'total': total number of names in the file } } + """ + if not quiet: + print("\nChecking for missing names in the exported KML file compared to other KML files...") + + names = check_duplicate_names(kml_file, quiet=True) + + missing_files = {} + + for file in pathlib.Path().glob(kml_glob): + with open(file, "r", encoding="utf-8") as f: + content = f.read() + + regex = r"()?" + matches = re.findall(regex, content) + + current_names = set(match[1] for match in matches) + + for name in current_names: + if not name in names: + if file.name not in missing_files: + missing_files[file.name] = {'missing': [], 'total': len( + current_names)-1} # because of the name of the document + missing_files[file.name]['missing'].append(name) + + if not quiet: + if not missing_files: + print("All names in the KML files matching the glob pattern were found in the exported KML file.") + else: + print("Files with names not found in the exported KML file:") + for file, count in missing_files.items(): + print(f" - {file}: missing {len(count['missing'])}/{count['total']}") + for name in count['missing']: + print(f" - {name}") + + return missing_files + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Check for duplicate or missing names in the KML file exported from Google My Maps.") + parser.add_argument("kml_file", type=str, help="Path to the KML file exported from Google My Maps.") + parser.add_argument("kml_glob", type=str, + help="Glob pattern to match KML files to check against the exported KML file.") + args = parser.parse_args() + + check_duplicate_names(args.kml_file) + check_missing_names(args.kml_file, args.kml_glob) -- cgit v1.3.1