142 lines
5.2 KiB
Python
142 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
KML coordinate extractor
|
||
------------------------
|
||
|
||
* Every <Polygon> element → list of [lat, lon] pairs, written to *output_polygons*.
|
||
* Every <LineString> element → list of [lat, lon] pairs, written to *output_linestrings*.
|
||
* Every <Placemark> that contains a <name> and a <Point> → “Marker Name: lat, lon”
|
||
written to *output_markers*.
|
||
|
||
The coordinates in a KML file are stored as `lon,lat[,alt]`.
|
||
The script converts them to `lat,lon` because that’s the format you asked for.
|
||
"""
|
||
|
||
import xml.etree.ElementTree as ET
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Helpers
|
||
# ----------------------------------------------------------------------
|
||
def parse_kml(file_path: Path) -> ET.ElementTree:
|
||
"""Parse the KML file; exit with a message on failure."""
|
||
try:
|
||
return ET.parse(file_path)
|
||
except ET.ParseError as exc:
|
||
print(f"ERROR: Failed to parse {file_path}: {exc}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
def _lon_lat_to_list(coord_str: str) -> list[list[float]]:
|
||
"""
|
||
Convert a KML coordinate string (lon,lat[,alt] …) into a list of [lat, lon].
|
||
Ignores malformed entries.
|
||
"""
|
||
coords = []
|
||
for pair in coord_str.strip().split():
|
||
parts = pair.split(',')
|
||
if len(parts) < 2:
|
||
continue
|
||
lon, lat = float(parts[0]), float(parts[1])
|
||
coords.append([lat, lon])
|
||
return coords
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Main extraction routine
|
||
# ----------------------------------------------------------------------
|
||
def extract_kml(
|
||
kml_file: Path,
|
||
out_polygons: Path,
|
||
out_linestrings: Path,
|
||
out_markers: Path,
|
||
) -> None:
|
||
tree = parse_kml(kml_file)
|
||
root = tree.getroot()
|
||
|
||
# Namespace handling (most KML files use the same namespace)
|
||
ns = {'kml': root.tag.split('}')[0].strip('{')}
|
||
|
||
# ------------------------------------------------------------------
|
||
# 1) Polygons
|
||
# ------------------------------------------------------------------
|
||
polygons = [] # List[ List[ [lat, lon] ] ]
|
||
for poly in root.findall('.//kml:Polygon', ns):
|
||
coord_el = poly.find('.//kml:coordinates', ns)
|
||
if coord_el is None or not coord_el.text:
|
||
continue
|
||
polygons.append(_lon_lat_to_list(coord_el.text))
|
||
|
||
# Write polygons
|
||
with out_polygons.open('w', encoding='utf-8') as fp:
|
||
for i, poly in enumerate(polygons, start=1):
|
||
fp.write(f"Polygon {i}:\n")
|
||
fp.write(str(poly) + "\n\n")
|
||
|
||
# ------------------------------------------------------------------
|
||
# 2) LineStrings
|
||
# ------------------------------------------------------------------
|
||
linestrings = [] # List[ List[ [lat, lon] ] ]
|
||
for ls in root.findall('.//kml:LineString', ns):
|
||
coord_el = ls.find('.//kml:coordinates', ns)
|
||
if coord_el is None or not coord_el.text:
|
||
continue
|
||
linestrings.append(_lon_lat_to_list(coord_el.text))
|
||
|
||
# Write LineStrings
|
||
with out_linestrings.open('w', encoding='utf-8') as fp:
|
||
for i, ls in enumerate(linestrings, start=1):
|
||
fp.write(f"LineString {i}:\n")
|
||
fp.write(str(ls) + "\n\n")
|
||
|
||
# ------------------------------------------------------------------
|
||
# 3) Markers (Point placemarks with a name)
|
||
# ------------------------------------------------------------------
|
||
markers = [] # List[ (name, [lat, lon]) ]
|
||
for pm in root.findall('.//kml:Placemark', ns):
|
||
name_el = pm.find('kml:name', ns)
|
||
point_el = pm.find('.//kml:Point/kml:coordinates', ns)
|
||
if name_el is None or point_el is None:
|
||
continue
|
||
|
||
name = name_el.text.strip()
|
||
coord_list = _lon_lat_to_list(point_el.text)
|
||
if not coord_list:
|
||
continue
|
||
markers.append((name, coord_list[0])) # a point has exactly one pair
|
||
|
||
# Write markers
|
||
with out_markers.open('w', encoding='utf-8') as fp:
|
||
for name, (lat, lon) in markers:
|
||
fp.write(f"{name}: {lat}, {lon}\n")
|
||
|
||
# ------------------------------------------------------------------
|
||
# Summary
|
||
# ------------------------------------------------------------------
|
||
print(f"Polygons written to: {out_polygons}")
|
||
print(f"LineStrings written to: {out_linestrings}")
|
||
print(f"Markers written to: {out_markers}")
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Entry‑point
|
||
# ----------------------------------------------------------------------
|
||
def main() -> None:
|
||
if len(sys.argv) != 5:
|
||
print(
|
||
"Usage: python breakout_kml.py <input_file> "
|
||
"<output_polygons> <output_linestrings> <output_markers>"
|
||
)
|
||
sys.exit(1)
|
||
|
||
input_file = Path(sys.argv[1]).expanduser().resolve()
|
||
out_polygons = Path(sys.argv[2]).expanduser().resolve()
|
||
out_linestrings = Path(sys.argv[3]).expanduser().resolve()
|
||
out_markers = Path(sys.argv[4]).expanduser().resolve()
|
||
|
||
if not input_file.exists():
|
||
print(f"ERROR: Input file not found: {input_file}", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
extract_kml(input_file, out_polygons, out_linestrings, out_markers)
|
||
|
||
if __name__ == "__main__":
|
||
main() |