From 4cc6e02286dd25793c9a3802489d14bbfc1c75dc Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Fri, 28 Jun 2024 12:30:41 -0500 Subject: [PATCH 01/17] #75 modernize type hinting, pull aliases to a new module, add missing ellipse for __geo_interface__ on BaseMultiLineString --- fudgeo/alias.py | 76 ++++++++++++++++++++++ fudgeo/constant.py | 16 ++--- fudgeo/extension/metadata.py | 58 +++++++---------- fudgeo/extension/schema.py | 32 ++++------ fudgeo/extension/spatial.py | 22 +++---- fudgeo/geometry/base.py | 9 +-- fudgeo/geometry/linestring.py | 25 ++++---- fudgeo/geometry/linestring.pyi | 76 +++++++++++----------- fudgeo/geometry/point.py | 49 ++++++++------- fudgeo/geometry/point.pyi | 51 ++++++++------- fudgeo/geometry/polygon.py | 35 ++++++----- fudgeo/geometry/polygon.pyi | 111 ++++++++++++++++----------------- fudgeo/geometry/util.py | 34 +++------- fudgeo/geopkg.py | 37 +++++------ fudgeo/sql.py | 16 +++-- fudgeo/util.py | 5 +- 16 files changed, 343 insertions(+), 309 deletions(-) create mode 100644 fudgeo/alias.py diff --git a/fudgeo/alias.py b/fudgeo/alias.py new file mode 100644 index 0000000..182bfb1 --- /dev/null +++ b/fudgeo/alias.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +""" +Type Hint Aliases +""" + + +from datetime import datetime +from typing import Optional, TYPE_CHECKING, Union + + +if TYPE_CHECKING: + # noinspection PyUnresolvedReferences + from fudgeo.geopkg import FeatureClass, Field, Table + # noinspection PyUnresolvedReferences + from fudgeo.extension.metadata import ( + GeoPackageReference, TableReference, ColumnReference, + RowReference, RowColumnReference) + # noinspection PyUnresolvedReferences + from fudgeo.extension.schema import ( + EnumerationConstraint, GlobConstraint, RangeConstraint) + # noinspection PyUnresolvedReferences + from fudgeo.geometry.linestring import ( + LineString, LineStringZ, LineStringM, LineStringZM) + # noinspection PyUnresolvedReferences + from fudgeo.geometry.polygon import ( + LinearRing, LinearRingZ, LinearRingM, LinearRingZM, + Polygon, PolygonZ, PolygonM, PolygonZM) + + +INT = Optional[int] +BOOL = Optional[bool] +DATE = Optional[datetime] +FLOAT = Optional[float] +STRING = Optional[str] +BYTE_ARRAY = Optional[bytearray] + + +TABLE = Union['Table', 'FeatureClass'] +FIELDS = Union[tuple['Field', ...], list['Field']] + + +REFERENCE_RECORD = tuple[ + str, STRING, STRING, INT, datetime, int, INT] +REFERENCE = Union[ + 'GeoPackageReference', 'TableReference', 'ColumnReference', + 'RowReference', 'RowColumnReference'] +REFERENCES = Union[REFERENCE, tuple[REFERENCE, ...], list[REFERENCE]] + + +RECORDS = list[ + tuple[str, str, STRING, FLOAT, INT, FLOAT, INT, STRING]] +CONSTRAINT = Union[ + 'EnumerationConstraint', 'GlobConstraint', 'RangeConstraint'] +CONSTRAINTS = Union[ + CONSTRAINT, list[CONSTRAINT], tuple[CONSTRAINT, ...]] + + +NONES = tuple[None, None, None, None] +DOUBLE = tuple[float, float] +TRIPLE = tuple[float, float, float] +QUADRUPLE = tuple[float, float, float, float] +COORDINATES = Union[list[DOUBLE], list[TRIPLE], list[QUADRUPLE]] + + +GEOMS = Union[ + list['LineString'], list['LinearRing'], list['Polygon']] +GEOMS_Z = Union[ + list['LineStringZ'], list['LinearRingZ'], list['PolygonZ']] +GEOMS_M = Union[ + list['LineStringM'], list['LinearRingM'], list['PolygonM']] +GEOMS_ZM = Union[ + list['LineStringZM'], list['LinearRingZM'], list['PolygonZM']] + + +if __name__ == '__main__': # pragma: no cover + pass diff --git a/fudgeo/constant.py b/fudgeo/constant.py index 7f8d8d5..cdb6dad 100644 --- a/fudgeo/constant.py +++ b/fudgeo/constant.py @@ -5,14 +5,9 @@ from struct import pack -from typing import Dict, List, Set, Tuple, Union from fudgeo.enumeration import EnvelopeCode -DOUBLE = Tuple[float, float] -TRIPLE = Tuple[float, float, float] -QUADRUPLE = Tuple[float, float, float, float] -COORDINATES = Union[List[DOUBLE], List[TRIPLE], List[QUADRUPLE]] COMMA_SPACE: str = ', ' GPKG_EXT: str = '.gpkg' @@ -68,22 +63,23 @@ WKB_MULTI_POLYGON_ZM_PRE: bytes = pack(BYTE_CODE, 1, 3006) -POINT_PREFIX_ZM: Dict[Tuple[bool, bool], bytes] = { +POINT_PREFIX_ZM: dict[tuple[bool, bool], bytes] = { (False, False): WKB_POINT_PRE, (True, False): WKB_POINT_Z_PRE, (False, True): WKB_POINT_M_PRE, (True, True): WKB_POINT_ZM_PRE, } -POINT_PREFIXES: Set[bytes] = { +POINT_PREFIXES: set[bytes] = { WKB_POINT_PRE, WKB_POINT_Z_PRE, WKB_POINT_M_PRE, WKB_POINT_ZM_PRE} HEADER_OFFSET: int = 8 -ENVELOPE_LENGTH: Dict[int, int] = { +ENVELOPE_LENGTH: dict[int, int] = { EnvelopeCode.empty: 0, EnvelopeCode.xy: 32, EnvelopeCode.xyz: 48, EnvelopeCode.xym: 48, EnvelopeCode.xyzm: 64} -ENVELOPE_COUNT: Dict[int, int] = {k: v // 8 for k, v in ENVELOPE_LENGTH.items()} -ENVELOPE_OFFSET = {k: v + HEADER_OFFSET for k, v in ENVELOPE_LENGTH.items()} +ENVELOPE_COUNT: dict[int, int] = {k: v // 8 for k, v in ENVELOPE_LENGTH.items()} +ENVELOPE_OFFSET: dict[int, int] = { + k: v + HEADER_OFFSET for k, v in ENVELOPE_LENGTH.items()} if __name__ == '__main__': # pragma: no cover diff --git a/fudgeo/extension/metadata.py b/fudgeo/extension/metadata.py index fb2fa7c..7d6d45d 100644 --- a/fudgeo/extension/metadata.py +++ b/fudgeo/extension/metadata.py @@ -7,8 +7,9 @@ from abc import abstractmethod from datetime import datetime from sqlite3 import DatabaseError, OperationalError -from typing import List, Optional, TYPE_CHECKING, Tuple, Union +from typing import TYPE_CHECKING +from fudgeo.alias import DATE, INT, REFERENCES, REFERENCE_RECORD, TABLE from fudgeo.enumeration import MetadataReferenceScope, MetadataScope from fudgeo.sql import ( CREATE_METADATA, CREATE_METADATA_REFERENCE, HAS_METADATA, INSERT_EXTENSION, @@ -19,32 +20,22 @@ if TYPE_CHECKING: # pragma: no cover from sqlite3 import Connection - # noinspection PyUnresolvedReferences - from fudgeo.geopkg import FeatureClass, GeoPackage, Table - - -TABLE = Union['Table', 'FeatureClass'] -REFERENCE_RECORD = Tuple[str, Optional[str], Optional[str], Optional[int], - datetime, int, Optional[int]] -REFERENCE = Union['GeoPackageReference', 'TableReference', 'ColumnReference', - 'RowReference', 'RowColumnReference'] -REFERENCES = Union[REFERENCE, Tuple[REFERENCE, ...], List[REFERENCE]] + from fudgeo.geopkg import GeoPackage class AbstractReference: """ Abstract Reference """ - def __init__(self, scope: str, file_id: int, - parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + def __init__(self, scope: str, file_id: int, parent_id: INT = None, + timestamp: DATE = None) -> None: """ Initialize the AbstractReference class """ super().__init__() self._scope: str = scope self._file_id: int = file_id - self._parent_id: Optional[int] = parent_id + self._parent_id: INT = parent_id self._timestamp: datetime = timestamp or now() # End init built-in @@ -71,8 +62,7 @@ class AbstractTableReference(AbstractReference): Abstract Table Reference """ def __init__(self, scope: str, table_name: str, file_id: int, - parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + parent_id: INT = None, timestamp: DATE = None) -> None: """ Initialize the AbstractTableReference class """ @@ -143,8 +133,8 @@ class AbstractColumnReference(AbstractTableReference): Abstract Column Reference """ def __init__(self, scope: str, table_name: str, column_name: str, - file_id: int, parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + file_id: int, parent_id: INT = None, + timestamp: DATE = None) -> None: """ Initialize the AbstractTableReference class """ @@ -176,9 +166,8 @@ class GeoPackageReference(AbstractReference): """ GeoPackage Reference """ - def __init__(self, file_id: int, - parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + def __init__(self, file_id: int, parent_id: INT = None, + timestamp: DATE = None) -> None: """ Initialize the GeoPackageReference class """ @@ -208,9 +197,8 @@ class TableReference(AbstractTableReference): """ Table Reference """ - def __init__(self, table_name: str, file_id: int, - parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + def __init__(self, table_name: str, file_id: int, parent_id: INT = None, + timestamp: DATE = None) -> None: """ Initialize the TableReference class """ @@ -233,9 +221,8 @@ class ColumnReference(AbstractColumnReference): """ Column Reference """ - def __init__(self, table_name: str, column_name: str, - file_id: int, parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + def __init__(self, table_name: str, column_name: str, file_id: int, + parent_id: INT = None, timestamp: DATE = None) -> None: """ Initialize the ColumnReference class """ @@ -259,9 +246,8 @@ class RowReference(AbstractTableReference): """ Row Reference """ - def __init__(self, table_name: str, row_id: int, - file_id: int, parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + def __init__(self, table_name: str, row_id: int, file_id: int, + parent_id: INT = None, timestamp: DATE = None) -> None: """ Initialize the RowReference class """ @@ -294,8 +280,8 @@ class RowColumnReference(AbstractColumnReference): Row/Column Reference """ def __init__(self, table_name: str, column_name: str, row_id: int, - file_id: int, parent_id: Optional[int] = None, - timestamp: Optional[datetime] = None) -> None: + file_id: int, parent_id: INT = None, + timestamp: DATE = None) -> None: """ Initialize the RowColumnReference class """ @@ -337,10 +323,8 @@ def __init__(self, geopackage: 'GeoPackage') -> None: self._geopackage: 'GeoPackage' = geopackage # End init built-in - def add_metadata(self, uri: str, - scope: str = MetadataScope.dataset, - metadata: str = '', - mime_type: str = 'text/xml') -> Optional[int]: + def add_metadata(self, uri: str, scope: str = MetadataScope.dataset, + metadata: str = '', mime_type: str = 'text/xml') -> INT: """ Add Metadata to the Geopackage if the metadata extension enabled. """ diff --git a/fudgeo/extension/schema.py b/fudgeo/extension/schema.py index 1714a61..df861bf 100644 --- a/fudgeo/extension/schema.py +++ b/fudgeo/extension/schema.py @@ -7,8 +7,9 @@ from abc import abstractmethod from numbers import Number from sqlite3 import DatabaseError, OperationalError -from typing import List, Optional, TYPE_CHECKING, Tuple, Union +from typing import TYPE_CHECKING, Union +from fudgeo.alias import CONSTRAINTS, RECORDS, STRING from fudgeo.enumeration import ConstraintType, SQLFieldType from fudgeo.sql import ( CREATE_DATA_COLUMNS, CREATE_DATA_COLUMN_CONSTRAINTS, HAS_SCHEMA, @@ -21,25 +22,19 @@ from fudgeo.geopkg import GeoPackage -RECORDS = List[Tuple[str, str, Optional[str], Optional[float], Optional[int], - Optional[float], Optional[int], Optional[str]]] -CONSTRAINT = Union['EnumerationConstraint', 'GlobConstraint', 'RangeConstraint'] -CONSTRAINTS = Union[CONSTRAINT, List[CONSTRAINT], Tuple[CONSTRAINT, ...]] - - class AbstractConstraint: """ Abstract Constraint """ def __init__(self, type_: str, name: str, - description: Optional[str]) -> None: + description: STRING) -> None: """ Initialize the AbstractConstraint class """ super().__init__() self._type: str = type_ self._name: str = name - self._description: Optional[str] = description + self._description: STRING = description # End init built-in def validate(self) -> None: @@ -64,14 +59,14 @@ class EnumerationConstraint(AbstractConstraint): """ Enumeration Constraint """ - def __init__(self, name: str, values: Union[List, Tuple], - description: Optional[str] = None) -> None: + def __init__(self, name: str, values: Union[list, tuple], + description: STRING = None) -> None: """ Initialize the EnumerationConstraint class """ super().__init__( type_=ConstraintType.enum, name=name, description=description) - self._values: List = sorted(set(values)) + self._values: list = sorted(set(values)) # End init built-in def validate(self) -> None: @@ -99,7 +94,7 @@ class GlobConstraint(AbstractConstraint): Glob Constraint """ def __init__(self, name: str, pattern: str, - description: Optional[str] = None) -> None: + description: STRING = None) -> None: """ Initialize the GlobConstraint class """ @@ -133,7 +128,7 @@ class RangeConstraint(AbstractConstraint): """ def __init__(self, name: str, min_value: float, max_value: float, min_inclusive: bool = True, max_inclusive: bool = True, - description: Optional[str] = None) -> None: + description: STRING = None) -> None: """ Initialize the RangeConstraint class """ @@ -181,11 +176,10 @@ def __init__(self, geopackage: 'GeoPackage') -> None: # End init built-in def add_column_definition(self, table_name: str, column_name: str, - name: Optional[str] = None, - title: Optional[str] = None, - description: Optional[str] = None, - mime_type: Optional[str] = None, - constraint_name: Optional[str] = None) -> None: + name: STRING = None, title: STRING = None, + description: STRING = None, + mime_type: STRING = None, + constraint_name: STRING = None) -> None: """ Add Column Definition """ diff --git a/fudgeo/extension/spatial.py b/fudgeo/extension/spatial.py index 1728e99..50cf61c 100644 --- a/fudgeo/extension/spatial.py +++ b/fudgeo/extension/spatial.py @@ -8,11 +8,12 @@ from sqlite3 import IntegrityError # noinspection PyPep8Naming from struct import error as StructError, unpack -from typing import Callable, Dict, Optional, TYPE_CHECKING, Tuple, Type, Union +from typing import Callable, TYPE_CHECKING, Type, Union +from fudgeo.alias import FLOAT, INT, NONES, QUADRUPLE from fudgeo.constant import ( ENVELOPE_COUNT, ENVELOPE_OFFSET, HEADER_CODE, HEADER_OFFSET, POINT_PREFIXES, - QUADRUPLE, WKB_LINESTRING_M_PRE, WKB_LINESTRING_PRE, WKB_LINESTRING_ZM_PRE, + WKB_LINESTRING_M_PRE, WKB_LINESTRING_PRE, WKB_LINESTRING_ZM_PRE, WKB_LINESTRING_Z_PRE, WKB_MULTI_LINESTRING_M_PRE, WKB_MULTI_LINESTRING_PRE, WKB_MULTI_LINESTRING_ZM_PRE, WKB_MULTI_LINESTRING_Z_PRE, WKB_MULTI_POINT_M_PRE, WKB_MULTI_POINT_PRE, WKB_MULTI_POINT_ZM_PRE, @@ -40,9 +41,6 @@ from fudgeo.geopkg import FeatureClass -NONES = Tuple[None, None, None, None] - - def add_spatial_index(conn: 'Connection', feature_class: 'FeatureClass') -> None: """ Add Spatial Index Table, Table Entry, and Triggers. Load Spatial Index @@ -100,7 +98,7 @@ def _find_bounds(geometry: bytes) -> Union[QUADRUPLE, NONES]: # End _find_bounds function -def _st_is_empty(geometry: bytes) -> Optional[int]: +def _st_is_empty(geometry: bytes) -> INT: """ Is Empty """ @@ -111,7 +109,7 @@ def _st_is_empty(geometry: bytes) -> Optional[int]: # End _st_is_empty function -def _st_min_x(geometry: bytes) -> Optional[float]: +def _st_min_x(geometry: bytes) -> FLOAT: """ Min X """ @@ -119,7 +117,7 @@ def _st_min_x(geometry: bytes) -> Optional[float]: # End _st_min_x function -def _st_max_x(geometry: bytes) -> Optional[float]: +def _st_max_x(geometry: bytes) -> FLOAT: """ Max X """ @@ -127,7 +125,7 @@ def _st_max_x(geometry: bytes) -> Optional[float]: # End _st_max_x function -def _st_min_y(geometry: bytes) -> Optional[float]: +def _st_min_y(geometry: bytes) -> FLOAT: """ Min Y """ @@ -135,7 +133,7 @@ def _st_min_y(geometry: bytes) -> Optional[float]: # End _st_min_y function -def _st_max_y(geometry: bytes) -> Optional[float]: +def _st_max_y(geometry: bytes) -> FLOAT: """ Max Y """ @@ -143,7 +141,7 @@ def _st_max_y(geometry: bytes) -> Optional[float]: # End _st_max_y function -ST_FUNCS: Dict[str, Callable] = { +ST_FUNCS: dict[str, Callable[[bytes], Union[INT, FLOAT]]] = { 'ST_IsEmpty': _st_is_empty, 'ST_MinX': _st_min_x, 'ST_MaxX': _st_max_x, @@ -152,7 +150,7 @@ def _st_max_y(geometry: bytes) -> Optional[float]: } -PREFIX_GEOM_TYPE: Dict[bytes, Type['AbstractGeometry']] = { +PREFIX_GEOM_TYPE: dict[bytes, Type['AbstractGeometry']] = { WKB_POINT_PRE: Point, WKB_POINT_Z_PRE: PointZ, WKB_POINT_M_PRE: PointM, diff --git a/fudgeo/geometry/base.py b/fudgeo/geometry/base.py index 76f9a0a..c4b49c1 100644 --- a/fudgeo/geometry/base.py +++ b/fudgeo/geometry/base.py @@ -5,8 +5,9 @@ from abc import abstractmethod -from typing import List, Optional, Tuple +from typing import Optional +from fudgeo.alias import BOOL from fudgeo.geometry.util import EMPTY_ENVELOPE, Envelope, make_header @@ -23,8 +24,8 @@ def __init__(self, srs_id: int) -> None: super().__init__() self.srs_id: int = srs_id self._env: Envelope = EMPTY_ENVELOPE - self._args: Optional[Tuple[memoryview, int]] = None - self._is_empty: Optional[bool] = None + self._args: Optional[tuple[memoryview, int]] = None + self._is_empty: BOOL = None # End init built-in @abstractmethod @@ -37,7 +38,7 @@ def _to_wkb(self, ary: bytearray) -> bytearray: # pragma: nocover @staticmethod def _join_geometries(ary: bytearray, - geoms: List['AbstractGeometry']) -> bytearray: + geoms: list['AbstractGeometry']) -> bytearray: """ Join Geometries """ diff --git a/fudgeo/geometry/linestring.py b/fudgeo/geometry/linestring.py index f9d3e08..418add4 100644 --- a/fudgeo/geometry/linestring.py +++ b/fudgeo/geometry/linestring.py @@ -5,7 +5,7 @@ from struct import pack -from typing import Any, ClassVar, Dict, List, TYPE_CHECKING +from typing import Any, ClassVar, TYPE_CHECKING from fudgeo.constant import ( COUNT_CODE, EMPTY, FOUR_D, THREE_D, TWO_D, WKB_LINESTRING_M_PRE, @@ -16,12 +16,13 @@ from fudgeo.geometry.base import AbstractGeometry from fudgeo.geometry.point import Point, PointM, PointZ, PointZM from fudgeo.geometry.util import ( - EMPTY_ENVELOPE, ENV_COORD, ENV_GEOM, Envelope, as_array, lazy_unpack, + EMPTY_ENVELOPE, ENV_COORD, ENV_GEOM, as_array, lazy_unpack, pack_coordinates, unpack_line, unpack_lines) if TYPE_CHECKING: from numpy import ndarray + from fudgeo.geometry.util import Envelope class BaseLineString(AbstractGeometry): @@ -37,7 +38,7 @@ class BaseLineString(AbstractGeometry): _has_z: ClassVar[bool] = False _wkb_prefix: ClassVar[bytes] = EMPTY - def __init__(self, coordinates: List, srs_id: int) -> None: + def __init__(self, coordinates: list, srs_id: int) -> None: """ Initialize the BaseLineString class """ @@ -57,7 +58,7 @@ def __eq__(self, other: Any) -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict: + def __geo_interface__(self) -> dict: """ Geo Interface """ @@ -93,7 +94,7 @@ def is_empty(self) -> bool: # End is_empty property @property - def points(self) -> List: + def points(self) -> list: """ Points """ @@ -104,7 +105,7 @@ def points(self) -> List: # End points property @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -200,12 +201,12 @@ class BaseMultiLineString(AbstractGeometry): _env_code: ClassVar[int] = EnvelopeCode.empty _wkb_prefix: ClassVar[bytes] = EMPTY - def __init__(self, coordinates: List[List], srs_id: int) -> None: + def __init__(self, coordinates: list[list], srs_id: int) -> None: """ Initialize the MultiLineString class """ super().__init__(srs_id=srs_id) - self._lines: List[LineString] = self._make_lines(coordinates) + self._lines: list[LineString] = self._make_lines(coordinates) # End init built-in def __eq__(self, other: Any) -> bool: @@ -220,7 +221,7 @@ def __eq__(self, other: Any) -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict: + def __geo_interface__(self) -> dict: """ Geo Interface """ @@ -234,7 +235,7 @@ def __geo_interface__(self) -> Dict: for line in self.lines)} # End geo_interface property - def _make_lines(self, coordinates: List[List]) -> List: + def _make_lines(self, coordinates: list[list]) -> list: """ Make Lines """ @@ -245,7 +246,7 @@ def _make_lines(self, coordinates: List[List]) -> List: # End init built-in @property - def lines(self) -> List: + def lines(self) -> list: """ Lines """ @@ -276,7 +277,7 @@ def _to_wkb(self, ary: bytearray) -> bytearray: # End _to_wkb method @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ diff --git a/fudgeo/geometry/linestring.pyi b/fudgeo/geometry/linestring.pyi index ff8399f..02a2db1 100644 --- a/fudgeo/geometry/linestring.pyi +++ b/fudgeo/geometry/linestring.pyi @@ -3,11 +3,11 @@ Line String """ -from typing import Any, ClassVar, Dict, List, Tuple, Union +from typing import Any, ClassVar, Union from numpy import ndarray -from fudgeo.constant import DOUBLE, QUADRUPLE, TRIPLE +from fudgeo.alias import DOUBLE, QUADRUPLE, TRIPLE from fudgeo.geometry.base import AbstractGeometry from fudgeo.geometry.point import Point, PointM, PointZ, PointZM from fudgeo.geometry.util import Envelope @@ -25,16 +25,16 @@ class BaseLineString(AbstractGeometry): _wkb_prefix: ClassVar[bytes] _coordinates: ndarray - def __init__(self, coordinates: List, srs_id: int) -> None: ... + def __init__(self, coordinates: list, srs_id: int) -> None: ... def __eq__(self, other: Any) -> bool: ... @property - def __geo_interface__(self) -> Dict: ... + def __geo_interface__(self) -> dict: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List: ... + def points(self) -> list: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -47,16 +47,16 @@ class LineString(BaseLineString): """ Line String """ - def __init__(self, coordinates: List[DOUBLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[DOUBLE], srs_id: int) -> None: ... def __eq__(self, other: 'LineString') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[DOUBLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[DOUBLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[Point]: ... + def points(self) -> list[Point]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -69,16 +69,16 @@ class LineStringZ(BaseLineString): """ Line String Z """ - def __init__(self, coordinates: List[TRIPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[TRIPLE], srs_id: int) -> None: ... def __eq__(self, other: 'LineStringZ') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[TRIPLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[TRIPLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointZ]: ... + def points(self) -> list[PointZ]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -91,16 +91,16 @@ class LineStringM(BaseLineString): """ Line String M """ - def __init__(self, coordinates: List[TRIPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[TRIPLE], srs_id: int) -> None: ... def __eq__(self, other: 'LineStringM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[TRIPLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[TRIPLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointM]: ... + def points(self) -> list[PointM]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -113,16 +113,16 @@ class LineStringZM(BaseLineString): """ Line String ZM """ - def __init__(self, coordinates: List[QUADRUPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[QUADRUPLE], srs_id: int) -> None: ... def __eq__(self, other: 'LineStringZM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[QUADRUPLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[QUADRUPLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointZM]: ... + def points(self) -> list[PointZM]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -139,15 +139,15 @@ class BaseMultiLineString(AbstractGeometry): _dimension: ClassVar[int] _env_code: ClassVar[int] _wkb_prefix: ClassVar[bytes] - _lines: List + _lines: list - def __init__(self, coordinates: List[List], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list], srs_id: int) -> None: ... def __eq__(self, other: Any) -> bool: ... @property - def __geo_interface__(self) -> Dict: - def _make_lines(self, coordinates: List[List]) -> List: ... + def __geo_interface__(self) -> dict: ... + def _make_lines(self, coordinates: list[list]) -> list: ... @property - def lines(self) -> List: ... + def lines(self) -> list: ... @property def is_empty(self) -> bool: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -162,13 +162,13 @@ class MultiLineString(BaseMultiLineString): """ Multi Line String """ - def __init__(self, coordinates: List[List[DOUBLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[DOUBLE]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiLineString') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[DOUBLE]]]]: ... - def _make_lines(self, coordinates: List[List[DOUBLE]]) -> List[LineString]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[DOUBLE]]]]: ... + def _make_lines(self, coordinates: list[list[DOUBLE]]) -> list[LineString]: ... @property - def lines(self) -> List[LineString]: ... + def lines(self) -> list[LineString]: ... @property def is_empty(self) -> bool: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -183,13 +183,13 @@ class MultiLineStringZ(BaseMultiLineString): """ Multi Line String Z """ - def __init__(self, coordinates: List[List[TRIPLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[TRIPLE]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiLineStringZ') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[TRIPLE]]]]: ... - def _make_lines(self, coordinates: List[List[TRIPLE]]) -> List[LineStringZ]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[TRIPLE]]]]: ... + def _make_lines(self, coordinates: list[list[TRIPLE]]) -> list[LineStringZ]: ... @property - def lines(self) -> List[LineStringZ]: ... + def lines(self) -> list[LineStringZ]: ... @property def is_empty(self) -> bool: ... @property @@ -204,13 +204,13 @@ class MultiLineStringM(BaseMultiLineString): """ Multi Line String M """ - def __init__(self, coordinates: List[List[TRIPLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[TRIPLE]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiLineStringM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[TRIPLE]]]]: ... - def _make_lines(self, coordinates: List[List[TRIPLE]]) -> List[LineStringM]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[TRIPLE]]]]: ... + def _make_lines(self, coordinates: list[list[TRIPLE]]) -> list[LineStringM]: ... @property - def lines(self) -> List[LineStringM]: ... + def lines(self) -> list[LineStringM]: ... @property def is_empty(self) -> bool: ... @property @@ -225,13 +225,13 @@ class MultiLineStringZM(BaseMultiLineString): """ Multi Line String ZM """ - def __init__(self, coordinates: List[List[QUADRUPLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[QUADRUPLE]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiLineStringZM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[QUADRUPLE]]]]: ... - def _make_lines(self, coordinates: List[List[QUADRUPLE]]) -> List[LineStringZM]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[QUADRUPLE]]]]: ... + def _make_lines(self, coordinates: list[list[QUADRUPLE]]) -> list[LineStringZM]: ... @property - def lines(self) -> List[LineStringZM]: ... + def lines(self) -> list[LineStringZM]: ... @property def is_empty(self) -> bool: ... @property diff --git a/fudgeo/geometry/point.py b/fudgeo/geometry/point.py index 4340ca4..704209d 100644 --- a/fudgeo/geometry/point.py +++ b/fudgeo/geometry/point.py @@ -6,22 +6,25 @@ from math import isnan, nan from struct import pack, unpack -from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Union +from typing import Any, ClassVar, TYPE_CHECKING, Union +from fudgeo.alias import BYTE_ARRAY, DOUBLE, QUADRUPLE, TRIPLE from fudgeo.constant import ( - DOUBLE, EMPTY, FOUR_D, FOUR_D_PACK_CODE, FOUR_D_UNPACK_CODE, HEADER_OFFSET, - QUADRUPLE, THREE_D, THREE_D_PACK_CODE, THREE_D_UNPACK_CODE, TRIPLE, TWO_D, - TWO_D_PACK_CODE, TWO_D_UNPACK_CODE, WKB_MULTI_POINT_M_PRE, - WKB_MULTI_POINT_PRE, WKB_MULTI_POINT_ZM_PRE, WKB_MULTI_POINT_Z_PRE, - WKB_POINT_M_PRE, WKB_POINT_PRE, WKB_POINT_ZM_PRE, WKB_POINT_Z_PRE) + EMPTY, FOUR_D, FOUR_D_PACK_CODE, FOUR_D_UNPACK_CODE, HEADER_OFFSET, THREE_D, + THREE_D_PACK_CODE, THREE_D_UNPACK_CODE, TWO_D, TWO_D_PACK_CODE, + TWO_D_UNPACK_CODE, WKB_MULTI_POINT_M_PRE, WKB_MULTI_POINT_PRE, + WKB_MULTI_POINT_ZM_PRE, WKB_MULTI_POINT_Z_PRE, WKB_POINT_M_PRE, + WKB_POINT_PRE, WKB_POINT_ZM_PRE, WKB_POINT_Z_PRE) from fudgeo.enumeration import EnvelopeCode from fudgeo.geometry.base import AbstractGeometry from fudgeo.geometry.util import ( - EMPTY_ENVELOPE, ENV_COORD, Envelope, as_array, lazy_unpack, make_header, + EMPTY_ENVELOPE, ENV_COORD, as_array, lazy_unpack, make_header, pack_coordinates, unpack_header, unpack_points) + if TYPE_CHECKING: from numpy import ndarray + from fudgeo.geometry.util import Envelope class Point(AbstractGeometry): @@ -51,7 +54,7 @@ def __eq__(self, other: 'Point') -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict[str, Union[str, DOUBLE]]: + def __geo_interface__(self) -> dict[str, Union[str, DOUBLE]]: """ Geo Interface """ @@ -82,7 +85,7 @@ def _unpack(value: bytes) -> DOUBLE: return x, y # End _unpack method - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: """ To WKB """ @@ -90,7 +93,7 @@ def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: # End _to_wkb method @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -164,7 +167,7 @@ def __eq__(self, other: 'PointZ') -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict[str, Union[str, TRIPLE]]: + def __geo_interface__(self) -> dict[str, Union[str, TRIPLE]]: """ Geo Interface """ @@ -196,7 +199,7 @@ def _unpack(value: bytes) -> TRIPLE: return x, y, z # End _unpack method - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: """ To WKB """ @@ -204,7 +207,7 @@ def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: # End _to_wkb method @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -278,7 +281,7 @@ def __eq__(self, other: 'PointM') -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict[str, Union[str, TRIPLE]]: + def __geo_interface__(self) -> dict[str, Union[str, TRIPLE]]: """ Geo Interface """ @@ -309,7 +312,7 @@ def _unpack(value: bytes) -> TRIPLE: return x, y, m # End _unpack method - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: """ To WKB """ @@ -317,7 +320,7 @@ def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: # End _to_wkb method @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -393,7 +396,7 @@ def __eq__(self, other: 'PointZM') -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict[str, Union[str, QUADRUPLE]]: + def __geo_interface__(self) -> dict[str, Union[str, QUADRUPLE]]: """ Geo Interface """ @@ -428,7 +431,7 @@ def _unpack(value: bytes) -> QUADRUPLE: return x, y, z, m # End _unpack method - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: """ To WKB """ @@ -436,7 +439,7 @@ def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: # End _to_wkb method @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -495,7 +498,7 @@ class BaseMultiPoint(AbstractGeometry): _has_z: ClassVar[bool] = False _wkb_prefix: ClassVar[bytes] = EMPTY - def __init__(self, coordinates: List, srs_id: int) -> None: + def __init__(self, coordinates: list, srs_id: int) -> None: """ Initialize the BaseMultiPoint class """ @@ -515,7 +518,7 @@ def __eq__(self, other: Any) -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict: + def __geo_interface__(self) -> dict: """ Geo Interface """ @@ -551,7 +554,7 @@ def is_empty(self) -> bool: # End is_empty property @property - def points(self) -> List: + def points(self) -> list: """ Points """ @@ -562,7 +565,7 @@ def points(self) -> List: # End points property @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ diff --git a/fudgeo/geometry/point.pyi b/fudgeo/geometry/point.pyi index 0d4e7ff..43e279b 100644 --- a/fudgeo/geometry/point.pyi +++ b/fudgeo/geometry/point.pyi @@ -3,16 +3,15 @@ Points """ -from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union +from typing import Any, ClassVar, Union from numpy import ndarray -from fudgeo.constant import DOUBLE, QUADRUPLE, TRIPLE +from fudgeo.alias import BYTE_ARRAY, DOUBLE, QUADRUPLE, TRIPLE from fudgeo.geometry.base import AbstractGeometry from fudgeo.geometry.util import Envelope - class Point(AbstractGeometry): """ Point @@ -23,13 +22,13 @@ class Point(AbstractGeometry): def __init__(self, *, x: float, y: float, srs_id: int) -> None: ... def __eq__(self, other: 'Point') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, DOUBLE]]: ... + def __geo_interface__(self) -> dict[str, Union[str, DOUBLE]]: ... def as_tuple(self) -> DOUBLE: ... @property def is_empty(self) -> bool: ... @staticmethod def _unpack(value: bytes) -> DOUBLE: ... - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: ... + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: ... @property def envelope(self) -> Envelope: ... def to_gpkg(self) -> bytes: ... @@ -53,13 +52,13 @@ class PointZ(AbstractGeometry): def __init__(self, *, x: float, y: float, z: float, srs_id: int) -> None: ... def __eq__(self, other: 'PointZ') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, TRIPLE]]: ... + def __geo_interface__(self) -> dict[str, Union[str, TRIPLE]]: ... def as_tuple(self) -> TRIPLE: ... @property def is_empty(self) -> bool: ... @staticmethod def _unpack(value: bytes) -> TRIPLE: ... - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: ... + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: ... @property def envelope(self) -> Envelope: ... def to_gpkg(self) -> bytes: ... @@ -83,13 +82,13 @@ class PointM(AbstractGeometry): def __init__(self, *, x: float, y: float, m: float, srs_id: int) -> None: ... def __eq__(self, other: 'PointM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, TRIPLE]]: ... + def __geo_interface__(self) -> dict[str, Union[str, TRIPLE]]: ... def as_tuple(self) -> TRIPLE: ... @property def is_empty(self) -> bool: ... @staticmethod def _unpack(value: bytes) -> TRIPLE: ... - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: ... + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: ... @property def envelope(self) -> Envelope: ... def to_gpkg(self) -> bytes: ... @@ -114,13 +113,13 @@ class PointZM(AbstractGeometry): def __init__(self, *, x: float, y: float, z: float, m: float, srs_id: int) -> None: ... def __eq__(self, other: 'PointZM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, QUADRUPLE]]: ... + def __geo_interface__(self) -> dict[str, Union[str, QUADRUPLE]]: ... def as_tuple(self) -> QUADRUPLE: ... @property def is_empty(self) -> bool: ... @staticmethod def _unpack(value: bytes) -> QUADRUPLE: ... - def _to_wkb(self, ary: Optional[bytearray] = None) -> bytes: ... + def _to_wkb(self, ary: BYTE_ARRAY = None) -> bytes: ... @property def envelope(self) -> Envelope: ... def to_gpkg(self) -> bytes: ... @@ -147,16 +146,16 @@ class BaseMultiPoint(AbstractGeometry): _wkb_prefix: ClassVar[bytes] _coordinates: ndarray - def __init__(self, coordinates: List, srs_id: int) -> None: ... + def __init__(self, coordinates: list, srs_id: int) -> None: ... def __eq__(self, other: Any) -> bool: ... @property - def __geo_interface__(self) -> Dict: ... + def __geo_interface__(self) -> dict: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List: ... + def points(self) -> list: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -169,16 +168,16 @@ class MultiPoint(BaseMultiPoint): """ Multi Point """ - def __init__(self, coordinates: List[DOUBLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[DOUBLE], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPoint') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[DOUBLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[DOUBLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[Point]: ... + def points(self) -> list[Point]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -191,16 +190,16 @@ class MultiPointZ(BaseMultiPoint): """ Multi Point Z """ - def __init__(self, coordinates: List[TRIPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[TRIPLE], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPointZ') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[TRIPLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[TRIPLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointZ]: ... + def points(self) -> list[PointZ]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -213,16 +212,16 @@ class MultiPointM(BaseMultiPoint): """ Multi Point M """ - def __init__(self, coordinates: List[TRIPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[TRIPLE], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPointM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[TRIPLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[TRIPLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointM]: ... + def points(self) -> list[PointM]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -235,16 +234,16 @@ class MultiPointZM(BaseMultiPoint): """ Multi Point ZM """ - def __init__(self, coordinates: List[QUADRUPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[QUADRUPLE], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPointZM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Tuple[QUADRUPLE]]: ... + def __geo_interface__(self) -> dict[str, tuple[QUADRUPLE]]: ... @property def coordinates(self) -> 'ndarray': ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointZM]: ... + def points(self) -> list[PointZM]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... diff --git a/fudgeo/geometry/polygon.py b/fudgeo/geometry/polygon.py index 54a1570..f64a833 100644 --- a/fudgeo/geometry/polygon.py +++ b/fudgeo/geometry/polygon.py @@ -5,7 +5,7 @@ from struct import pack -from typing import Any, ClassVar, Dict, List, TYPE_CHECKING +from typing import Any, ClassVar, TYPE_CHECKING from fudgeo.constant import ( COUNT_CODE, EMPTY, FOUR_D, THREE_D, TWO_D, WKB_MULTI_POLYGON_M_PRE, @@ -15,12 +15,13 @@ from fudgeo.geometry.base import AbstractGeometry from fudgeo.geometry.point import Point, PointM, PointZ, PointZM from fudgeo.geometry.util import ( - EMPTY_ENVELOPE, ENV_COORD, ENV_GEOM, Envelope, as_array, lazy_unpack, + EMPTY_ENVELOPE, ENV_COORD, ENV_GEOM, as_array, lazy_unpack, pack_coordinates, unpack_lines, unpack_polygons) if TYPE_CHECKING: from numpy import ndarray + from fudgeo.geometry.util import Envelope class BaseLinearRing(AbstractGeometry): @@ -32,7 +33,7 @@ class BaseLinearRing(AbstractGeometry): _class: ClassVar[Any] = object _env_code: ClassVar[int] = EnvelopeCode.empty - def __init__(self, coordinates: List, srs_id: int) -> None: + def __init__(self, coordinates: list, srs_id: int) -> None: """ Initialize the BaseLinearRing class """ @@ -60,7 +61,7 @@ def is_empty(self) -> bool: # End is_empty property @property - def points(self) -> List: + def points(self) -> list: """ Points """ @@ -71,7 +72,7 @@ def points(self) -> List: # End points property @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -154,12 +155,12 @@ class BasePolygon(AbstractGeometry): _env_code: ClassVar[int] = EnvelopeCode.empty _wkb_prefix: ClassVar[bytes] = EMPTY - def __init__(self, coordinates: List[List], srs_id: int) -> None: + def __init__(self, coordinates: list[list], srs_id: int) -> None: """ Initialize the BasePolygon class """ super().__init__(srs_id=srs_id) - self._rings: List = self._make_rings(coordinates) + self._rings: list = self._make_rings(coordinates) # End init built-in def __eq__(self, other: Any) -> bool: @@ -174,7 +175,7 @@ def __eq__(self, other: Any) -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict: + def __geo_interface__(self) -> dict: """ Geo Interface """ @@ -188,7 +189,7 @@ def __geo_interface__(self) -> Dict: for ring in self.rings)} # End geo_interface property - def _make_rings(self, coordinates: List[List]) -> List: + def _make_rings(self, coordinates: list[list]) -> list: """ Make Rings """ @@ -198,7 +199,7 @@ def _make_rings(self, coordinates: List[List]) -> List: # End _make_rings method @property - def rings(self) -> List: + def rings(self) -> list: """ Rings """ @@ -221,7 +222,7 @@ def is_empty(self) -> bool: # End is_empty property @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ @@ -314,13 +315,13 @@ class BaseMultiPolygon(AbstractGeometry): _env_code: ClassVar[int] = EnvelopeCode.empty _wkb_prefix: ClassVar[bytes] = EMPTY - def __init__(self, coordinates: List[List[List]], + def __init__(self, coordinates: list[list[list]], srs_id: int) -> None: """ Initialize the BaseMultiPolygon class """ super().__init__(srs_id=srs_id) - self._polygons: List = self._make_polygons(coordinates) + self._polygons: list = self._make_polygons(coordinates) # End init built-in def __eq__(self, other: Any) -> bool: @@ -335,7 +336,7 @@ def __eq__(self, other: Any) -> bool: # End eq built-in @property - def __geo_interface__(self) -> Dict: + def __geo_interface__(self) -> dict: """ Geo Interface """ @@ -349,7 +350,7 @@ def __geo_interface__(self) -> Dict: for ring in poly.rings) for poly in self.polygons)} # End geo_interface property - def _make_polygons(self, coordinates: List[List[List]]) -> List: + def _make_polygons(self, coordinates: list[list[list]]) -> list: """ Make Polygons """ @@ -359,7 +360,7 @@ def _make_polygons(self, coordinates: List[List[List]]) -> List: # End _make_polygons method @property - def polygons(self) -> List: + def polygons(self) -> list: """ Polygons """ @@ -381,7 +382,7 @@ def is_empty(self) -> bool: # End is_empty property @property - def envelope(self) -> Envelope: + def envelope(self) -> 'Envelope': """ Envelope """ diff --git a/fudgeo/geometry/polygon.pyi b/fudgeo/geometry/polygon.pyi index e93fa82..b83856d 100644 --- a/fudgeo/geometry/polygon.pyi +++ b/fudgeo/geometry/polygon.pyi @@ -4,12 +4,11 @@ Polygons """ -from typing import Any, ClassVar, Dict, List, Tuple, Union +from typing import Any, ClassVar, Union from numpy import ndarray -from fudgeo.constant import ( - DOUBLE, QUADRUPLE, TRIPLE) +from fudgeo.alias import DOUBLE, QUADRUPLE, TRIPLE from fudgeo.geometry.base import AbstractGeometry from fudgeo.geometry.point import Point, PointM, PointZ, PointZM from fudgeo.geometry.util import Envelope @@ -23,14 +22,14 @@ class BaseLinearRing(AbstractGeometry): _env_code: ClassVar[int] coordinates: ndarray - def __init__(self, coordinates: List, srs_id: int) -> None: ... + def __init__(self, coordinates: list, srs_id: int) -> None: ... def __eq__(self, other: Any) -> bool: ... @property - def __geo_interface__(self) -> Dict: ... + def __geo_interface__(self) -> dict: ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List: ... + def points(self) -> list: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -43,12 +42,12 @@ class LinearRing(BaseLinearRing): """ Linear Ring """ - def __init__(self, coordinates: List[DOUBLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[DOUBLE], srs_id: int) -> None: ... def __eq__(self, other: 'LinearRing') -> bool: ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[Point]: ... + def points(self) -> list[Point]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -61,12 +60,12 @@ class LinearRingZ(BaseLinearRing): """ Linear Ring Z """ - def __init__(self, coordinates: List[TRIPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[TRIPLE], srs_id: int) -> None: ... def __eq__(self, other: 'LinearRingZ') -> bool: ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointZ]: ... + def points(self) -> list[PointZ]: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @property def envelope(self) -> Envelope: ... @@ -79,12 +78,12 @@ class LinearRingM(BaseLinearRing): """ Linear Ring M """ - def __init__(self, coordinates: List[TRIPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[TRIPLE], srs_id: int) -> None: ... def __eq__(self, other: 'LinearRingM') -> bool: ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointM]: ... + def points(self) -> list[PointM]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -97,12 +96,12 @@ class LinearRingZM(BaseLinearRing): """ Linear Ring ZM """ - def __init__(self, coordinates: List[QUADRUPLE], srs_id: int) -> None: ... + def __init__(self, coordinates: list[QUADRUPLE], srs_id: int) -> None: ... def __eq__(self, other: 'LinearRingZM') -> bool: ... @property def is_empty(self) -> bool: ... @property - def points(self) -> List[PointZM]: ... + def points(self) -> list[PointZM]: ... @property def envelope(self) -> Envelope: ... def _to_wkb(self, ary: bytearray) -> bytearray: ... @@ -119,15 +118,15 @@ class BasePolygon(AbstractGeometry): _dimension: ClassVar[int] _env_code: ClassVar[int] _wkb_prefix: ClassVar[bytes] - _rings: List + _rings: list - def __init__(self, coordinates: List[List], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list], srs_id: int) -> None: ... def __eq__(self, other: Any) -> bool: ... @property - def __geo_interface__(self) -> Dict: ... - def _make_rings(self, coordinates: List[List]) -> List: ... + def __geo_interface__(self) -> dict: ... + def _make_rings(self, coordinates: list[list]) -> list: ... @property - def rings(self) -> List: ... + def rings(self) -> list: ... @property def is_empty(self) -> bool: ... @property @@ -142,13 +141,13 @@ class Polygon(BasePolygon): """ Polygon """ - def __init__(self, coordinates: List[List[DOUBLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[DOUBLE]], srs_id: int) -> None: ... def __eq__(self, other: 'Polygon') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[DOUBLE]]]]: ... - def _make_rings(self, coordinates: List[List[DOUBLE]]) -> List[LinearRing]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[DOUBLE]]]]: ... + def _make_rings(self, coordinates: list[list[DOUBLE]]) -> list[LinearRing]: ... @property - def rings(self) -> List[LinearRing]: ... + def rings(self) -> list[LinearRing]: ... @property def is_empty(self) -> bool: ... @property @@ -163,13 +162,13 @@ class PolygonZ(BasePolygon): """ Polygon Z """ - def __init__(self, coordinates: List[List[TRIPLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[TRIPLE]], srs_id: int) -> None: ... def __eq__(self, other: 'PolygonZ') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[TRIPLE]]]]: ... - def _make_rings(self, coordinates: List[List[TRIPLE]]) -> List[LinearRingZ]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[TRIPLE]]]]: ... + def _make_rings(self, coordinates: list[list[TRIPLE]]) -> list[LinearRingZ]: ... @property - def rings(self) -> List[LinearRingZ]: ... + def rings(self) -> list[LinearRingZ]: ... @property def is_empty(self) -> bool: ... @property @@ -184,13 +183,13 @@ class PolygonM(BasePolygon): """ Polygon M """ - def __init__(self, coordinates: List[List[TRIPLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[TRIPLE]], srs_id: int) -> None: ... def __eq__(self, other: 'PolygonM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[TRIPLE]]]]: ... - def _make_rings(self, coordinates: List[List[TRIPLE]]) -> List[LinearRingM]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[TRIPLE]]]]: ... + def _make_rings(self, coordinates: list[list[TRIPLE]]) -> list[LinearRingM]: ... @property - def rings(self) -> List[LinearRingM]: ... + def rings(self) -> list[LinearRingM]: ... @property def is_empty(self) -> bool: ... @property @@ -205,13 +204,13 @@ class PolygonZM(BasePolygon): """ Polygon ZM """ - def __init__(self, coordinates: List[List[QUADRUPLE]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[QUADRUPLE]], srs_id: int) -> None: ... def __eq__(self, other: 'PolygonZM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[QUADRUPLE]]]]: ... - def _make_rings(self, coordinates: List[List[QUADRUPLE]]) -> List[LinearRingZM]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[QUADRUPLE]]]]: ... + def _make_rings(self, coordinates: list[list[QUADRUPLE]]) -> list[LinearRingZM]: ... @property - def rings(self) -> List[LinearRingZM]: ... + def rings(self) -> list[LinearRingZM]: ... @property def is_empty(self) -> bool: ... @property @@ -230,15 +229,15 @@ class BaseMultiPolygon(AbstractGeometry): _dimension: ClassVar[int] _env_code: ClassVar[int] _wkb_prefix: ClassVar[bytes] - _polygons: List + _polygons: list - def __init__(self, coordinates: List[List[List]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[list]], srs_id: int) -> None: ... def __eq__(self, other: Any) -> bool: ... @property - def __geo_interface__(self) -> Dict: ... - def _make_polygons(self, coordinates: List[List[List]]) -> List: ... + def __geo_interface__(self) -> dict: ... + def _make_polygons(self, coordinates: list[list[list]]) -> list: ... @property - def polygons(self) -> List: ... + def polygons(self) -> list: ... @property def is_empty(self) -> bool: ... @property @@ -253,13 +252,13 @@ class MultiPolygon(BaseMultiPolygon): """ Multi Polygon """ - def __init__(self, coordinates: List[List[List[DOUBLE]]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[list[DOUBLE]]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPolygon') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[Tuple[DOUBLE]]]]]: ... - def _make_polygons(self, coordinates: List[List[List[DOUBLE]]]) -> List[Polygon]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[tuple[DOUBLE]]]]]: ... + def _make_polygons(self, coordinates: list[list[list[DOUBLE]]]) -> list[Polygon]: ... @property - def polygons(self) -> List[Polygon]: ... + def polygons(self) -> list[Polygon]: ... @property def is_empty(self) -> bool: ... @property @@ -271,13 +270,13 @@ class MultiPolygon(BaseMultiPolygon): class MultiPolygonZ(BaseMultiPolygon): - def __init__(self, coordinates: List[List[List[TRIPLE]]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[list[TRIPLE]]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPolygonZ') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[Tuple[TRIPLE]]]]]: ... - def _make_polygons(self, coordinates: List[List[List[TRIPLE]]]) -> List[PolygonZ]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[tuple[TRIPLE]]]]]: ... + def _make_polygons(self, coordinates: list[list[list[TRIPLE]]]) -> list[PolygonZ]: ... @property - def polygons(self) -> List[PolygonZ]: ... + def polygons(self) -> list[PolygonZ]: ... @property def is_empty(self) -> bool: ... @property @@ -292,13 +291,13 @@ class MultiPolygonM(BaseMultiPolygon): """ Multi Polygon M """ - def __init__(self, coordinates: List[List[List[TRIPLE]]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[list[TRIPLE]]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPolygonM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[Tuple[TRIPLE]]]]]: ... - def _make_polygons(self, coordinates: List[List[List[TRIPLE]]]) -> List[PolygonM]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[tuple[TRIPLE]]]]]: ... + def _make_polygons(self, coordinates: list[list[list[TRIPLE]]]) -> list[PolygonM]: ... @property - def polygons(self) -> List[PolygonM]: ... + def polygons(self) -> list[PolygonM]: ... @property def is_empty(self) -> bool: ... @property @@ -313,13 +312,13 @@ class MultiPolygonZM(BaseMultiPolygon): """ Multi Polygon ZM """ - def __init__(self, coordinates: List[List[List[QUADRUPLE]]], srs_id: int) -> None: ... + def __init__(self, coordinates: list[list[list[QUADRUPLE]]], srs_id: int) -> None: ... def __eq__(self, other: 'MultiPolygonZM') -> bool: ... @property - def __geo_interface__(self) -> Dict[str, Union[str, Tuple[Tuple[Tuple[QUADRUPLE]]]]]: ... - def _make_polygons(self, coordinates: List[List[List[QUADRUPLE]]]) -> List[PolygonZM]: ... + def __geo_interface__(self) -> dict[str, Union[str, tuple[tuple[tuple[QUADRUPLE]]]]]: ... + def _make_polygons(self, coordinates: list[list[list[QUADRUPLE]]]) -> list[PolygonZM]: ... @property - def polygons(self) -> List[PolygonZM]: ... + def polygons(self) -> list[PolygonZM]: ... @property def is_empty(self) -> bool: ... @property diff --git a/fudgeo/geometry/util.py b/fudgeo/geometry/util.py index ae4a89b..74dad1d 100644 --- a/fudgeo/geometry/util.py +++ b/fudgeo/geometry/util.py @@ -8,31 +8,17 @@ from math import nan # noinspection PyPep8Naming from struct import error as StructError, pack, unpack -from typing import Any, Callable, Dict, List, TYPE_CHECKING, Tuple, Union +from typing import Any, Callable, Union from numpy import array, frombuffer, ndarray from bottleneck import nanmax, nanmin +from fudgeo.alias import GEOMS, GEOMS_M, GEOMS_Z, GEOMS_ZM from fudgeo.constant import ( COUNT_CODE, EMPTY, ENVELOPE_COUNT, ENVELOPE_OFFSET, GP_MAGIC, HEADER_CODE, HEADER_OFFSET, POINT_PREFIX_ZM) from fudgeo.enumeration import EnvelopeCode -if TYPE_CHECKING: # pragma: no cover - # noinspection PyUnresolvedReferences - from fudgeo.geometry.linestring import ( - LineString, LineStringZ, LineStringM, LineStringZM) - # noinspection PyUnresolvedReferences - from fudgeo.geometry.polygon import ( - LinearRing, LinearRingZ, LinearRingM, LinearRingZM, - Polygon, PolygonZ, PolygonM, PolygonZM) - - -GEOMS = Union[List['LineString'], List['LinearRing'], List['Polygon']] -GEOMS_Z = Union[List['LineStringZ'], List['LinearRingZ'], List['PolygonZ']] -GEOMS_M = Union[List['LineStringM'], List['LinearRingM'], List['PolygonM']] -GEOMS_ZM = Union[List['LineStringZM'], List['LinearRingZM'], List['PolygonZM']] - def as_array(coordinates: Any) -> ndarray: """ @@ -107,14 +93,14 @@ def __eq__(self, other: 'Envelope') -> bool: # End eq built-in @property - def bounding_box(self) -> Tuple[float, float, float, float]: + def bounding_box(self) -> tuple[float, float, float, float]: """ Bounding Box """ return self.min_x, self.min_y, self.max_x, self.max_y # End bounding_box property - def to_wkb(self) -> Tuple[int, bytes]: + def to_wkb(self) -> tuple[int, bytes]: """ To WKB """ @@ -281,7 +267,7 @@ def pack_coordinates(ary: bytearray, prefix: bytes, coordinates: ndarray, def unpack_lines(view: memoryview, dimension: int, is_ring: bool = False) \ - -> List[ndarray]: + -> list[ndarray]: """ Unpack Values for Multi LineString and Polygons """ @@ -300,7 +286,7 @@ def unpack_lines(view: memoryview, dimension: int, is_ring: bool = False) \ def unpack_polygons(view: memoryview, dimension: int) \ - -> List[List[ndarray]]: + -> list[list[ndarray]]: """ Unpack Values for Multi Polygon Type Containing Polygons """ @@ -317,7 +303,7 @@ def unpack_polygons(view: memoryview, dimension: int) \ def get_count_and_data(view: memoryview, is_ring: bool = False) \ - -> Tuple[int, memoryview]: + -> tuple[int, memoryview]: """ Get Count from header and return the value portion of the stream """ @@ -342,7 +328,7 @@ def make_header(srs_id: int, is_empty: bool, envelope_code: int = 0) -> bytes: @lru_cache(maxsize=None) -def unpack_header(view: Union[bytes, memoryview]) -> Tuple[int, int, int, bool]: +def unpack_header(view: Union[bytes, memoryview]) -> tuple[int, int, int, bool]: """ Cached Unpacking of a GeoPackage Geometry Header """ @@ -553,7 +539,7 @@ def _envelope_xyzm(xs: ndarray, ys: ndarray, # End _envelope_xyzm function -ENV_GEOM: Dict[int, Callable] = { +ENV_GEOM: dict[int, Callable[[Union[GEOMS, GEOMS_Z, GEOMS_M, GEOMS_ZM]], Envelope]] = { EnvelopeCode.empty: lambda _: EMPTY_ENVELOPE, EnvelopeCode.xy: envelope_from_geometries, EnvelopeCode.xyz: envelope_from_geometries_z, @@ -562,7 +548,7 @@ def _envelope_xyzm(xs: ndarray, ys: ndarray, } -ENV_COORD: Dict[int, Callable] = { +ENV_COORD: dict[int, Callable[[ndarray], Envelope]] = { EnvelopeCode.empty: lambda _: EMPTY_ENVELOPE, EnvelopeCode.xy: envelope_from_coordinates, EnvelopeCode.xyz: envelope_from_coordinates_z, diff --git a/fudgeo/geopkg.py b/fudgeo/geopkg.py index d41f427..66e17c8 100644 --- a/fudgeo/geopkg.py +++ b/fudgeo/geopkg.py @@ -10,8 +10,9 @@ from sqlite3 import ( PARSE_COLNAMES, PARSE_DECLTYPES, connect, register_adapter, register_converter) -from typing import Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union +from typing import Optional, TYPE_CHECKING, Type, Union +from fudgeo.alias import FIELDS, INT, STRING from fudgeo.constant import COMMA_SPACE, GPKG_EXT, SHAPE from fudgeo.enumeration import DataType, GPKGFlavors, GeometryType, SQLFieldType from fudgeo.extension.metadata import ( @@ -42,9 +43,6 @@ from fudgeo.geometry.base import AbstractGeometry -FIELDS = Union[Tuple['Field', ...], List['Field']] - - def _adapt_geometry(val: 'AbstractGeometry') -> bytes: """ Adapt Geometry to Geopackage @@ -260,7 +258,7 @@ def create_table(self, name: str, fields: FIELDS = (), # End create_feature_class method @property - def tables(self) -> Dict[str, 'Table']: + def tables(self) -> dict[str, 'Table']: """ Tables in the GeoPackage """ @@ -269,7 +267,7 @@ def tables(self) -> Dict[str, 'Table']: # End tables property @property - def feature_classes(self) -> Dict[str, 'FeatureClass']: + def feature_classes(self) -> dict[str, 'FeatureClass']: """ Feature Classes in the GeoPackage """ @@ -278,7 +276,7 @@ def feature_classes(self) -> Dict[str, 'FeatureClass']: # End feature_classes property def _get_table_objects(self, cls: Type['BaseTable'], - data_type: str) -> Dict[str, 'BaseTable']: + data_type: str) -> dict[str, 'BaseTable']: """ Get Table Objects """ @@ -381,7 +379,7 @@ def primary_key_field(self) -> Optional['Field']: # End primary_key_field property @property - def fields(self) -> List['Field']: + def fields(self) -> list['Field']: """ Fields """ @@ -392,7 +390,7 @@ def fields(self) -> List['Field']: # End fields property @property - def field_names(self) -> List[str]: + def field_names(self) -> list[str]: """ Field Names """ @@ -538,7 +536,7 @@ def drop(self) -> None: # End drop method @staticmethod - def _check_result(cursor: 'Cursor') -> Optional[str]: + def _check_result(cursor: 'Cursor') -> STRING: """ Check Result """ @@ -552,7 +550,7 @@ def _check_result(cursor: 'Cursor') -> Optional[str]: # End _check_result method @property - def geometry_column_name(self) -> Optional[str]: + def geometry_column_name(self) -> STRING: """ Geometry Column Name """ @@ -562,7 +560,7 @@ def geometry_column_name(self) -> Optional[str]: # End geometry_column_name property @property - def geometry_type(self) -> Optional[str]: + def geometry_type(self) -> STRING: """ Geometry Type """ @@ -603,7 +601,7 @@ def has_m(self) -> bool: # End has_m property @property - def spatial_index_name(self) -> Optional[str]: + def spatial_index_name(self) -> STRING: """ Spatial Index Name (escaped) if present, None otherwise """ @@ -631,7 +629,7 @@ def has_spatial_index(self) -> bool: # End has_spatial_index property @property - def extent(self) -> Tuple[float, float, float, float]: + def extent(self) -> tuple[float, float, float, float]: """ Extent property """ @@ -645,7 +643,7 @@ def extent(self) -> Tuple[float, float, float, float]: return result @extent.setter - def extent(self, value: Tuple[float, float, float, float]) -> None: + def extent(self, value: tuple[float, float, float, float]) -> None: if not isinstance(value, (tuple, list)): # pragma: nocover raise ValueError('Please supply a tuple or list of values') if not len(value) == 4: # pragma: nocover @@ -662,7 +660,7 @@ class SpatialReferenceSystem: """ def __init__(self, name: str, organization: str, org_coord_sys_id: int, definition: str, description: str = '', - srs_id: Optional[int] = None) -> None: + srs_id: INT = None) -> None: """ Initialize the SpatialReferenceSystem class """ @@ -687,7 +685,7 @@ def srs_id(self, value: int) -> None: self._srs_id = value # End srs_id property - def as_record(self) -> Tuple[str, int, str, int, str, str]: + def as_record(self) -> tuple[str, int, str, int, str, str]: """ Record of the Spatial Reference System """ @@ -701,15 +699,14 @@ class Field: """ Field Object for GeoPackage """ - def __init__(self, name: str, data_type: str, - size: Optional[int] = None) -> None: + def __init__(self, name: str, data_type: str, size: INT = None) -> None: """ Initialize the Field class """ super().__init__() self.name: str = name self.data_type: str = data_type - self.size: Optional[int] = size + self.size: INT = size # End init built-in @property diff --git a/fudgeo/sql.py b/fudgeo/sql.py index 429da0c..5af76ad 100644 --- a/fudgeo/sql.py +++ b/fudgeo/sql.py @@ -4,10 +4,8 @@ """ -from typing import Set, Tuple - # NOTE from https://sqlite.org/lang_keywords.html -KEYWORDS: Set[str] = { +KEYWORDS: set[str] = { 'ABORT', 'ACTION', 'ADD', 'AFTER', 'ALL', 'ALTER', 'ALWAYS', 'ANALYZE', 'AND', 'AS', 'ASC', 'ATTACH', 'AUTOINCREMENT', 'BEFORE', 'BEGIN', 'BETWEEN', 'BY', 'CASCADE', 'CASE', 'CAST', 'CHECK', 'COLLATE', 'COLUMN', 'COMMIT', @@ -257,7 +255,7 @@ """ -DEFAULT_SRS_RECS: Tuple[Tuple[str, int, str, int, str, str], ...] = ( +DEFAULT_SRS_RECS: tuple[tuple[str, int, str, int, str, str], ...] = ( ('Undefined Cartesian SRS', -1, 'NONE', -1, 'undefined', 'undefined cartesian coordinate reference system'), ('Undefined Geographic SRS', 0, 'NONE', 0, 'undefined', @@ -268,9 +266,9 @@ ESRI_4326: str = """GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]""" -DEFAULT_EPSG_RECS: Tuple[Tuple[str, int, str, int, str, str], ...] = ( +DEFAULT_EPSG_RECS: tuple[tuple[str, int, str, int, str, str], ...] = ( DEFAULT_SRS_RECS + (('WGS 84', 4326, 'EPSG', 4326, EPSG_4326, ''),)) -DEFAULT_ESRI_RECS: Tuple[Tuple[str, int, str, int, str, str], ...] = ( +DEFAULT_ESRI_RECS: tuple[tuple[str, int, str, int, str, str], ...] = ( DEFAULT_SRS_RECS + (('GCS_WGS_1984', 4326, 'EPSG', 4326, ESRI_4326, ''),)) @@ -386,7 +384,7 @@ """ -SPATIAL_INDEX_RECORD: Tuple[str, str, str] = ( +SPATIAL_INDEX_RECORD: tuple[str, str, str] = ( 'gpkg_rtree_index', f'{ROOT}#extension_rtree', 'write-only') @@ -442,7 +440,7 @@ """ -METADATA_RECORDS: Tuple[Tuple[str, None, str, str, str], ...] = ( +METADATA_RECORDS: tuple[tuple[str, None, str, str, str], ...] = ( ('gpkg_metadata', None, 'gpkg_metadata', f'{ROOT}#extension_metadata', 'read-write'), ('gpkg_metadata_reference', None, 'gpkg_metadata', @@ -508,7 +506,7 @@ """ -SCHEMA_RECORDS: Tuple[Tuple[str, None, str, str, str], ...] = ( +SCHEMA_RECORDS: tuple[tuple[str, None, str, str, str], ...] = ( ('gpkg_data_columns', None, 'gpkg_schema', f'{ROOT}#extension_schema', 'read-write'), ('gpkg_data_column_constraints', None, 'gpkg_schema', diff --git a/fudgeo/util.py b/fudgeo/util.py index b91cf5a..3c38661 100644 --- a/fudgeo/util.py +++ b/fudgeo/util.py @@ -6,12 +6,13 @@ from datetime import datetime, timedelta, timezone from re import IGNORECASE, compile as recompile -from typing import Callable +from typing import Callable, Match, Optional from fudgeo.sql import KEYWORDS -NAME_MATCHER: Callable = recompile(r'^[A-Z]\w*$', IGNORECASE).match +NAME_MATCHER: Callable[[str], Optional[Match[str]]] = ( + recompile(r'^[A-Z]\w*$', IGNORECASE).match) def escape_name(name: str) -> str: From c585811b4c93afffdd33c20aeb270e5ee27fd0f3 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Fri, 28 Jun 2024 12:31:10 -0500 Subject: [PATCH 02/17] #75 drop 3.7 and 3.8, bump version number --- pyproject.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ce421ac..bb93da9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fudgeo" -version = "0.7.2" +version = "0.8.0" description = """\ GeoPackage support from Python. fudgeo is a lightweight package \ for creating OGC GeoPackages, Feature Classes, and Tables. Easily \ @@ -13,8 +13,6 @@ license = { file = "LICENSE" } classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -24,7 +22,7 @@ classifiers = [ ] keywords = ["geopackage"] dependencies = ["numpy", "Bottleneck"] -requires-python = ">=3.7" +requires-python = ">=3.9" [tool.setuptools] packages = ["fudgeo", "fudgeo.geometry", "fudgeo.extension"] From 07c403515db3fbd1f0bcefa49bdc5054a8badce7 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Fri, 28 Jun 2024 12:41:53 -0500 Subject: [PATCH 03/17] #75 remove typing imports and modernize type hints --- README.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index c2e0de9..014e67f 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,6 @@ either of these options are enabled, the geometry inserted into the Feature Class **must** include a value for the option specified. ```python -from typing import Tuple from fudgeo.enumeration import GeometryType, SQLFieldType from fudgeo.geopkg import FeatureClass, Field, GeoPackage, SpatialReferenceSystem @@ -99,7 +98,7 @@ SRS_WKT: str = ( SRS: SpatialReferenceSystem = SpatialReferenceSystem( name='WGS_1984_UTM_Zone_23N', organization='EPSG', org_coord_sys_id=32623, definition=SRS_WKT) -fields: Tuple[Field, ...] = ( +fields: tuple[Field, ...] = ( Field('road_id', SQLFieldType.integer), Field('name', SQLFieldType.text, size=100), Field('begin_easting', SQLFieldType.double), @@ -137,13 +136,12 @@ portion of the code is omitted... ```python from random import choice, randint from string import ascii_uppercase, digits -from typing import List, Tuple from fudgeo.geometry import LineStringM from fudgeo.geopkg import GeoPackage # Generate some random points and attributes -rows: List[Tuple[LineStringM, int, str, float, float, float, float, bool]] = [] +rows: list[tuple[LineStringM, int, str, float, float, float, float, bool]] = [] for i in range(10000): name = ''.join(choice(ascii_uppercase + digits) for _ in range(10)) road_id = randint(0, 1000) @@ -171,20 +169,19 @@ of this package. ```python -from typing import List, Tuple from fudgeo.geometry import LineStringZM, Point, Polygon # Point in WGS 84 pt: Point = Point(x=-119, y=34) # Line with ZM Values for use with UTM Zone 23N (WGS 84) -coords: List[Tuple[float, float, float, float]] = [ +coords: list[tuple[float, float, float, float]] = [ (300000, 1, 10, 0), (300000, 4000000, 20, 1000), (700000, 4000000, 30, 2000), (700000, 1, 40, 3000)] line: LineStringZM = LineStringZM(coords, srs_id=32623) # list of rings where a ring is simply the list of points it contains. -rings: List[List[Tuple[float, float]]] = [ +rings: list[list[tuple[float, float]]] = [ [(300000, 1), (300000, 4000000), (700000, 4000000), (700000, 1), (300000, 1)]] poly: Polygon = Polygon(rings, srs_id=32623) ``` @@ -209,21 +206,19 @@ then the approach is to ensure `SQLite` knows how to convert the geopackage stored geometry to a `fudgeo` geometry, this is done like so: ```python -from typing import List, Tuple from fudgeo.geometry import LineStringM from fudgeo.geopkg import GeoPackage gpkg: GeoPackage = GeoPackage('../data/example.gpkg') cursor = gpkg.connection.execute( """SELECT SHAPE "[LineStringM]", road_id FROM test""") -features: List[Tuple[LineStringM, int]] = cursor.fetchall() +features: list[tuple[LineStringM, int]] = cursor.fetchall() ``` or a little more general, accounting for extended geometry types and possibility of the geometry column being something other tha `SHAPE`: ```python -from typing import List, Tuple from fudgeo.geometry import LineStringM from fudgeo.geopkg import FeatureClass, GeoPackage @@ -232,7 +227,7 @@ fc: FeatureClass = FeatureClass(geopackage=gpkg, name='road_l') cursor = gpkg.connection.execute(f""" SELECT {fc.geometry_column_name} "[{fc.geometry_type}]", road_id FROM {fc.escaped_name}""") -features: List[Tuple[LineStringM, int]] = cursor.fetchall() +features: list[tuple[LineStringM, int]] = cursor.fetchall() ``` @@ -245,7 +240,6 @@ Spatial Indexes apply to individual feature classes. A spatial index can be added at create time or added on an existing feature class. ```python -from typing import Tuple from fudgeo.enumeration import SQLFieldType from fudgeo.geopkg import FeatureClass, Field, GeoPackage, SpatialReferenceSystem @@ -267,7 +261,7 @@ SRS_WKT: str = ( SRS: SpatialReferenceSystem = SpatialReferenceSystem( name='WGS_1984_UTM_Zone_23N', organization='EPSG', org_coord_sys_id=32623, definition=SRS_WKT) -fields: Tuple[Field, ...] = ( +fields: tuple[Field, ...] = ( Field('id', SQLFieldType.integer), Field('name', SQLFieldType.text, size=100)) From a0f8210387c510d18ce5f6800b65599d299980dd Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Fri, 28 Jun 2024 12:42:08 -0500 Subject: [PATCH 04/17] #75 add notes for next release --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 014e67f..ecad68c 100644 --- a/README.md +++ b/README.md @@ -416,6 +416,11 @@ Support provided for the following constraint types: ## Release History +### next release +* drop support for Python 3.7 and 3.8 +* modernize type hinting +* add `select` method to `FeatureClass` and `Table` objects + ### v0.7.2 * bump `user_version` to reflect adopted version 1.4.0 of OGC GeoPackage * updated r-tree triggers based on changes made in 1.4.0 From 8103d3e8327a8c2b15a2eaf29df388a99a783334 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Sun, 7 Jul 2024 10:35:40 -0300 Subject: [PATCH 05/17] #76 update year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0f560e3..1daef57 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2023 Integrated Informatics Inc +Copyright (c) 2021-2024 Integrated Informatics Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From ddfbf929dcb224a2715670a25bb777e4b0b300e7 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Sun, 7 Jul 2024 10:35:50 -0300 Subject: [PATCH 06/17] #76 version bump --- fudgeo/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fudgeo/__init__.py b/fudgeo/__init__.py index 239578b..8416532 100644 --- a/fudgeo/__init__.py +++ b/fudgeo/__init__.py @@ -4,7 +4,7 @@ """ -__version__ = '0.7.2' +__version__ = '0.8.0' if __name__ == '__main__': From 808553e6bfd2c834768bde2deea456d9d2a08fbc Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Sun, 7 Jul 2024 10:47:59 -0300 Subject: [PATCH 07/17] #76 ignore --- fudgeo/extension/metadata.py | 1 + fudgeo/geometry/point.py | 1 + 2 files changed, 2 insertions(+) diff --git a/fudgeo/extension/metadata.py b/fudgeo/extension/metadata.py index 7d6d45d..9a19357 100644 --- a/fudgeo/extension/metadata.py +++ b/fudgeo/extension/metadata.py @@ -108,6 +108,7 @@ def _validate_row_id(row_id: int, table: TABLE) -> None: """ Validate Row ID """ + # noinspection SqlNoDataSourceInspection cursor = table.geopackage.connection.execute(f""" SELECT COUNT(1) AS C FROM {table.escaped_name} diff --git a/fudgeo/geometry/point.py b/fudgeo/geometry/point.py index 704209d..44cbb0f 100644 --- a/fudgeo/geometry/point.py +++ b/fudgeo/geometry/point.py @@ -450,6 +450,7 @@ def to_gpkg(self) -> bytes: """ To Geopackage """ + # noinspection PyArgumentEqualDefault return (make_header(srs_id=self.srs_id, is_empty=self.is_empty) + self._to_wkb(None)) # End to_gpkg method From 5b87bc009bcce63b95100ff30e110aacff98cfdb Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Sun, 7 Jul 2024 10:48:08 -0300 Subject: [PATCH 08/17] #76 remove extra blank line --- fudgeo/extension/ogr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fudgeo/extension/ogr.py b/fudgeo/extension/ogr.py index cffd54b..504cbc5 100644 --- a/fudgeo/extension/ogr.py +++ b/fudgeo/extension/ogr.py @@ -40,4 +40,3 @@ def add_ogr_contents(conn: 'Connection', name: str, escaped_name: str) -> None: if __name__ == '__main__': # pragma: no cover pass - From c8c8fa565e731ba781a24da47b5730536d5ed461 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 10:50:02 -0230 Subject: [PATCH 09/17] #76 pragmas --- fudgeo/__init__.py | 2 +- fudgeo/alias.py | 2 +- fudgeo/extension/__init__.py | 2 +- fudgeo/geometry/base.py | 4 ++-- fudgeo/geometry/point.py | 2 +- fudgeo/geometry/polygon.py | 2 +- fudgeo/geometry/util.py | 2 +- fudgeo/geopkg.py | 2 +- fudgeo/util.py | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fudgeo/__init__.py b/fudgeo/__init__.py index 8416532..bc76da2 100644 --- a/fudgeo/__init__.py +++ b/fudgeo/__init__.py @@ -7,5 +7,5 @@ __version__ = '0.8.0' -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover pass diff --git a/fudgeo/alias.py b/fudgeo/alias.py index 182bfb1..b438b24 100644 --- a/fudgeo/alias.py +++ b/fudgeo/alias.py @@ -8,7 +8,7 @@ from typing import Optional, TYPE_CHECKING, Union -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover # noinspection PyUnresolvedReferences from fudgeo.geopkg import FeatureClass, Field, Table # noinspection PyUnresolvedReferences diff --git a/fudgeo/extension/__init__.py b/fudgeo/extension/__init__.py index d00ac8b..dc7f557 100644 --- a/fudgeo/extension/__init__.py +++ b/fudgeo/extension/__init__.py @@ -4,5 +4,5 @@ """ -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover pass diff --git a/fudgeo/geometry/base.py b/fudgeo/geometry/base.py index c4b49c1..908527b 100644 --- a/fudgeo/geometry/base.py +++ b/fudgeo/geometry/base.py @@ -49,7 +49,7 @@ def _join_geometries(ary: bytearray, @property @abstractmethod - def is_empty(self) -> bool: + def is_empty(self) -> bool: # pragma: no cover """ Is Empty """ @@ -58,7 +58,7 @@ def is_empty(self) -> bool: @property @abstractmethod - def envelope(self) -> Envelope: + def envelope(self) -> Envelope: # pragma: no cover """ Envelope """ diff --git a/fudgeo/geometry/point.py b/fudgeo/geometry/point.py index 44cbb0f..e1587e7 100644 --- a/fudgeo/geometry/point.py +++ b/fudgeo/geometry/point.py @@ -22,7 +22,7 @@ pack_coordinates, unpack_header, unpack_points) -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from numpy import ndarray from fudgeo.geometry.util import Envelope diff --git a/fudgeo/geometry/polygon.py b/fudgeo/geometry/polygon.py index f64a833..671a5fa 100644 --- a/fudgeo/geometry/polygon.py +++ b/fudgeo/geometry/polygon.py @@ -19,7 +19,7 @@ pack_coordinates, unpack_lines, unpack_polygons) -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from numpy import ndarray from fudgeo.geometry.util import Envelope diff --git a/fudgeo/geometry/util.py b/fudgeo/geometry/util.py index 74dad1d..9bbd591 100644 --- a/fudgeo/geometry/util.py +++ b/fudgeo/geometry/util.py @@ -71,7 +71,7 @@ def __eq__(self, other: 'Envelope') -> bool: """ Equality """ - if not isinstance(other, Envelope): + if not isinstance(other, Envelope): # pragma: no cover return NotImplemented code = self.code if code != other.code: diff --git a/fudgeo/geopkg.py b/fudgeo/geopkg.py index 66e17c8..dc2415b 100644 --- a/fudgeo/geopkg.py +++ b/fudgeo/geopkg.py @@ -38,7 +38,7 @@ from fudgeo.util import convert_datetime, escape_name, now -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from sqlite3 import Connection, Cursor from fudgeo.geometry.base import AbstractGeometry diff --git a/fudgeo/util.py b/fudgeo/util.py index 3c38661..0a8c0c4 100644 --- a/fudgeo/util.py +++ b/fudgeo/util.py @@ -48,7 +48,7 @@ def convert_datetime(val: bytes) -> datetime: break except ValueError: pass - else: + else: # pragma: no cover raise Exception(f"Could not split datetime: '{val}'") year, month, day = map(int, dt.split(dash)) tm, *micro = tm.split(b'.') From 16547629d063f66f4f16e15ab9dcd4d59e49135e Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 10:50:10 -0230 Subject: [PATCH 10/17] #76 new alias --- fudgeo/alias.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fudgeo/alias.py b/fudgeo/alias.py index b438b24..7cb5466 100644 --- a/fudgeo/alias.py +++ b/fudgeo/alias.py @@ -37,6 +37,7 @@ TABLE = Union['Table', 'FeatureClass'] FIELDS = Union[tuple['Field', ...], list['Field']] +FIELD_NAMES = Union[tuple[str, ...], list[str]] REFERENCE_RECORD = tuple[ From e14a76f6f3bc5b8a74d0ea5b8395ca2a9d038c51 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 12:16:06 -0230 Subject: [PATCH 11/17] #76 pragma --- fudgeo/geopkg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fudgeo/geopkg.py b/fudgeo/geopkg.py index dc2415b..c48b40b 100644 --- a/fudgeo/geopkg.py +++ b/fudgeo/geopkg.py @@ -373,7 +373,7 @@ def primary_key_field(self) -> Optional['Field']: cursor = self.geopackage.connection.execute( SELECT_PRIMARY_KEY.format(self.name, SQLFieldType.integer)) result = cursor.fetchone() - if not result: + if not result: # pragma: no cover return return Field(*result) # End primary_key_field property From 03683ae1558fe0fbc1cb988de4c203496bd5f14c Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 12:16:32 -0230 Subject: [PATCH 12/17] #76 implement equality for Field --- fudgeo/geopkg.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/fudgeo/geopkg.py b/fudgeo/geopkg.py index c48b40b..da487da 100644 --- a/fudgeo/geopkg.py +++ b/fudgeo/geopkg.py @@ -709,14 +709,6 @@ def __init__(self, name: str, data_type: str, size: INT = None) -> None: self.size: INT = size # End init built-in - @property - def escaped_name(self) -> str: - """ - Escaped Name, only adds quotes if needed - """ - return escape_name(self.name) - # End escaped_name property - def __repr__(self) -> str: """ String representation @@ -726,6 +718,23 @@ def __repr__(self) -> str: return f'{self.escaped_name} {self.data_type}{self.size}' return f'{self.escaped_name} {self.data_type}' # End repr built-in + + def __eq__(self, other: 'Field') -> bool: + """ + Equality Implementation + """ + if not isinstance(other, Field): + return NotImplemented + return repr(self).casefold() == repr(other).casefold() + # End eq built-int + + @property + def escaped_name(self) -> str: + """ + Escaped Name, only adds quotes if needed + """ + return escape_name(self.name) + # End escaped_name property # End Field class From d51906eb78dbbb9a3d27b051fd45c1ac9dbc7abe Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 12:17:38 -0230 Subject: [PATCH 13/17] #76 select method implementation --- fudgeo/geopkg.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/fudgeo/geopkg.py b/fudgeo/geopkg.py index da487da..267c502 100644 --- a/fudgeo/geopkg.py +++ b/fudgeo/geopkg.py @@ -12,7 +12,7 @@ register_converter) from typing import Optional, TYPE_CHECKING, Type, Union -from fudgeo.alias import FIELDS, INT, STRING +from fudgeo.alias import FIELDS, FIELD_NAMES, INT, STRING from fudgeo.constant import COMMA_SPACE, GPKG_EXT, SHAPE from fudgeo.enumeration import DataType, GPKGFlavors, GeometryType, SQLFieldType from fudgeo.extension.metadata import ( @@ -396,6 +396,60 @@ def field_names(self) -> list[str]: """ return [f.name for f in self.fields] # End field_names property + + def _remove_special(self, fields: list['Field']) -> list['Field']: + """ + Remove Special Fields + """ + key_field = self.primary_key_field + if key_field: + while key_field in fields: + fields.remove(key_field) + return fields + # End _remove_special method + + def _validate_fields(self, fields: Union[FIELDS, FIELD_NAMES]) -> list['Field']: + """ + Validate Input Fields + """ + keepers = [] + field_lookup = {f.name.casefold(): f for f in self.fields} + if not isinstance(fields, (list, tuple)): + fields = [fields] + for f in fields: + if not isinstance(f, (Field, str)): + continue + name = getattr(f, 'name', f).casefold() + field = field_lookup.get(name) + if field is None: + continue + keepers.append(field) + return self._remove_special(keepers) + # End _validate_fields method + + def _execute_select(self, field_names: str, where_clause: str, + limit: INT) -> 'Cursor': + """ + Builds the SELECT statement and Executes, returning a cursor + """ + # noinspection SqlNoDataSourceInspection + sql = f"""SELECT {field_names} FROM {self.escaped_name}""" + if where_clause: + sql = f"""{sql} WHERE {where_clause}""" + if limit and limit > 0: + sql = f"""{sql} LIMIT {limit}""" + return self.geopackage.connection.execute(sql) + # End _execute_select method + + def _include_primary(self, fields: list['Field']) -> list['Field']: + """ + Include Primary Field + """ + key_field = self.primary_key_field + if key_field: + fields = [key_field, *fields] + return fields + # End _include_primary method # End BaseTable class @@ -446,6 +500,28 @@ def drop(self) -> None: delete_metadata=self.geopackage.is_metadata_enabled, delete_schema=self.geopackage.is_schema_enabled) # End drop method + + def select(self, fields: Union[FIELDS, FIELD_NAMES] = (), + where_clause: str = '', include_primary: bool = False, + limit: INT = None) -> 'Cursor': + """ + Builds a SELECT statement from fields, where clause, and options. + Returns a cursor for the SELECT statement. + + The fail-over SELECT statement will return ROWID, this happens when + no (valid) fields / field names provided and no primary key included + or there is no primary key. + """ + fields = self._validate_fields(fields) + if include_primary: + fields = self._include_primary(fields) + if not fields: + field_names = 'ROWID' + else: + field_names = COMMA_SPACE.join(f.escaped_name for f in fields) + return self._execute_select( + field_names=field_names, where_clause=where_clause, limit=limit) + # End select method # End Table class @@ -651,6 +727,44 @@ def extent(self, value: tuple[float, float, float, float]) -> None: with self.geopackage.connection as conn: conn.execute(UPDATE_EXTENT, tuple([*value, self.name])) # End extent property + + def _remove_special(self, fields: list['Field']) -> list['Field']: + """ + Remove Special Fields + """ + fields = super()._remove_special(fields) + geom_name = (self.geometry_column_name or '').casefold() + if geom_name: + fields = [f for f in fields if f.name.casefold() != geom_name] + return fields + # End _remove_special method + + def select(self, fields: Union[FIELDS, FIELD_NAMES] = (), + where_clause: str = '', include_geometry: bool = True, + include_primary: bool = False, limit: INT = None) -> 'Cursor': + """ + Builds a SELECT statement from fields, where clause, and options. + Returns a cursor for the SELECT statement. + + The fail-over SELECT statement will return ROWID, this happens when + no (valid) fields / field names provided, no primary key included + or there is no primary key, and no geometry included. + """ + fields = self._validate_fields(fields) + if include_primary: + fields = self._include_primary(fields) + field_names = COMMA_SPACE.join(f.escaped_name for f in fields) + if include_geometry: + geom = f'{self.geometry_column_name} "[{self.geometry_type}]"' + if field_names: + field_names = f'{geom}{COMMA_SPACE}{field_names}' + else: + field_names = geom + if not field_names: + field_names = 'ROWID' + return self._execute_select( + field_names=field_names, where_clause=where_clause, limit=limit) + # End select method # End FeatureClass class From 1196e4066bf1f1b96dd0391c460e0276746e7f76 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 12:17:56 -0230 Subject: [PATCH 14/17] #76 add tests for validation and select, use the select method --- tests/test_geopkg.py | 178 +++++++++++++++++++++++++++++++++---------- 1 file changed, 136 insertions(+), 42 deletions(-) diff --git a/tests/test_geopkg.py b/tests/test_geopkg.py index 7d7252e..9226e40 100644 --- a/tests/test_geopkg.py +++ b/tests/test_geopkg.py @@ -5,7 +5,7 @@ import sys -from datetime import datetime, timedelta, timezone +from datetime import datetime, timedelta from math import isnan from random import randint, choice from string import ascii_uppercase, digits @@ -34,8 +34,6 @@ SELECT_RTREE = """SELECT * FROM rtree_{0}_{1} ORDER BY 1""" # noinspection SqlNoDataSourceInspection INSERT_SHAPE = """INSERT INTO {} (SHAPE) VALUES (?)""" -# noinspection SqlNoDataSourceInspection -SELECT_FID_SHAPE = """SELECT fid, SHAPE "[{}]" FROM {}""" def random_points_and_attrs(count, srs_id): @@ -119,12 +117,12 @@ def test_create_table(tmp_path, fields, name, ogr_contents, trigger_count): assert table.count == 2 *_, eee, select = fields # noinspection SqlNoDataSourceInspection - cursor = conn.execute(f"""SELECT {eee.name} FROM {table.escaped_name}""") + cursor = table.select(fields=eee) value, = cursor.fetchone() assert isinstance(value, datetime) assert value == eee_datetime # noinspection SqlNoDataSourceInspection - cursor = conn.execute(f"""SELECT {select.escaped_name} FROM {table.escaped_name}""") + cursor = table.select(fields=select) value, = cursor.fetchone() assert isinstance(value, datetime) assert value == fff_datetime @@ -376,9 +374,8 @@ def test_insert_point_rows(setup_geopackage, name, add_index): with gpkg.connection as conn: conn.executemany(INSERT_ROWS.format(fc.escaped_name), rows) assert fc.count == count - conn = gpkg.connection # noinspection SqlNoDataSourceInspection - cursor = conn.execute(f"""SELECT {SHAPE} FROM {fc.escaped_name} LIMIT 10""") + cursor = fc.select(limit=10) points = [rec[0] for rec in cursor.fetchall()] assert all([isinstance(pt, Point) for pt in points]) assert all(pt.srs_id == srs.srs_id for pt in points) @@ -394,10 +391,10 @@ def test_insert_point_rows(setup_geopackage, name, add_index): # End test_insert_point_rows function -def _insert_shape_and_fetch(gpkg, geom, name): +def _insert_shape_and_fetch(gpkg, geom, fc): with gpkg.connection as conn: - conn.execute(INSERT_SHAPE.format(name), (geom,)) - cursor = conn.execute(SELECT_FID_SHAPE.format(geom.__class__.__name__, name)) + conn.execute(INSERT_SHAPE.format(fc.escaped_name), (geom,)) + cursor = fc.select(include_primary=True) return cursor.fetchall() @@ -419,9 +416,9 @@ def test_insert_poly(setup_geopackage, rings, add_index): spatial_index=add_index) assert fc.has_spatial_index is add_index geom = Polygon(rings, srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, poly = result[0] + poly, _ = result[0] assert isinstance(poly, Polygon) assert poly == geom cursor = gpkg.connection.execute( @@ -451,9 +448,9 @@ def test_insert_multi_poly(setup_geopackage, add_index): [[(100000, 1000000), (100000, 2000000), (200000, 2000000), (200000, 1000000), (100000, 1000000)]]] geom = MultiPolygon(polys, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, poly = result[0] + poly, _ = result[0] assert isinstance(poly, MultiPolygon) assert poly == geom cursor = gpkg.connection.execute( @@ -480,9 +477,9 @@ def test_insert_lines(setup_geopackage, add_index): assert fc.has_spatial_index is add_index coords = [(300000, 1), (300000, 4000000), (700000, 4000000), (700000, 1)] geom = LineString(coords, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, line = result[0] + line, _ = result[0] assert isinstance(line, LineString) assert line == geom cursor = gpkg.connection.execute( @@ -509,11 +506,11 @@ def test_insert_multi_point(setup_geopackage, add_index): assert fc.has_spatial_index is add_index multipoints = [(300000, 1), (700000, 4000000)] geom = MultiPoint(multipoints, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, line = result[0] - assert isinstance(line, MultiPoint) - assert line == geom + pts, _ = result[0] + assert isinstance(pts, MultiPoint) + assert pts == geom cursor = gpkg.connection.execute( SELECT_ST_FUNCS.format(fc.geometry_column_name, fc.escaped_name)) assert cursor.fetchall() == [(0, 300000, 700000, 1, 4000000)] @@ -539,9 +536,9 @@ def test_insert_lines_z(setup_geopackage, add_index): coords = [(300000, 1, 10), (300000, 4000000, 20), (700000, 4000000, 30), (700000, 1, 40)] geom = LineStringZ(coords, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, line = result[0] + line, _ = result[0] assert isinstance(line, LineStringZ) assert line == geom cursor = gpkg.connection.execute( @@ -569,9 +566,9 @@ def test_insert_lines_m(setup_geopackage, add_index): coords = [(300000, 1, 10), (300000, 4000000, 20), (700000, 4000000, 30), (700000, 1, 40)] geom = LineStringM(coords, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, line = result[0] + line, _ = result[0] assert isinstance(line, LineStringM) assert line == geom cursor = gpkg.connection.execute( @@ -599,9 +596,9 @@ def test_insert_lines_zm(setup_geopackage, add_index): coords = [(300000, 1, 10, 0), (300000, 4000000, 20, 1000), (700000, 4000000, 30, 2000), (700000, 1, 40, 3000)] geom = LineStringZM(coords, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, line = result[0] + line, _ = result[0] assert isinstance(line, LineStringZM) assert line == geom cursor = gpkg.connection.execute( @@ -625,17 +622,17 @@ def test_insert_multi_lines(setup_geopackage, add_index): fc = gpkg.create_feature_class( 'SELECT', srs, fields=fields, shape_type=GeometryType.multi_linestring, - z_enabled=True, m_enabled=True, spatial_index=add_index) + z_enabled=False, m_enabled=False, spatial_index=add_index) assert fc.has_spatial_index is add_index coords = [[(300000, 1), (300000, 4000000), (700000, 4000000), (700000, 1)], [(600000, 100000), (600000, 3900000), (400000, 3900000), (400000, 100000)]] geom = MultiLineString(coords, srs_id=srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, line = result[0] - assert isinstance(line, MultiLineString) - assert line == geom + lines, _ = result[0] + assert isinstance(lines, MultiLineString) + assert lines == geom cursor = gpkg.connection.execute( SELECT_ST_FUNCS.format(fc.geometry_column_name, fc.escaped_name)) assert cursor.fetchall() == [(0, 300000, 700000, 1, 4000000)] @@ -658,12 +655,12 @@ def test_insert_polygon_m(setup_geopackage, add_index): _, gpkg, srs, fields = setup_geopackage fc = gpkg.create_feature_class( 'SELECT', srs, fields=fields, shape_type=GeometryType.polygon, - spatial_index=add_index) + spatial_index=add_index, m_enabled=True) assert fc.has_spatial_index is add_index geom = PolygonM(rings, srs.srs_id) - result = _insert_shape_and_fetch(gpkg, geom, fc.escaped_name) + result = _insert_shape_and_fetch(gpkg, geom, fc) assert len(result) == 1 - _, poly = result[0] + poly, _ = result[0] assert isinstance(poly, PolygonM) assert poly == geom cursor = gpkg.connection.execute( @@ -726,10 +723,7 @@ def test_escaped_columns(setup_geopackage): {example_dot.escaped_name}) VALUES (?, ?, ?, ?, ?)""", rows) # noinspection SqlNoDataSourceInspection - cursor = conn.execute( - f"""SELECT {select.escaped_name}, {union.escaped_name}, - {all_.escaped_name}, {example_dot.escaped_name} - FROM {fc.name}""") + cursor = fc.select(fields=(select, union, all_, example_dot), include_geometry=False) records = cursor.fetchall() assert len(records) == 2 assert records[0] == (1, 'asdf', 'lmnop', ';;::;;;') @@ -762,10 +756,7 @@ def test_escaped_table(setup_geopackage): {union.escaped_name}, {all_.escaped_name}) VALUES (?, ?, ?, ?)""", rows) # noinspection SqlNoDataSourceInspection - cursor = conn.execute( - f"""SELECT {select.escaped_name}, {union.escaped_name}, - {all_.escaped_name} - FROM {fc.escaped_name}""") + cursor = fc.select(fields=(select, union, all_), include_geometry=False) records = cursor.fetchall() assert len(records) == 2 assert records[0] == (1, 'asdf', 'lmnop') @@ -776,6 +767,109 @@ def test_escaped_table(setup_geopackage): # End test_escaped_table function +def test_validate_fields_feature_class(setup_geopackage): + """ + Test validate fields on feature class + """ + _, gpkg, srs, _ = setup_geopackage + name = 'VALIDATE_FIELDS_FC' + a = Field('a', SQLFieldType.integer) + b = Field('b', SQLFieldType.text, 20) + c = Field('c', SQLFieldType.text, 50) + fields = a, b, c + fc = gpkg.create_feature_class(name=name, srs=srs, fields=fields) + expected_names = ['fid', SHAPE, a.name, b.name, c.name] + assert fc.field_names == expected_names + fields = fc._validate_fields(fields=expected_names) + assert [f.name for f in fields] == [a.name, b.name, c.name] + fields = fc._validate_fields(fields=[n.upper() for n in expected_names]) + assert [f.name for f in fields] == [a.name, b.name, c.name] + assert not fc._validate_fields(fields='d') + assert not fc._validate_fields(fields=1234) + assert not fc._validate_fields(fields=()) + assert not fc._validate_fields(fields=(None, Ellipsis, 1234, object)) +# End test_validate_fields_feature_class function + + +@mark.parametrize('names, include_primary, include_geometry, where_clause, expected', [ + (('a', 'b', 'c'), False, True, '', ('', 'a', 'b', 'c')), + (('a', 'b', 'c'), True, True, '', ('', 'fid', 'a', 'b', 'c')), + ((), False, True, '', ('',)), + ((), True, True, '', ('', 'fid')), + (('a', 'b', 'c'), False, True, 'a = 10', ('', 'a', 'b', 'c')), + (('a', 'b', 'c'), True, True, "a = 20 AND c = 'asdf'", ('', 'fid', 'a', 'b', 'c')), + (('a', 'b', 'c'), False, False, '', ('a', 'b', 'c')), + (('a', 'b', 'c'), True, False, '', ('fid', 'a', 'b', 'c')), + ((), False, False, '', ('fid',)), + ((), True, False, '', ('fid',)), + (('a', 'b', 'c'), False, False, 'a = 10', ('a', 'b', 'c')), + (('a', 'b', 'c'), True, False, "a = 20 AND c = 'asdf'", ('fid', 'a', 'b', 'c')), +]) +def test_select_feature_class(setup_geopackage, names, include_primary, include_geometry, where_clause, expected): + """ + Test select method on feature class + """ + _, gpkg, srs, _ = setup_geopackage + name = 'SELECT_FC' + a = Field('a', SQLFieldType.integer) + b = Field('b', SQLFieldType.text, 20) + c = Field('c', SQLFieldType.text, 50) + fields = a, b, c + fc = gpkg.create_feature_class(name=name, srs=srs, fields=fields) + cursor = fc.select(fields=names, include_primary=include_primary, + include_geometry=include_geometry, where_clause=where_clause) + assert tuple(name for name, *_ in cursor.description) == expected + assert not cursor.fetchall() +# End test_select_feature_class function + + +def test_validate_fields_table(setup_geopackage): + """ + Test validate fields method on table class + """ + _, gpkg, _, _ = setup_geopackage + name = 'VALIDATE_FIELDS_TABLE' + a = Field('a', SQLFieldType.integer) + b = Field('b', SQLFieldType.text, 20) + c = Field('c', SQLFieldType.text, 50) + tbl = gpkg.create_table(name=name, fields=(a, b, c)) + expected_names = ['fid', a.name, b.name, c.name] + assert tbl.field_names == expected_names + fields = tbl._validate_fields(fields=expected_names) + assert [f.name for f in fields] == [a.name, b.name, c.name] + fields = tbl._validate_fields(fields=[n.upper() for n in expected_names]) + assert [f.name for f in fields] == [a.name, b.name, c.name] + assert not tbl._validate_fields(fields='d') + assert not tbl._validate_fields(fields=1234) + assert not tbl._validate_fields(fields=()) + assert not tbl._validate_fields(fields=(None, Ellipsis, 1234, object)) +# End test_validate_fields_table function + + +@mark.parametrize('names, include, where_clause, expected', [ + (('a', 'b', 'c'), False, '', ('a', 'b', 'c')), + (('a', 'b', 'c'), True, '', ('fid', 'a', 'b', 'c')), + ((), False, '', ('fid',)), + ((), True, '', ('fid',)), + (('a', 'b', 'c'), False, 'a = 10', ('a', 'b', 'c')), + (('a', 'b', 'c'), True, "a = 20 AND c = 'asdf'", ('fid', 'a', 'b', 'c')), +]) +def test_select_table(setup_geopackage, names, include, where_clause, expected): + """ + Test select method on table class + """ + _, gpkg, _, _ = setup_geopackage + name = 'SELECT_TABLE' + a = Field('a', SQLFieldType.integer) + b = Field('b', SQLFieldType.text, 20) + c = Field('c', SQLFieldType.text, 50) + tbl = gpkg.create_table(name=name, fields=(a, b, c)) + cursor = tbl.select(fields=names, include_primary=include, where_clause=where_clause) + assert tuple(name for name, *_ in cursor.description) == expected + assert not cursor.fetchall() +# End test_select_table function + + @mark.skipif(sys.platform != 'darwin', reason='will be different on windows') def test_representation(): """ From 6d110b5a58ef9e990a5b06a429cb346a13fab319 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 12:31:27 -0230 Subject: [PATCH 15/17] #76 update readme examples --- README.md | 55 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ecad68c..5f251b7 100644 --- a/README.md +++ b/README.md @@ -186,47 +186,48 @@ rings: list[list[tuple[float, float]]] = [ poly: Polygon = Polygon(rings, srs_id=32623) ``` -### Select Features from GeoPackage (SQL) +### Select Features from GeoPackage -When selecting features from a GeoPackage feature class use SQL. For -the most part (mainly simple geometries e.g. those without *Z* or *M*) this -can be done via a basic `SELECT` statement like: +When selecting features from a GeoPackage feature class use SQL or use the +helper method `select`. + +For simple geometries (e.g. those without *Z* or *M*) this can be done via a +basic `SELECT` statement or the `select` method. ```python +from fudgeo.geometry import Point +from fudgeo.geopkg import FeatureClass, GeoPackage + gpkg = GeoPackage(...) + +# NOTE for fudgeo version v0.8.0 and above use helper method +fc = FeatureClass(geopackage=gpkg, name='point_fc') +cursor = fc.select(fields=('example_id',), include_geometry=True) +features: list[tuple[Point, int]] = cursor.fetchall() + +# NOTE for fudgeo prior to v0.8.0 cursor = gpkg.connection.execute("""SELECT SHAPE, example_id FROM point_fc""") -features = cursor.fetchall() +features: list[tuple[Point, int]] = cursor.fetchall() ``` -This will return a list of tuples where each tuple contains a `Point` -object and an integer for `example_id` field. +When using SQL with extended geometry types (e.g. those with *Z* and/or *M*) +then ensure `SQLite` knows how to convert the geopackage stored geometry to a +`fudgeo` geometry by including the converter, this is done like so: -When working with extended geometry types (those with *Z* and/or *M*) -then the approach is to ensure `SQLite` knows how to convert the -geopackage stored geometry to a `fudgeo` geometry, this is done like so: ```python from fudgeo.geometry import LineStringM -from fudgeo.geopkg import GeoPackage +from fudgeo.geopkg import FeatureClass, GeoPackage -gpkg: GeoPackage = GeoPackage('../data/example.gpkg') -cursor = gpkg.connection.execute( - """SELECT SHAPE "[LineStringM]", road_id FROM test""") +gpkg = GeoPackage('../data/example.gpkg') +# NOTE for fudgeo version v0.8.0 and above use helper method +fc = FeatureClass(geopackage=gpkg, name='test') +cursor = fc.select(fields=('road_id',), include_geometry=True) features: list[tuple[LineStringM, int]] = cursor.fetchall() -``` - -or a little more general, accounting for extended geometry types and -possibility of the geometry column being something other tha `SHAPE`: -```python -from fudgeo.geometry import LineStringM -from fudgeo.geopkg import FeatureClass, GeoPackage - -gpkg: GeoPackage = GeoPackage('../data/example.gpkg') -fc: FeatureClass = FeatureClass(geopackage=gpkg, name='road_l') -cursor = gpkg.connection.execute(f""" - SELECT {fc.geometry_column_name} "[{fc.geometry_type}]", road_id - FROM {fc.escaped_name}""") +# NOTE for fudgeo prior to v0.8.0 +cursor = gpkg.connection.execute( + """SELECT SHAPE "[LineStringM]", road_id FROM test""") features: list[tuple[LineStringM, int]] = cursor.fetchall() ``` From a3bcc74e5ff7bec9e6988699d16bb2dd4d4c6d65 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 15:03:01 -0230 Subject: [PATCH 16/17] #77 update skip condition --- tests/geometry/test_geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/geometry/test_geometry.py b/tests/geometry/test_geometry.py index 0f8e91d..aa0ffd0 100644 --- a/tests/geometry/test_geometry.py +++ b/tests/geometry/test_geometry.py @@ -11,7 +11,7 @@ from fudgeo.geometry import LineString, Point, Polygon -@mark.skipif(version_info[:2] < (3, 11), reason='threshold based on 3.11') +@mark.skipif(version_info[:2] != (3, 11), reason='threshold based on 3.11') @mark.parametrize('scale, geom_type, expected', [ (1, Point, 0.025), (1, LineString, 0.0025), From f3c410eae514c1e7e261fd7b56a37776c1905503 Mon Sep 17 00:00:00 2001 From: Jason Humber Date: Tue, 9 Jul 2024 15:17:23 -0230 Subject: [PATCH 17/17] #77 update version in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f251b7..1449cfb 100644 --- a/README.md +++ b/README.md @@ -417,7 +417,7 @@ Support provided for the following constraint types: ## Release History -### next release +### v0.8.0 * drop support for Python 3.7 and 3.8 * modernize type hinting * add `select` method to `FeatureClass` and `Table` objects