Tutorial
This tutorial covers advanced features and patterns for working with KML ORM.
Advanced Querying
Note
Folder Hierarchy Behavior
KML ORM provides two methods for accessing elements:
Direct children only:
kml.placemarks.children()All elements (including nested):
kml.placemarks.all()
Field Lookups
KML ORM supports various Django-style field lookups:
from kmlorm import KMLFile
kml = KMLFile.from_file('stores.kml')
# Exact match (default) - includes all elements including nested
exact = kml.placemarks.all().filter(name='Capital Electric Supply')
# Case-insensitive contains
contains = kml.placemarks.all().filter(name__icontains='electric')
# Starts with / ends with
starts = kml.placemarks.all().filter(name__startswith='Capital')
ends = kml.placemarks.all().filter(name__endswith='Store')
# In a list of values
multiple = kml.placemarks.all().filter(name__in=['Hardware Store', 'Electric Depot'])
# Null checks
with_description = kml.placemarks.all().filter(description__isnull=False)
without_description = kml.placemarks.all().filter(description__isnull=True)
# Regular expressions
regex_match = kml.placemarks.all().filter(name__regex=r'^Capital.*Electric.*$')
Complex Queries
Combine multiple filters and use Q objects for complex logic:
# Multiple filters (AND logic)
result = (kml.placemarks
.filter(name__icontains='electric')
.filter(visibility=True)
.exclude(description__isnull=True)
)
# Geospatial + attribute filtering
baltimore_electric_stores = (kml.placemarks
.filter(name__icontains='electric')
.near(-76.6, 39.3, radius_km=25)
.has_coordinates()
)
Working with Hierarchies
Cross-Folder Queries
Query across all folders simultaneously:
# All placemarks regardless of folder (includes nested)
all_stores = kml.placemarks.all().filter(name__icontains='store')
# Get placemarks from specific folder (direct children only)
supply_folder = kml.folders.children().get(name='Supply Locations')
supply_stores = supply_folder.placemarks.children()
Important: kml.placemarks.all() includes all placemarks including
those in nested folders. Use kml.placemarks.children() for direct
children only.
Note
As of version 1.0.1, folder.placemarks.all() correctly returns ALL
placemarks including those in nested subfolders, while folder.placemarks.children()
returns only direct children.
# Get all placemarks in Supply Locations folder and its subfolders
supply_folder = kml.folders.children().get(name='Supply Locations')
# Get only direct children (2 placemarks)
direct_only = supply_folder.placemarks.children()
print(len(direct_only)) # 2 (Capital Electric Supply, Electric Depot)
# Get ALL placemarks including nested ones (3 placemarks)
all_nested = supply_folder.placemarks.all()
print(len(all_nested)) # 3 (includes Main Warehouse from nested folder)
# The same pattern works for folders
direct_folders = supply_folder.folders.children() # Only direct subfolders
all_folders = supply_folder.folders.all() # All nested folders at any depth
Geometry Collection Behavior
Added in version 1.1.0: Geometry managers (points, paths, polygons) now collect from all sources including Placemarks and MultiGeometry containers.
When using .all() on geometry managers, the collection includes:
# Example KML structure with various geometry locations
kml_content = '''
<kml>
<Document>
<Point><coordinates>0,0,0</coordinates></Point> <!-- Standalone Point -->
<Placemark>
<Point><coordinates>1,1,0</coordinates></Point> <!-- Point in Placemark -->
</Placemark>
<Placemark>
<MultiGeometry>
<Point><coordinates>2,2,0</coordinates></Point> <!-- Point in MultiGeometry -->
<LineString><coordinates>3,3,0 4,4,0</coordinates></LineString>
</MultiGeometry>
</Placemark>
</Document>
</kml>
'''
kml = KMLFile.from_string(kml_content)
# kml.points.children() returns 1 (only the standalone Point)
# kml.points.all() returns 3 (standalone + from Placemark + from MultiGeometry)
# Similarly for paths and polygons
# kml.paths.children() returns 0 (no standalone LineStrings)
# kml.paths.all() returns 1 (from MultiGeometry)
Spatial Operations
Distance Calculations
Note
About Distance Calculations
KML ORM provides comprehensive spatial calculations with multiple strategies:
Haversine (default): Great circle distances with 0.5% accuracy
Vincenty: High-precision geodesic calculations (±0.5mm accuracy)
Euclidean: Fast approximation for small distances (<100km)
All calculations use the WGS84 ellipsoid model and handle edge cases like date line crossing and polar regions correctly.
For detailed technical information, see Spatial Calculations.
Calculate distances between placemarks with different units:
from kmlorm.spatial import DistanceUnit
# Get two placemarks (includes nested)
store1 = kml.placemarks.all().get(name__contains='Rosedale')
store2 = kml.placemarks.all().get(name__contains='Timonium')
# Calculate distance in various units
if store1.coordinates and store2.coordinates:
km = store1.distance_to(store2)
miles = store1.distance_to(store2, unit=DistanceUnit.MILES)
meters = store1.distance_to(store2, unit=DistanceUnit.METERS)
print(f"Distance: {km:.2f} km")
print(f"Distance: {miles:.2f} miles")
print(f"Distance: {meters:.0f} meters")
# 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")
Bulk Distance Operations
Efficiently calculate distances to many locations:
from kmlorm.spatial import SpatialCalculations
# Center location (Baltimore)
center = (-76.6, 39.3)
# Get all stores with coordinates
stores = kml.placemarks.all().has_coordinates()
# Calculate distances from center to all stores
distances = SpatialCalculations.distances_to_many(center, stores)
# Combine with store names for display
for store, distance in zip(stores, distances):
if distance is not None:
print(f"{store.name}: {distance:.1f} km")
# Sort by distance
store_distances = [(s, d) for s, d in zip(stores, distances) if d is not None]
store_distances.sort(key=lambda x: x[1])
print("\nClosest stores:")
for store, distance in store_distances[:5]:
print(f" {store.name}: {distance:.1f} km")
Working with Different Geometry Types
Paths (LineStrings)
Work with path/route data:
# Get all paths (direct children only)
paths = kml.paths.children()
# Get ALL paths including those in Placemarks and MultiGeometry
all_paths = kml.paths.all()
for path in all_paths:
print(f"Path: {path.name if path.name else 'Unnamed'}")
if path.coordinates:
print(f" Points: {len(path.coordinates)}")
print(f" Length: {path.calculate_length():.2f} km")
Polygons
Work with polygon areas:
# Get all polygons (direct children only)
polygons = kml.polygons.children()
# Get ALL polygons including those in Placemarks and MultiGeometry
all_polygons = kml.polygons.all()
for polygon in all_polygons:
print(f"Polygon: {polygon.name if polygon.name else 'Unnamed'}")
if polygon.outer_boundary:
print(f" Boundary points: {len(polygon.outer_boundary)}")
print(f" Has holes: {len(polygon.inner_boundaries) > 0}")
Data Validation
Validate Elements
Ensure data integrity with validation:
from kmlorm.core.exceptions import KMLValidationError
for placemark in kml.placemarks.all():
try:
if placemark.validate():
print(f"✓ {placemark.name} is valid")
except KMLValidationError as e:
print(f"✗ {placemark.name} validation failed: {e}")
Coordinate Validation
Validate coordinate ranges:
from kmlorm.models.point import Coordinate
try:
# Valid coordinate
coord = Coordinate(longitude=-76.5, latitude=39.3)
coord.validate()
# Invalid coordinate (will raise exception)
invalid = Coordinate(longitude=200, latitude=100)
invalid.validate()
except KMLValidationError as e:
print(f"Invalid coordinate: {e}")
Performance Optimization
Efficient Querying
Use efficient query patterns for better performance:
# Good: Use specific filters early
nearby_electric = (kml.placemarks
.filter(name__icontains='electric') # Filter first
.near(-76.6, 39.3, radius_km=10) # Then apply geospatial
)
# Less efficient: Geospatial first on large dataset
all_nearby = kml.placemarks.near(-76.6, 39.3, radius_km=50)
electric_nearby = all_nearby.filter(name__icontains='electric')
Batch Operations
Process large datasets efficiently:
# Process in batches
all_placemarks = kml.placemarks.all()
batch_size = 100
for i in range(0, len(all_placemarks), batch_size):
batch = all_placemarks[i:i + batch_size]
process_batch(batch)
def process_batch(placemarks):
for placemark in placemarks:
# Process individual placemark
if placemark.coordinates:
validate_location(placemark)
Error Handling Patterns
Graceful Error Handling
Handle errors gracefully in production code:
import logging
from kmlorm.core.exceptions import (
KMLParseError,
KMLElementNotFound,
KMLValidationError
)
logger = logging.getLogger(__name__)
def safe_kml_processing(file_path):
try:
kml = KMLFile.from_file(file_path)
# Process with error handling
for placemark in kml.placemarks.all():
try:
if placemark.validate():
process_placemark(placemark)
except KMLValidationError as e:
logger.warning(f"Skipping invalid placemark {placemark.name}: {e}")
continue
except KMLParseError as e:
logger.error(f"Failed to parse KML file {file_path}: {e}")
return None
except Exception as e:
logger.error(f"Unexpected error processing {file_path}: {e}")
raise
def process_placemark(placemark):
# Your processing logic here
pass
Integration Patterns
With Pandas
Convert KML data to pandas DataFrames:
import pandas as pd
def kml_to_dataframe(kml_file):
data = []
for placemark in kml_file.placemarks.all():
row = {
'name': placemark.name,
'description': placemark.description,
'longitude': placemark.longitude,
'latitude': placemark.latitude,
'altitude': placemark.altitude,
'address': placemark.address,
'phone': placemark.phone_number,
}
data.append(row)
return pd.DataFrame(data)
# Usage
kml = KMLFile.from_file('stores.kml')
df = kml_to_dataframe(kml)
print(df.head())
With GeoPandas
Convert to GeoPandas for advanced geospatial analysis:
import geopandas as gpd
from shapely.geometry import Point
def kml_to_geodataframe(kml_file):
data = []
geometries = []
for placemark in kml_file.placemarks.all():
if placemark.coordinates:
# Create Shapely Point
point = Point(placemark.longitude, placemark.latitude)
geometries.append(point)
# Create data row
data.append({
'name': placemark.name,
'description': placemark.description,
'address': placemark.address,
})
# Create GeoDataFrame
gdf = gpd.GeoDataFrame(data, geometry=geometries, crs='EPSG:4326')
return gdf
# Usage
kml = KMLFile.from_file('stores.kml')
gdf = kml_to_geodataframe(kml)
# Now you can use GeoPandas operations
# Buffer points by 1km
buffered = gdf.buffer(0.01) # roughly 1km at this latitude
Custom Extensions
Extending Models
Create custom model extensions:
from kmlorm import Placemark
class Store(Placemark):
@property
def is_open(self):
# Custom business logic
return getattr(self, 'hours', None) is not None
def distance_to_customer(self, customer_location):
if self.coordinates and customer_location:
return self.distance_to(customer_location)
return float('inf')
# Usage
store = Store(name="My Store", coordinates=(-76.5, 39.3))
distance = store.distance_to_customer((-76.6, 39.4))
Next Steps
Explore the complete API Reference documentation
Check out real-world Examples
Learn about Contributing to the project