Repository: gboeing/osmnx

Issue:
features_from_xml fails on export containing incomplete multipolygon
### Contributing guidelines

- [x] I understand the contributing guidelines

### Documentation

- [x] My problem is not addressed by the documentation or examples

### Existing issues

- [x] My problem does not appear in an existing issue

### What operating system and Python version are you using?

Python 3.14

### What OSMnx version are you using?

2.0.5

### Environment packages and versions

```shell
Package            Version
------------------ -----------
certifi            2025.7.14
charset-normalizer 3.4.2
contourpy          1.3.3
cycler             0.12.1
fonttools          4.59.0
geopandas          1.1.1
idna               3.10
iniconfig          2.1.0
kiwisolver         1.4.8
matplotlib         3.10.5
mock               5.2.0
networkx           3.5
numpy              2.3.2
osmium             4.0.2
osmnx              2.0.5
packaging          25.0
pandas             2.2.3
pillow             11.3.0
pip                25.1.1
pluggy             1.6.0
Pygments           2.19.2
pyogrio            0.11.1
pyparsing          3.2.3
pyproj             3.7.1
pytest             8.4.1
python-dateutil    2.9.0.post0
pytz               2025.2
requests           2.32.4
shapely            2.1.1
six                1.17.0
tzdata             2025.2
urllib3            2.5.0
```

### How did you install OSMnx?

pip or uv (via PyPI)

### Problem description

`featuress_from_xml` will fail with the following stacktrace if run on an export that contains an incomplete multipolygon.
```
Traceback (most recent call last):
  File "<python-input-5>", line 2, in <module>
    osmnx.features_from_xml("./osm_export_xml.osm")
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/osmnx/features.py", line 374, in features_from_xml
    gdf = _create_gdf(response_jsons, polygon, tags)
  File "/usr/local/lib/python3.13/site-packages/osmnx/features.py", line 420, in _create_gdf
    data=_process_features(elements, set(tags.keys())),
         ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/osmnx/features.py", line 503, in _process_features
    relation["geometry"] = _build_relation_geometry(relation.pop("members"), way_geoms)
                           ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.13/site-packages/osmnx/features.py", line 601, in _build_relation_geometry
    geom = way_geoms[member["ref"]]
           ~~~~~~~~~^^^^^^^^^^^^^^^
KeyError: 44098878
```
This came up while testing a regression in a test on https://github.com/chrieke/prettymapp when updating from osmnx v1 to v2. In v1 (tested on 1.9.3) it succeeds with no error, seemingly having ignored the relation. In v2 (tested on 2.0.0 and 2.0.5) it errors.

If you move/zoom the export so that the entire multipolygon is exported then osmnx ingests it correctly. For example around https://www.openstreetmap.org/export#map=19/52.508763/13.387299 it's rather easy to trigger due to the large number of multipolygons.

### Minimal standalone reproducible example

```python
Export an .osm file that only partially contains a relation. For example https://github.com/chrieke/prettymapp/blob/3a6ef573f343718f001be8ee8038c15b687876f9/prettymapp/tests/mock_data/osm_export_xml.osm

Run

import osmnx
osmnx.features_from_xml("./osm_export_xml.osm")
```


Discussion:
@C4K3 thanks for flagging this. Proposed fix in #1334.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a88e383a..7936e3596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ## TBD
 
