Skip to content

OSM Data Structures#

This module provides classes for working with OpenStreetMap (OSM) data elements.

Base Class#

osmdiff.osm.OSMObject(tags={}, attribs={}, bounds=None) #

Base class for all OpenStreetMap elements (nodes, ways, relations).

Parameters:

Name Type Description Default
tags Dict[str, str]

Key-value tag dictionary

{}
attribs Dict[str, str]

XML attributes dictionary

{}
bounds List[float]

Optional bounding box coordinates [minlon, minlat, maxlon, maxlat]

None
Note

This is an abstract base class - use Node, Way or Relation for concrete elements.

Initialize an empty OSM object.

Source code in src/osmdiff/osm/osm.py
def __init__(
    self,
    tags: Dict[str, str] = {},
    attribs: Dict[str, str] = {},
    bounds: List[float] = None,
) -> None:
    """Initialize an empty OSM object."""
    self.tags = tags or {}
    self.attribs = attribs or {}
    self.bounds = bounds or None

__repr__() #

String representation of the OSM object.

Returns:

Name Type Description
str str

Object type and ID, with additional info for ways/relations

Source code in src/osmdiff/osm/osm.py
def __repr__(self) -> str:
    """
    String representation of the OSM object.

    Returns:
        str: Object type and ID, with additional info for ways/relations
    """
    out = "{type} {id}".format(type=type(self).__name__, id=self.attribs.get("id"))
    if type(self) == Way:
        out += " ({ways} nodes)".format(ways=len(self.nodes))
    if type(self) == Relation:
        out += " ({mem} members)".format(mem=len(self.members))
    return out

from_file(filename) classmethod #

Create object from XML file.

Parameters:

Name Type Description Default
filename str

Path to XML file

required

Returns:

Name Type Description
OSMObject OSMObject

Parsed object

Source code in src/osmdiff/osm/osm.py
@classmethod
def from_file(cls, filename: str) -> "OSMObject":
    """
    Create object from XML file.

    Args:
        filename: Path to XML file

    Returns:
        OSMObject: Parsed object
    """
    with open(filename, "r") as f:
        tree = ElementTree.parse(f)
        return cls.from_xml(tree.getroot())

from_xml(elem) classmethod #

Create OSM object from XML element.

Parameters:

Name Type Description Default
elem Element

XML element representing an OSM object

required

Returns:

Name Type Description
OSMObject OSMObject

Appropriate subclass instance

Raises:

Type Description
ValueError

If XML element is invalid

TypeError

If element type is unknown

Source code in src/osmdiff/osm/osm.py
@classmethod
def from_xml(cls, elem: Element) -> "OSMObject":
    """
    Create OSM object from XML element.

    Args:
        elem: XML element representing an OSM object

    Returns:
        OSMObject: Appropriate subclass instance

    Raises:
        ValueError: If XML element is invalid
        TypeError: If element type is unknown
    """
    if elem is None:
        raise ValueError("XML element cannot be None")

    osmtype = ""
    if elem.tag == "member":
        osmtype = elem.attrib.get("type")
        if not osmtype:
            raise ValueError("Member element missing type attribute")
    else:
        osmtype = elem.tag

    if osmtype not in ("node", "nd", "way", "relation"):
        raise TypeError(f"Unknown OSM element type: {osmtype}")

    if osmtype in ("node", "nd"):
        o = Node()
    elif osmtype == "way":
        o = Way()
        o._parse_nodes(elem)
    elif osmtype == "relation":
        o = Relation()
        o._parse_members(elem)
    else:
        pass
    o.attribs = elem.attrib
    o.osmtype = str(o.__class__.__name__).lower()[0]
    o._parse_tags(elem)
    o._parse_bounds(elem)
    return o

to_dict() #

Convert object to dictionary.

Returns:

Type Description
Dict[str, Any]

Dict[str, Any]: Dictionary representation

