Quick Start Guide

This guide will get you up and running with KML ORM in just a few minutes.

Installation

Install KML ORM using pip:

pip install kmlorm

Requirements

  • Python 3.11+

  • lxml

Note

Working with Folders

KML files often organize placemarks inside folders. By default, kml.placemarks.all() returns all the placemarks in a document even though they may be nested below the top level document. Think of kml.placemarks.all() as roughly equivalent to Django’s Placemark.objects.all() queryset. In Django, the database is implied. To make a true parallel, the Django statement would be written, database.Placemark.objects.all(). (Don’t. That won’t work in Django. We are just drawing comparisons for understanding.)

To retrieve placemarks only at the “root” level, use kml.placemarks.children().

This changed in version 1.0.0. Prior to this version, to get the placemarks at the “root” level, the statement was kml.placemarks.all(), and to get all the placemarks in a file, the statement was kml.placemarks.all(flatten=True).

  • Document-level only: kml.placemarks.children()

  • All placemarks (including those in folders): kml.placemarks.all()

Basic Usage

Loading KML Data

KML ORM can load KML data from files, strings, or URLs:

from kmlorm import KMLFile

# From file
kml = KMLFile.from_file('places.kml')

# From string
kml_string = """<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
  <Document>
    <Placemark>
      <name>Test Store</name>
      <Point>
        <coordinates>-76.5,39.3,0</coordinates>
      </Point>
    </Placemark>
  </Document>
</kml>"""
kml = KMLFile.from_string(kml_string)

# From URL (example with localhost server)
kml = KMLFile.from_url('http://localhost:8000/data.kml')

Accessing Elements

Once loaded, use Django-style managers to access different element types:

# Get placemarks at document root level only
root_placemarks = kml.placemarks.children()
print(f"Found {len(root_placemarks)} root-level placemarks")

# Get ALL placemarks including those nested in folders
all_placemarks = kml.placemarks.all()
print(f"Found {len(all_placemarks)} total placemarks")

# Access different element types at the "root" level
folders = kml.folders.children()
paths = kml.paths.children()
polygons = kml.polygons.children()
points = kml.points.children()
multigeometries = kml.multigeometries.children()

# Or get ALL elements of each type using all()
all_folders = kml.folders.all()
all_paths = kml.paths.all()
all_polygons = kml.polygons.all()
all_points = kml.points.all()
all_multigeometries = kml.multigeometries.all()

# Note: For geometry elements (points, paths, polygons), .all() collects from:
# - Standalone geometries at any level
# - Geometries within Placemarks (e.g., placemark.point)
# - Geometries within MultiGeometry containers

KML Structure Example:

<kml>
  <Document>
    <Folder>
      <Folder>...</Folder>           <!-- Nested folder: found by all() -->
      <Placemark>...</Placemark>     <!-- Nested placemark: found by all() -->
      <Path>...</Path>               <!-- Nested path: found by all() -->
      <MultiGeometry>...</MultiGeometry> <!-- Nested multigeometry: found by all() -->
    </Folder>
    <Folder>...</Folder>             <!-- Direct child folder: found by children() -->
    <Placemark>...</Placemark>       <!-- Direct child placemark: found by children() -->
    <Path>...</Path>                 <!-- Direct child path: found by children() -->
    <MultiGeometry>...</MultiGeometry> <!-- Direct child multigeometry: found by children() -->
  </Document>
</kml>

Basic Queries

Filter elements using Django-style query methods:

# Filter by name (root-level placemarks only)
capital_stores = kml.placemarks.filter(name__icontains='capital')

# Filter ALL placemarks including those in folders
all_capital_stores = kml.placemarks.all().filter(name__icontains='capital')

# Exclude items (root-level only)
not_capital = kml.placemarks.exclude(name__icontains='capital')

# Exclude ALL placemarks including those in folders
all_not_capital = kml.placemarks.all().exclude(name__icontains='capital')

# Get a single item (searches root-level only)
store = kml.placemarks.get(name='Capital Electric - Rosedale')

# Get from ALL placemarks including folders
store = kml.placemarks.all().get(name='Capital Electric - Rosedale')

# Check if items exist (root-level only)
has_stores = kml.placemarks.filter(name__icontains='store').exists()

# Check ALL placemarks including folders
has_any_stores = kml.placemarks.all().filter(name__icontains='store').exists()

Note

Important: Searching All vs. Root-Level Elements

The query methods like filter(), exclude(), get(), and exists() operate on the manager’s current elements:

  • kml.placemarks.children().filter() - Searches only root-level placemarks

  • kml.placemarks.all().filter() - Searches ALL placemarks including nested ones

