from __future__ import annotations
"""Profile Data Processing Service.
This module handles the extraction and processing of topographic profile data
from DEM layers along section lines.
"""
# /***************************************************************************
# SecInterp - ProfileService
# A QGIS plugin
# Service for generating topographic profiles.
# -------------------
# begin : 2025-12-07
# copyright : (C) 2025 by Juan M Bernales
# email : juanbernales@gmail.com
# ***************************************************************************/
#
# /***************************************************************************
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU General Public License as published by *
# * the Free Software Foundation; either version 2 of the License, or *
# * (at your option) any later version. *
# * *
# ***************************************************************************/
from qgis.core import QgsRasterLayer, QgsVectorLayer
from sec_interp.core import utils as scu
from sec_interp.core.exceptions import DataMissingError, GeometryError
from sec_interp.core.interfaces.profile_interface import IProfileService
from sec_interp.core.types import ProfileData
from sec_interp.logger_config import get_logger
logger = get_logger(__name__)
[docs]
class ProfileService(IProfileService):
"""Service for generating topographic profiles.
This service handles the extraction of elevation data along a cross-section
line by sampling a raster DEM.
"""
[docs]
def generate_topographic_profile(
self,
line_lyr: QgsVectorLayer,
raster_lyr: QgsRasterLayer,
band_number: int = 1,
interval: float | None = None,
) -> ProfileData:
"""Generate topographic profile data by sampling elevation along the section line.
Args:
line_lyr: The cross-section line layer.
raster_lyr: The DEM/raster layer for elevation.
band_number: Raster band to sample (default: 1).
interval: Optional sampling interval. If None, uses raster resolution.
Returns:
A list of (distance, elevation) tuples representing the profile.
Raises:
DataMissingError: If line layer has no features.
GeometryError: If line geometry is invalid.
"""
line_feat = next(line_lyr.getFeatures(), None)
if not line_feat:
raise DataMissingError("Line layer has no features", {"layer": line_lyr.name()})
geom = line_feat.geometry()
if not geom or geom.isNull():
raise GeometryError("Line geometry is not valid", {"layer": line_lyr.name()})
da = scu.create_distance_area(line_lyr.crs())
# Sample points using helper
# For topographic profile, we measure from the start of the line
points = scu.sample_elevation_along_line(
geom, raster_lyr, band_number, da, interval=interval
)
# Convert QgsPointXY to tuples
values = [(round(p.x(), 1), round(p.y(), 1)) for p in points]
return values