Source code for sec_interp.exporters.shp_exporter

from __future__ import annotations

"""Shapefile exporter module for vector data."""

from pathlib import Path
from typing import Any

from qgis.core import (
    QgsCoordinateReferenceSystem,
    QgsFeature,
    QgsField,
    QgsFields,
    QgsProject,
    QgsVectorFileWriter,
    QgsWkbTypes,
)
from qgis.PyQt.QtCore import QMetaType

from sec_interp.logger_config import get_logger

from .base_exporter import BaseExporter

logger = get_logger(__name__)


[docs] class ShapefileExporter(BaseExporter): """Exporter for Shapefile and GeoPackage formats."""
[docs] def get_supported_extensions(self) -> list[str]: """Get supported vector format extensions.""" return [".shp", ".gpkg"]
[docs] def export(self, output_path: Path, features_data: list[dict[str, Any]]) -> bool: """Export features to shapefile or geopackage. Args: output_path: Output file path features_data: List of dicts with 'geometry' and 'attributes' keys Returns: True if export successful, False otherwise """ if not features_data: return False try: geometry_type = self.get_setting("geometry_type", QgsWkbTypes.LineString) crs = self.get_setting("crs", QgsCoordinateReferenceSystem("EPSG:4326")) fields = self._prepare_fields(features_data) writer = self._create_writer(output_path, fields, geometry_type, crs) if writer.hasError() != QgsVectorFileWriter.NoError: logger.error(f"Failed to create writer: {writer.errorMessage()}") return False self._write_features(writer, features_data, fields) # Note: writer is closed when object is deleted or goes out of scope del writer except Exception: logger.exception(f"Shapefile export failed for {output_path}") return False else: return True
def _write_features( self, writer: QgsVectorFileWriter, features_data: list[dict[str, Any]], fields: QgsFields, ) -> None: """Process and write features to the vector writer. Args: writer: The QGIS vector file writer instance. features_data: List of feature data dictionaries. fields: The QGIS field collection for the layer. """ for data in features_data: feature = QgsFeature(fields) if "geometry" in data: feature.setGeometry(data["geometry"]) if "attributes" in data: attrs = data["attributes"] feature.setAttributes([attrs.get(field.name()) for field in fields]) writer.addFeature(feature) def _prepare_fields(self, features_data: list[dict[str, Any]]) -> QgsFields: """Create fields based on first feature's attributes.""" fields = QgsFields() if features_data and "attributes" in features_data[0]: first_attrs = features_data[0]["attributes"] for key, value in first_attrs.items(): if isinstance(value, int): fields.append(QgsField(key, QMetaType.Type.Int)) elif isinstance(value, float): fields.append(QgsField(key, QMetaType.Type.Double)) else: fields.append(QgsField(key, QMetaType.Type.QString)) return fields def _create_writer( self, output_path: Path, fields: QgsFields, geometry_type: QgsWkbTypes, crs: QgsCoordinateReferenceSystem, ) -> QgsVectorFileWriter: """Create a QgsVectorFileWriter for the given path.""" ext = output_path.suffix.lower() driver = "GPKG" if ext == ".gpkg" else "ESRI Shapefile" options = QgsVectorFileWriter.SaveVectorOptions() options.driverName = driver options.fileEncoding = "UTF-8" return QgsVectorFileWriter.create( str(output_path), fields, geometry_type, crs, QgsProject.instance().transformContext(), options, )