+- handle relations with missing member geometries when loading features from XML (#1334)
 - exclude ways tagged "rest_area" or "services" when downloading network data (#1328)
 
 ## 2.0.5 (2025-07-05)
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 908b96f70..f079caf60 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -8,9 +8,9 @@ The foolproof way to install OSMnx is with `conda`_ or `mamba`_:
 
 .. code-block:: shell
 
-    conda create -n ox conda-forge::osmnx
+    conda create --strict-channel-priority -c conda-forge -n ox osmnx
 
-This creates a new conda environment and installs OSMnx into it, via the conda-forge channel. If you want other packages, such as :code:`jupyterlab`, installed in this environment as well, just add their names after :code:`osmnx` above. To upgrade OSMnx to a newer release, remove the conda environment you created and then create a new one again, as above. See the `conda`_ and `conda-forge`_ documentation for more details.
+This creates a new conda environment and installs OSMnx into it, via the conda-forge channel. If you want other packages, such as :code:`jupyterlab`, installed in this environment as well, just add their names after :code:`osmnx` above. To upgrade OSMnx to a newer release, remove the conda environment you created and then create a new one again, as above. See the `conda-forge`_ documentation for details.
 
 Docker
 ------
diff --git a/osmnx/features.py b/osmnx/features.py
index ca472816f..833cbc473 100644
--- a/osmnx/features.py
+++ b/osmnx/features.py
@@ -598,7 +598,14 @@ def _build_relation_geometry(
     # sort member geometries by member role and geometry type
     for member in members:
         if member["type"] == "way":
-            geom = way_geoms[member["ref"]]
+            geom = way_geoms.get(member["ref"])
+            if geom is None:
+                # a member's geometry may be missing when loaded from XML, if
+                # so, we cannot build this relation's complete geometry, so
+                # just return a null geometry to be removed at final filtering
+                msg = f"Cannot build relation geometry, missing member way {member['ref']}"
+                utils.log(msg, level=lg.WARNING)
+                return Polygon()
             role = member["role"]
             if role == "outer" and geom.geom_type == "LineString":
                 outer_linestrings.append(geom)


Test patch:
diff --git a/tests/input_data/osm_schema.xsd b/tests/input_data/osm_schema.xsd
index 167fefc5f..5afea91dc 100644
--- a/tests/input_data/osm_schema.xsd
+++ b/tests/input_data/osm_schema.xsd
@@ -87,6 +87,7 @@
       <xs:attribute name="copyright" type="xs:string" use="optional" />
       <xs:attribute name="generator" type="xs:string" use="required" />
       <xs:attribute name="license" type="xs:string" use="optional" />
+      <xs:attribute name="timestamp" type="xs:dateTime" use="optional" />
       <xs:attribute name="version" type="xs:float" use="required" fixed="0.6" />
     </xs:complexType>
   </xs:element>
diff --git a/tests/input_data/planet_10.068,48.135_10.071,48.137.osm b/tests/input_data/planet_10.068,48.135_10.071,48.137.osm
index da7c0ea7a..58a9755b7 100644
--- a/tests/input_data/planet_10.068,48.135_10.071,48.137.osm
+++ b/tests/input_data/planet_10.068,48.135_10.071,48.137.osm
@@ -967,4 +967,26 @@
 		<tag k="type" v="multipolygon"/>
 		<tag k="landuse" v="residential"/>
 	</relation>
+	<relation id="318560" visible="true" version="8" changeset="136724339" timestamp="2023-05-30T04:42:59Z" user="Aleks-Berlin" uid="85218">
+		<member type="way" ref="43326015" role="outer"/>
+		<member type="way" ref="44098878" role="inner"/>
+		<member type="way" ref="44098877" role="inner"/>
+		<member type="way" ref="44098876" role="inner"/>
+		<tag k="addr:housenumber" v="75"/>
+		<tag k="addr:postcode" v="10117"/>
+		<tag k="addr:street" v="Mauerstraße"/>
+		<tag k="architect" v="Ernst Hake;Heinrich Techo;Franz Ahrens"/>
+		<tag k="building" v="government"/>
+		<tag k="building:architecture" v="neo-baroque"/>
+		<tag k="building:levels" v="4"/>
+		<tag k="fixme" v="Gebäude baulich richtig aufteilen und dann building=office für das Ministerium und building=civic für das Museum verwendet werden."/>
+		<tag k="heritage" v="4"/>
+		<tag k="loc_name" v="Post-Kolosseum"/>
+		<tag k="museum" v="technology"/>
+		<tag k="old_name" v="Reichspostministerium"/>
+		<tag k="start_date" v="1872"/>
+		<tag k="tourism" v="museum"/>
+		<tag k="type" v="multipolygon"/>
+		<tag k="wikipedia" v="de:Museum für Kommunikation Berlin"/>
+	</relation>
 </osm>