Since most real-world KML files organize elements in folders, you’ll typically want to use all() before applying filters to search the entire document.

Working with Coordinates

Access coordinate data from placemarks:

for placemark in kml.placemarks.all():
    if placemark.coordinates:
        print(f"{placemark.name}: {placemark.longitude}, {placemark.latitude}")

Spatial Calculations

Calculate distances, bearings, and midpoints between geographic locations:

from kmlorm.spatial import DistanceUnit

# Get two placemarks
store1 = kml.placemarks.get(name='Store A')
store2 = kml.placemarks.get(name='Store B')

# Calculate distance (default: kilometers)
distance_km = store1.distance_to(store2)
print(f"Distance: {distance_km:.1f} km")

# Calculate in different units
distance_miles = store1.distance_to(store2, unit=DistanceUnit.MILES)
print(f"Distance: {distance_miles:.1f} miles")

# Calculate bearing (compass direction)
bearing = store1.bearing_to(store2)
print(f"Direction: {bearing:.1f}°")

# Find midpoint between locations
midpoint = store1.midpoint_to(store2)
print(f"Midpoint: {midpoint.longitude:.4f}, {midpoint.latitude:.4f}")

# Distance to specific coordinates (tuple or list)
baltimore = (-76.6, 39.3)
distance = store1.distance_to(baltimore)
print(f"Distance to Baltimore: {distance:.1f} km")

Geospatial Queries

Find elements based on location:

# Find placemarks near Baltimore (within 25 km)
nearby = kml.placemarks.near(-76.6, 39.3, radius_km=25)

# Find placemarks within a bounding box
in_area = kml.placemarks.within_bounds(
    north=39.5, south=39.0,
    east=-76.0, west=-77.0
)

# Only placemarks with coordinates
with_location = kml.placemarks.has_coordinates()

Chaining Queries

Combine multiple query methods for complex filtering:

# Complex query - searches ALL placemarks including nested
result = (kml.placemarks.all()  # Use .all() to search nested folders!
    .filter(name__icontains='electric')
    .near(-76.6, 39.3, radius_km=50)
    .has_coordinates()
    .order_by('name')
)

for placemark in result:
    print(f"- {placemark.name}")

Important

Query methods like filter(), exclude(), get() operate on direct children only by default. Use .all() first to search nested elements. See Query Method Behavior Reference for details.

Complete Example

Here’s a complete example that demonstrates common usage patterns:

from kmlorm import KMLFile
from kmlorm.core.exceptions import KMLParseError, KMLElementNotFound

def analyze_kml_file(file_path):
    try:
        # Load the KML file
        kml = KMLFile.from_file(file_path)

        print(f"Document: {kml.document_name}")
        print(f"Description: {kml.document_description}")
        print()

        # Show element counts
        counts = kml.element_counts()
        for element_type, count in counts.items():
            print(f"{element_type.title()}: {count}")
        print()

        # Find stores near Baltimore
        nearby_stores = (kml.placemarks
            .filter(name__icontains='store')
            .near(-76.6, 39.3, radius_km=30)
            .order_by('name')
        )

        print(f"Stores near Baltimore ({nearby_stores.count()}):")
        for store in nearby_stores:
            distance = calculate_distance_to_baltimore(store)
            print(f"- {store.name} ({distance:.1f} km away)")

        # Get a specific store
        try:
            rosedale_store = kml.placemarks.get(name__contains='Rosedale')
            print(f"\nRosedale store: {rosedale_store.address}")
        except KMLElementNotFound:
            print("\nNo Rosedale store found")

    except KMLParseError as e:
        print(f"Error parsing KML file: {e}")
    except Exception as e:
        print(f"Unexpected error: {e}")

def calculate_distance_to_baltimore(placemark):
    # Using built-in spatial calculations
    if placemark.coordinates:
        # Baltimore coordinates: -76.6, 39.3
        baltimore_coord = (-76.6, 39.3)
        return placemark.distance_to(baltimore_coord)
    return 0

# Run the analysis
if __name__ == "__main__":
    analyze_kml_file('example.kml')

Error Handling

Always handle potential errors when working with KML data:

from kmlorm.core.exceptions import (
    KMLParseError,
    KMLElementNotFound,
    KMLMultipleElementsReturned
)

try:
    kml = KMLFile.from_file('data.kml')
    store = kml.placemarks.get(name='My Store')
except KMLParseError:
    print("Invalid KML file")
except KMLElementNotFound:
    print("Store not found")
except KMLMultipleElementsReturned:
    print("Multiple stores found, be more specific")

Next Steps

  • Read the Tutorial for more detailed examples

  • Explore the API Reference for complete API documentation

  • Check out Examples for real-world use cases