Skip to content

Metadata Readers

geoembed.metadata.readers

Metadata readers — extract spatial and temporal information from imagery files.

Follows Single Responsibility Principle (SRP): each reader handles one data source.

MetadataReader

Bases: ABC

Abstract interface for file metadata readers.

read(file_path) abstractmethod

Extract metadata from a file.

Parameters:

Name Type Description Default
file_path Path

Path to the file to read.

required

Returns:

Type Description
dict[str, Any]

Dictionary of extracted metadata fields.

Source code in src/geoembed/metadata/readers.py
@abstractmethod
def read(self, file_path: Path) -> dict[str, Any]:
    """
    Extract metadata from a file.

    Args:
        file_path: Path to the file to read.

    Returns:
        Dictionary of extracted metadata fields.
    """
    ...

XmlSidecarReader()

Bases: MetadataReader

Extracts temporal and provider metadata from ISO 19115 XML sidecars.

Parses XML metadata files (.xml) that accompany GeoTIFFs to extract: - Capture datetime (from EX_TemporalExtent) - EPSG code (from referenceSystemIdentifier) - Provider/organisation name (from CI_ResponsibleParty)

Source code in src/geoembed/metadata/readers.py
def __init__(self) -> None:
    self.ns = {
        "gmd": "http://www.isotc211.org/2005/gmd",
        "gco": "http://www.isotc211.org/2005/gco",
        "gml": "http://www.opengis.net/gml/3.2",
        "gmx": "http://www.isotc211.org/2005/gmx",
        "xlink": "http://www.w3.org/1999/xlink",
    }

read(file_path)

Read metadata from the XML sidecar accompanying a GeoTIFF.

Parameters:

Name Type Description Default
file_path Path

Path to the GeoTIFF (XML is assumed to be at same path with .xml extension).

required

Returns:

Type Description
dict[str, Any]

Dict with keys: xml_path, datetime, epsg, provider. Empty dict if no XML found.

Source code in src/geoembed/metadata/readers.py
def read(self, file_path: Path) -> dict[str, Any]:
    """
    Read metadata from the XML sidecar accompanying a GeoTIFF.

    Args:
        file_path: Path to the GeoTIFF (XML is assumed to be at same path with .xml extension).

    Returns:
        Dict with keys: xml_path, datetime, epsg, provider. Empty dict if no XML found.
    """
    xml_path = file_path.with_suffix(".xml")
    if not xml_path.exists():
        return {}

    try:
        tree = ET.parse(xml_path)
        root = tree.getroot()
        if root is None:
            return {}
        return {
            "xml_path": xml_path,
            "datetime": self._extract_datetime(root),
            "epsg": self._extract_epsg(root),
            "provider": self._find_text(
                root, ".//gmd:CI_ResponsibleParty/gmd:organisationName/gco:CharacterString"
            ),
        }
    except Exception:
        return {}

GeotiffSpatialReader

Bases: MetadataReader

Extracts spatial bounds and geometry from GeoTIFF headers.

Uses rasterio to read CRS and bounds without loading pixel data. Returns native-CRS bounds (not reprojected).

read(file_path)

Read spatial metadata from a GeoTIFF.

Parameters:

Name Type Description Default
file_path Path

Path to the GeoTIFF.

required

Returns:

Type Description
dict[str, Any]

Dict with keys: bbox (list[float]), footprint (GeoJSON), epsg (int|None).

Source code in src/geoembed/metadata/readers.py
def read(self, file_path: Path) -> dict[str, Any]:
    """
    Read spatial metadata from a GeoTIFF.

    Args:
        file_path: Path to the GeoTIFF.

    Returns:
        Dict with keys: bbox (list[float]), footprint (GeoJSON), epsg (int|None).
    """
    with rasterio.open(file_path) as src:
        bounds = src.bounds
        crs = src.crs
        native_poly = Polygon.from_bounds(*bounds)
        epsg = crs.to_epsg() if crs else None
        return {"bbox": list(bounds), "footprint": mapping(native_poly), "epsg": epsg}