Source code for kmlorm.parsers.kml_file

"""
KMLFile class for loading and parsing KML files.

This module provides the main entry point for loading KML data from files,
strings, or URLs and exposing it through the Django-style ORM interface.
"""

# pylint: disable=too-many-instance-attributes, duplicate-code
from typing import Any, Dict, List, Optional

from ..core.managers import (
    FolderManager,
    MultiGeometryManager,
    PathManager,
    PlacemarkManager,
    PointManager,
    PolygonManager,
)
from ..models.folder import Folder
from ..models.multigeometry import MultiGeometry
from ..models.path import Path
from ..models.placemark import Placemark
from ..models.point import Point
from ..models.polygon import Polygon
from .xml_parser import XMLKMLParser


[docs] class KMLFile: """ Main class for loading and accessing KML file data. Provides Django-style managers for accessing different types of KML elements with familiar query interfaces. """ # Type annotations for manager attributes folders: FolderManager placemarks: PlacemarkManager paths: PathManager polygons: PolygonManager points: PointManager multigeometries: MultiGeometryManager
[docs] def __init__(self) -> None: """Initialize an empty KML file.""" # Initialize folders manager first self.folders = FolderManager() # Set up self-reference for folders to support nested folder flattening self.folders._folders_manager = self.folders # Managers for different element types with folders reference for flatten functionality self.placemarks = PlacemarkManager(folders_manager=self.folders) self.paths = PathManager(folders_manager=self.folders) self.polygons = PolygonManager(folders_manager=self.folders) self.points = PointManager(folders_manager=self.folders) self.multigeometries = MultiGeometryManager(folders_manager=self.folders) # Give geometry managers access to placemarks for complete collection self.points._placemarks_manager = self.placemarks self.paths._placemarks_manager = self.placemarks self.polygons._placemarks_manager = self.placemarks # Document metadata self._document_name: Optional[str] = None self._document_description: Optional[str] = None # XML parser self._parser = XMLKMLParser()
[docs] @classmethod def from_file(cls, file_path: str) -> "KMLFile": """ Load KML data from a file. Args: file_path: Path to KML or KMZ file Returns: KMLFile instance with loaded data Raises: KMLParseError: If file cannot be read or parsed FileNotFoundError: If file doesn't exist """ instance = cls() doc_name, doc_description, elements = instance._parser.parse_from_file(file_path) instance._document_name = doc_name instance._document_description = doc_description instance._populate_managers(elements) return instance
[docs] @classmethod def from_string(cls, kml_string: str) -> "KMLFile": """ Load KML data from a string. Args: kml_string: KML content as string Returns: KMLFile instance with loaded data Raises: KMLParseError: If KML cannot be parsed """ instance = cls() doc_name, doc_description, elements = instance._parser.parse_from_string(kml_string) instance._document_name = doc_name instance._document_description = doc_description instance._populate_managers(elements) return instance
[docs] @classmethod def from_url(cls, url: str) -> "KMLFile": """ Load KML data from a URL. Args: url: URL to KML or KMZ file Returns: KMLFile instance with loaded data Raises: KMLParseError: If URL cannot be accessed or parsed """ instance = cls() doc_name, doc_description, elements = instance._parser.parse_from_url(url) instance._document_name = doc_name instance._document_description = doc_description instance._populate_managers(elements) return instance
@property def document_name(self) -> Optional[str]: """Get the document name from KML.""" return self._document_name @property def document_description(self) -> Optional[str]: """Get the document description from KML.""" return self._document_description
[docs] def all_elements(self) -> List[Any]: """ Get all elements from the KML file. Returns: Combined list of all elements """ elements: List[Any] = [] elements.extend(self.placemarks.children()) elements.extend(self.folders.children()) elements.extend(self.paths.children()) elements.extend(self.polygons.children()) elements.extend(self.points.children()) elements.extend(self.multigeometries.children()) return elements
[docs] def element_counts(self) -> Dict[str, int]: """ Get counts of each element type. Returns: Dictionary with element type counts """ return { "placemarks": self.placemarks.count(), "folders": self.folders.count(), "paths": self.paths.count(), "polygons": self.polygons.count(), "points": self.points.count(), "multigeometries": self.multigeometries.count(), }
@staticmethod def _is_zip_content(content: bytes) -> bool: """ Return True if the provided bytes look like ZIP (KMZ) content. This performs a lightweight signature check for the PK.. ZIP file header. It is intentionally simple and only checks the leading bytes which is sufficient for KMZ detection in tests. """ if not content: return False # ZIP files start with 'PK' signature. The most common local file # header begins with PK\x03\x04. Check the first 4 bytes when # available, otherwise check the first 2 bytes. try: return content[:4] == b"PK\x03\x04" or content[:2] == b"PK" except (IndexError, TypeError): return False def _populate_managers(self, elements: List[Any]) -> None: """ Populate the managers with parsed elements. Args: elements: List of parsed KML element objects """ for element in elements: if isinstance(element, Placemark): self.placemarks.add(element) elif isinstance(element, Folder): self.folders.add(element) elif isinstance(element, Path): self.paths.add(element) elif isinstance(element, Polygon): self.polygons.add(element) elif isinstance(element, Point): self.points.add(element) elif isinstance(element, MultiGeometry): self.multigeometries.add(element)