Source code for shapelib.core.handlers.geo_file_handler
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any
import geopandas as gpd
from shapelib.core.geometries.base import BaseShapes
from shapelib.core.geometries.empty import Empty
from shapelib.core.geometries.lines import Lines
from shapelib.core.geometries.points import Points
from shapelib.core.geometries.polygons import Polygons
from shapelib.utils.utils import assume_wgs84_if_lonlat, get_most_common_geometry_type
[docs]
class GeoFileHandler(ABC):
"""
Abstract base class for handling geospatial files.
This class provides a common interface for geospatial file handlers, including
methods for loading data, validating files, and creating objects. Specific file
handlers (e.g., for Shapefiles, GeoJSON, etc.) should inherit from this class
and implement the required abstract methods.
Attributes
----------
driver : str
The driver used for handling the file, which must be defined in subclasses.
extension : str
The file extension associated with the handler, which must be defined in
subclasses.
keep_z : bool
Whether the object created from the file keeps the Z coordinate of 3-D
geometries. False by default; set by the factory from ``read_file``.
Methods
-------
check_path()
Checks if the file path exists and is a file.
load_data()
Abstract method to load data from the geospatial file.
validate_file()
Abstract method to validate the file according to specific handler requirements.
create_obj()
Abstract method to create and return an object representing the file's data.
"""
driver: str = "" # To be implemented by each subclass
extension: str = "" # To be implemented by each subclass
keep_z: bool = False # Set by GeoFileFactory before create_obj
def __init__(self, path: Path) -> None:
"""
Initialize the GeoFileHandler with a file path.
Parameters
----------
path : Path
The path to the geospatial file.
Raises
------
FileNotFoundError
If the file does not exist or is not a file.
"""
self.path = path
self.check_path()
[docs]
def check_path(self) -> None:
"""
Check if the file path exists and is a file.
This method verifies whether the provided path exists and points to a valid file.
If the file does not exist, it raises a `FileNotFoundError`.
Raises
------
FileNotFoundError
If the file does not exist or is not a file.
Examples
--------
>>> handler = GeoFileHandler(path=Path("/path/to/file"))
>>> handler.check_path() # Raises an error if the file is invalid
"""
if not self.path.exists() or not self.path.is_file():
raise FileNotFoundError(f"The file does not exist: {self.path.as_posix()}")
[docs]
def create_geometry_instance(self, data: gpd.GeoDataFrame, driver: str) -> Polygons | Lines | Points | Empty:
"""
Create a geometry instance (Polygons, Lines, Points, or Empty) based on
the most common geometry type in the GeoDataFrame.
Data read without a CRS is assumed to be WGS84 when its coordinates are
lon/lat, and rejected otherwise (see ``assume_wgs84_if_lonlat``).
Parameters
----------
data : GeoDataFrame
The GeoDataFrame containing geometries.
driver : str
The driver type (e.g., 'GPKG', 'ESRI Shapefile').
Returns
-------
BaseShapes
An instance of one of the geometry classes (Polygons, Lines, Points, or
Empty).
Raises
------
ValueError
If the data has no CRS and its coordinates are not lon/lat.
"""
data = assume_wgs84_if_lonlat(data, self.path.as_posix())
common_type = get_most_common_geometry_type(data)
match common_type:
case "Polygon" | "MultiPolygon":
return Polygons(data, path=self.path, crs=data.crs, driver=driver, keep_z=self.keep_z)
case "LineString" | "MultiLineString":
return Lines(data, path=self.path, crs=data.crs, driver=driver, keep_z=self.keep_z)
case "Point" | "MultiPoint":
return Points(data, path=self.path, crs=data.crs, driver=driver, keep_z=self.keep_z)
case _:
return Empty(data, path=self.path, crs=data.crs, driver=driver, keep_z=self.keep_z)
[docs]
@abstractmethod
def load_data(self) -> None:
"""
Abstract method to load data from the geospatial file.
This method must be implemented by subclasses to load the contents of the file
into memory or a data structure suitable for further operations.
Raises
------
NotImplementedError
If the method is not implemented by a subclass.
"""
pass
[docs]
@abstractmethod
def validate_file(self) -> None:
"""
Abstract method to validate the file according to specific handler requirements.
This method must be implemented by subclasses to perform custom validation
(e.g., checking the file's format or structure).
Raises
------
NotImplementedError
If the method is not implemented by a subclass.
"""
pass
[docs]
@abstractmethod
def create_obj(self, **kwargs: Any) -> BaseShapes:
"""
Abstract method to create and return an object representing the file's data.
This method must be implemented by subclasses to create and return an appropriate
object that represents the file's data, such as a GeoDataFrame or another custom
object.
Returns
-------
object
An object representing the data in the geospatial file.
Raises
------
NotImplementedError
If the method is not implemented by a subclass.
"""
pass