KML Document Model¶
The first concept to understand in fastkml is the document tree. A KML object owns
top-level features, Document and Folder group other features, and concrete feature
types like Placemark, GroundOverlay, ScreenOverlay, and NetworkLink sit at
the leaves. This matters because almost every operation in the library, from
find_all() traversal to style resolution, assumes your data is organized as this nested
feature graph:
KML
└── Document
├── Folder
│ ├── Folder
│ ├── GroundOverlay
│ └── NetworkLink
└── Placemark
KML is defined in fastkml/kml.py, _Container plus Document and Folder
live in fastkml/containers.py, and the shared feature fields live in
fastkml/features.py. Internally, KML.features and _Container.features are just
Python lists, but they are also registry-backed XML collections. KML.append() and
_Container.append() are deliberately small: they mutate the list, and the registry later
decides how that list maps back to Document, Folder, Placemark, overlay, or
network-link nodes.
How It Relates To Other Concepts¶
The document model is the glue for the rest of the library:
Geometry objects attach to
Placemark.kml_geometry— see Geometry Bridge.Shared styles usually live on
Document.stylesand are resolved withDocument.get_style_by_url().Time primitives, views, regions, and extended data are all feature attributes managed by
_Feature— see Time And Views and Extended Data.Recursive searching with
find()andfind_all()works because containers expose nestedfeatures(see Working with KML Files).
How It Works Internally¶
_Feature.__init__() in fastkml/features.py normalizes shared fields like name,
description, style_url, view, and extended_data. Placemark extends that
by accepting either kml_geometry or geometry, converting generic geometry input
through create_kml_geometry() when needed. Document extends _Container again by
storing schemata, and get_style_by_url() uses find_all() to resolve local
#styleId references.
The append methods are intentionally minimal because structure validation is mostly left to
you. _Container.append() only blocks appending a container to itself. That design keeps
tree construction cheap and predictable, but it also means logical mistakes like attaching a
shared Style to the wrong document will only show up in the generated KML or in the
consuming viewer.
Basic Usage¶
>>> from fastkml import Document, Folder, KML, Placemark
>>> from pygeoif import Point
>>> k = KML()
>>> doc = Document(id="ops", name="Operations")
>>> folder = Folder(name="Depots")
>>> folder.append(Placemark(name="North depot", geometry=Point(-0.12, 51.5, 0)))
>>> doc.append(folder)
>>> k.append(doc)
>>> print(len(k.features))
1
>>> print(doc.features[0].name)
Depots
Advanced Usage¶
>>> from fastkml import Document, KML, Placemark, Style, StyleMap, StyleUrl
>>> from fastkml.styles import IconStyle, Pair
>>> from fastkml.enums import PairKey
>>> from pygeoif import Point
>>> normal = Style(
... id="normal-style", styles=[IconStyle(icon_href="https://example.com/a.png")]
... )
>>> highlight = Style(
... id="highlight-style", styles=[IconStyle(icon_href="https://example.com/b.png")]
... )
>>> style_map = StyleMap(
... id="depot-style",
... pairs=[
... Pair(key=PairKey.normal, style=StyleUrl(url="#normal-style")),
... Pair(key=PairKey.highlight, style=StyleUrl(url="#highlight-style")),
... ],
... )
>>> doc = Document(id="styled-doc", styles=[normal, highlight, style_map])
>>> doc.append(
... Placemark(
... name="Styled depot",
... geometry=Point(-3.19, 55.95, 0),
... style_url=StyleUrl(url="#depot-style"),
... )
... )
>>> resolved = doc.get_style_by_url("#depot-style")
>>> print(type(resolved).__name__)
StyleMap
Warning
A StyleUrl only resolves if the target Style or StyleMap is reachable from
the same Document. If you attach shared styles to the wrong container,
Document.get_style_by_url() will quietly return None and your viewer will fall
back to defaults.
Why fastkml uses mutable feature lists¶
The features collections on KML, Document, and Folder are plain Python lists
because the library optimizes for predictable object editing rather than for immutable tree
transforms. That keeps append() cheap and makes examples like building a document
incrementally easy to read. The trade-off is that there is no structural transaction layer,
so you can produce invalid or surprising trees if you mutate lists carelessly. In practice,
that is acceptable for KML authoring because validation and viewer testing usually happen
after the tree is assembled, not during each append call.
Why style lookup lives on Document¶
Document.get_style_by_url() exists because shared KML styles are document-scoped by
convention and by the KML spec. Resolving a style from the document level lets the method
search both Style and StyleMap instances recursively with find_all() without
forcing every feature to know about the whole tree. The limitation is that a Placemark
cannot resolve its own style in isolation; you need the owning document context. That
separation keeps feature objects simple, but it pushes a bit more responsibility to
application code when you work with detached subtrees.