Source code in src/osmdiff/osm/osm.py
def to_dict(self) -> Dict[str, Any]:
    """
    Convert object to dictionary.

    Returns:
        Dict[str, Any]: Dictionary representation
    """
    return {
        "type": self.__class__.__name__,
        "id": self.attribs.get("id"),
        "tags": self.tags,
        "bounds": self.bounds,
    }

to_json() #

Convert object to JSON string.

Returns:

Name Type Description
str str

JSON representation

Source code in src/osmdiff/osm/osm.py
def to_json(self) -> str:
    """
    Convert object to JSON string.

    Returns:
        str: JSON representation
    """
    return json.dumps(self.to_dict())

OSM Elements#

Node#

osmdiff.osm.Node(tags={}, attribs={}, bounds=None) #

Bases: OSMObject

OpenStreetMap node (geographic point feature).

Implements geo_interface for GeoJSON compatibility as a Point feature. Coordinates must be valid (-180<=lon<=180, -90<=lat<=90).

Source code in src/osmdiff/osm/osm.py
def __init__(
    self,
    tags: Dict[str, str] = {},
    attribs: Dict[str, str] = {},
    bounds: List[float] = None,
) -> None:
    super().__init__(tags, attribs, bounds)

lat property #

Get latitude value.

lon property #

Get longitude value.

__eq__(other) #

Check if two nodes are equal.

Parameters:

Name Type Description Default
other OSMObject

Another OSMObject object

required

Returns:

Name Type Description
bool bool

True if nodes have same coordinates

Source code in src/osmdiff/osm/osm.py
def __eq__(self, other) -> bool:
    """
    Check if two nodes are equal.

    Args:
        other (OSMObject): Another OSMObject object

    Returns:
        bool: True if nodes have same coordinates
    """
    if not isinstance(other, Node):
        return False
    return self.lon == other.lon and self.lat == other.lat

Way#

osmdiff.osm.Way(tags=None, attribs=None, bounds=None, nodes=None) #

Bases: OSMObject

Represents an OSM way (linear feature).

Implements geo_interface for GeoJSON compatibility as either: - LineString for open ways - Polygon for closed ways

Initialize a Way object.

Source code in src/osmdiff/osm/osm.py
def __init__(
    self,
    tags: Dict[str, str] = None,
    attribs: Dict[str, str] = None,
    bounds: List[float] = None,
    nodes: List[Node] = None,
) -> None:
    """Initialize a Way object."""
    self.tags = tags or {}
    self.attribs = attribs or {}
    self.nodes = nodes or []
    super().__init__(tags, attribs, bounds)

is_closed() #

Check if the way forms a closed loop.

Returns:

Name Type Description
bool bool

True if first and last nodes are identical

Source code in src/osmdiff/osm/osm.py
def is_closed(self) -> bool:
    """
    Check if the way forms a closed loop.

    Returns:
        bool: True if first and last nodes are identical
    """
    return bool(self.nodes and self.nodes[0] == self.nodes[-1])

length() #

Calculate approximate length in meters.

Returns:

Name Type Description
float float

Length of way in meters (not implemented)

Source code in src/osmdiff/osm/osm.py
def length(self) -> float:
    """
    Calculate approximate length in meters.

    Returns:
        float: Length of way in meters (not implemented)
    """
    # Implementation using haversine formula
    pass

Relation#

osmdiff.osm.Relation(tags=None, attribs=None, bounds=None) #

Bases: OSMObject

Represents an OSM relation (collection of features).

Attributes#

members (list): List of member objects
__geo_interface__ (dict): GeoJSON-compatible interface, see https://gist.github.com/sgillies/2217756 for more details.

Example#

relation = Relation()
relation.members = [Way(), Node()]  # Add members
print(relation.__geo_interface__["type"])  # "FeatureCollection"

Initialize a Relation object.

Source code in src/osmdiff/osm/osm.py
def __init__(
    self,
    tags: Dict[str, str] = None,
    attribs: Dict[str, str] = None,
    bounds: List[float] = None,
) -> None:
    """Initialize a Relation object."""
    tags = tags or {}
    attribs = attribs or {}
    super().__init__(tags, attribs, bounds)
    self.members = []