Skip to content

Commit

Permalink
Merge branch 'feature/fudgeo-82' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jlhumber committed Oct 19, 2024
2 parents d500afb + 85ad24d commit 159e0cc
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 26 deletions.
29 changes: 17 additions & 12 deletions fudgeo/geopkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
SELECT_COUNT, SELECT_EXTENT, SELECT_GEOMETRY_COLUMN, SELECT_GEOMETRY_TYPE,
SELECT_HAS_ZM, SELECT_PRIMARY_KEY, SELECT_SRS, SELECT_TABLES_BY_TYPE,
TABLE_EXISTS, UPDATE_EXTENT)
from fudgeo.util import convert_datetime, escape_name, now
from fudgeo.util import check_geometry_name, convert_datetime, escape_name, now


if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -234,7 +234,8 @@ def create_feature_class(self, name: str, srs: 'SpatialReferenceSystem',
z_enabled: bool = False, m_enabled: bool = False,
fields: FIELDS = (), description: str = '',
overwrite: bool = False,
spatial_index: bool = False) -> 'FeatureClass':
spatial_index: bool = False,
geom_name: str = SHAPE) -> 'FeatureClass':
"""
Creates a feature class in the GeoPackage per the options given.
"""
Expand All @@ -243,7 +244,7 @@ def create_feature_class(self, name: str, srs: 'SpatialReferenceSystem',
geopackage=self, name=name, shape_type=shape_type, srs=srs,
z_enabled=z_enabled, m_enabled=m_enabled, fields=fields,
description=description, overwrite=overwrite,
spatial_index=spatial_index)
spatial_index=spatial_index, geom_name=geom_name)
# End create_feature_class method

def create_table(self, name: str, fields: FIELDS = (),
Expand Down Expand Up @@ -566,29 +567,33 @@ def create(cls, geopackage: GeoPackage, name: str, shape_type: str,
srs: 'SpatialReferenceSystem', z_enabled: bool = False,
m_enabled: bool = False, fields: FIELDS = (),
description: str = '', overwrite: bool = False,
spatial_index: bool = False) -> 'FeatureClass':
spatial_index: bool = False,
geom_name: str = SHAPE) -> 'FeatureClass':
"""
Create Feature Class
"""
cols = cls._column_names(fields)
with geopackage.connection as conn:
escaped_name = escape_name(name)
geom_name = check_geometry_name(geom_name)
has_contents = has_ogr_contents(conn)
if overwrite:
geom_name = cls._find_geometry_column_name(geopackage, name)
cls._drop(conn=conn, sql=REMOVE_FEATURE_CLASS,
name=name, escaped_name=escaped_name,
geom_name=geom_name, delete_ogr_contents=has_contents,
delete_metadata=geopackage.is_metadata_enabled,
delete_schema=geopackage.is_schema_enabled)
current_name = cls._find_geometry_column_name(geopackage, name)
cls._drop(
conn=conn, sql=REMOVE_FEATURE_CLASS, name=name,
escaped_name=escaped_name, geom_name=current_name,
delete_ogr_contents=has_contents,
delete_metadata=geopackage.is_metadata_enabled,
delete_schema=geopackage.is_schema_enabled)
conn.execute(CREATE_FEATURE_TABLE.format(
name=escaped_name, feature_type=shape_type, other_fields=cols))
name=escaped_name, geom_name=geom_name,
feature_type=shape_type, other_fields=cols))
if not geopackage.check_srs_exists(srs.srs_id):
conn.execute(INSERT_GPKG_SRS, srs.as_record())
conn.execute(INSERT_GPKG_CONTENTS_SHORT, (
name, DataType.features, name, description, now(), srs.srs_id))
conn.execute(INSERT_GPKG_GEOM_COL,
(name, SHAPE, shape_type, srs.srs_id,
(name, geom_name, shape_type, srs.srs_id,
int(z_enabled), int(m_enabled)))
if has_contents:
add_ogr_contents(conn, name=name, escaped_name=escaped_name)
Expand Down
2 changes: 1 addition & 1 deletion fudgeo/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
CREATE_FEATURE_TABLE: str = """
CREATE TABLE {name} (
fid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
SHAPE {feature_type}{other_fields})
{geom_name} {feature_type}{other_fields})
"""


Expand Down
20 changes: 19 additions & 1 deletion fudgeo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,30 @@ def escape_name(name: str) -> str:
"""
Escape Name
"""
if name.upper() in KEYWORDS or not NAME_MATCHER(name):
if _is_invalid_name(name):
name = f'"{name}"'
return name
# End escape_name function


def _is_invalid_name(name: str) -> bool:
"""
Is invalid name
"""
return name.upper() in KEYWORDS or not NAME_MATCHER(name)
# End _is_invalid_name function


def check_geometry_name(name: str) -> str:
"""
Check geometry name
"""
if _is_invalid_name(name):
raise ValueError(f'Invalid field name for geometry column: {name}')
return name
# End check_geometry_name function


def now() -> str:
"""
Formatted Now
Expand Down
6 changes: 3 additions & 3 deletions tests/extension/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ def example1(tmp_path, random_utm_coordinates) -> GeoPackage:
records.append((line, 12.3))
with pkg.connection as conn:
conn.executemany(f"""
INSERT INTO {roads.name} (SHAPE, overhead_clearance)
INSERT INTO {roads.name} ({roads.geometry_column_name}, overhead_clearance)
VALUES (?, ?)""", records)
conn.executemany(f"""
INSERT INTO {bridge.name} (SHAPE, overhead_clearance)
INSERT INTO {bridge.name} ({bridge.geometry_column_name}, overhead_clearance)
VALUES (?, ?)""", records)
metadata = pkg.metadata
uri = 'https://www.isotc211.org/2005/gmd'
Expand Down Expand Up @@ -117,7 +117,7 @@ def example2(tmp_path, random_utm_coordinates) -> GeoPackage:
i // 100, easting + northing))
with pkg.connection as conn:
conn.executemany(f"""
INSERT INTO {poi.name} (SHAPE, category, point)
INSERT INTO {poi.name} ({poi.geometry_column_name}, category, point)
VALUES (?, ?, ?)""", records)
metadata = pkg.metadata
uri = 'https://schemas.opengis.net/iso/19139/'
Expand Down
58 changes: 49 additions & 9 deletions tests/test_geopkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,28 @@

from pytest import mark, raises

from fudgeo.constant import SHAPE
from fudgeo.enumeration import GeometryType, SQLFieldType
from fudgeo.geometry import (
LineString, LineStringM, LineStringZ, LineStringZM, MultiLineString,
MultiPoint, MultiPolygon, Point, Polygon, PolygonM)
from fudgeo.geopkg import (
FeatureClass, Field, GeoPackage, SpatialReferenceSystem, Table)
from fudgeo.constant import SHAPE
from fudgeo.extension.ogr import has_ogr_contents
from fudgeo.sql import SELECT_SRS
from tests.crs import WGS_1984_UTM_Zone_23N

# noinspection SqlNoDataSourceInspection
INSERT_ROWS = """
INSERT INTO {} (SHAPE, "int.fld", text_fld, test_fld_size, test_bool)
INSERT INTO {} ({}, "int.fld", text_fld, test_fld_size, test_bool)
VALUES (?, ?, ?, ?, ?)
"""
# noinspection SqlNoDataSourceInspection
SELECT_ST_FUNCS = """SELECT ST_IsEmpty({0}), ST_MinX({0}), ST_MaxX({0}), ST_MinY({0}), ST_MaxY({0}) FROM {1}"""
# noinspection SqlNoDataSourceInspection,SqlResolve
SELECT_RTREE = """SELECT * FROM rtree_{0}_{1} ORDER BY 1"""
# noinspection SqlNoDataSourceInspection
INSERT_SHAPE = """INSERT INTO {} (SHAPE) VALUES (?)"""
INSERT_SHAPE = """INSERT INTO {} ({}) VALUES (?)"""


def random_points_and_attrs(count, srs_id):
Expand Down Expand Up @@ -372,7 +372,7 @@ def test_insert_point_rows(setup_geopackage, name, add_index):
count = 10000
rows = random_points_and_attrs(count, srs.srs_id)
with gpkg.connection as conn:
conn.executemany(INSERT_ROWS.format(fc.escaped_name), rows)
conn.executemany(INSERT_ROWS.format(fc.escaped_name, fc.geometry_column_name), rows)
assert fc.count == count
# noinspection SqlNoDataSourceInspection
cursor = fc.select(limit=10)
Expand All @@ -385,15 +385,15 @@ def test_insert_point_rows(setup_geopackage, name, add_index):
if not add_index:
assert fc.add_spatial_index()
cursor = gpkg.connection.execute(f"""
SELECT COUNT(1) AS C FROM "rtree_{name}_{SHAPE}"
SELECT COUNT(1) AS C FROM "rtree_{name}_{fc.geometry_column_name}"
""")
assert cursor.fetchone() == (count,)
# End test_insert_point_rows function


def _insert_shape_and_fetch(gpkg, geom, fc):
with gpkg.connection as conn:
conn.execute(INSERT_SHAPE.format(fc.escaped_name), (geom,))
conn.execute(INSERT_SHAPE.format(fc.escaped_name, fc.geometry_column_name), (geom,))
cursor = fc.select(include_primary=True)
return cursor.fetchall()

Expand Down Expand Up @@ -639,7 +639,7 @@ def test_insert_and_update_lines_zm(setup_geopackage, add_index):
gpkg.connection.execute(f"""
UPDATE {fc.escaped_name}
SET {fc.geometry_column_name} = ?
WHERE {fc.primary_key_field.name} = ?""", (geom, primary))
WHERE {fc.primary_key_field.escaped_name} = ?""", (geom, primary))

cursor = fc.select(include_primary=True)
result = cursor.fetchall()
Expand All @@ -651,6 +651,46 @@ def test_insert_and_update_lines_zm(setup_geopackage, add_index):
# End test_insert_and_update_lines_zm function


@mark.parametrize('geom_name, add_index, is_error', [
('geom', False, False),
('geom', True, False),
('GeOmEtRy', False, False),
('GeOmEtRy', True, False),
('aa bb', False, True),
('cc-dd', True, True),
])
def test_non_standard_geom_name(setup_geopackage, geom_name, add_index, is_error):
"""
Test non-standard geometry column name
"""
_, gpkg, srs, fields = setup_geopackage
tbl = 'SELECT'
kwargs = dict(
name=tbl, srs=srs, fields=fields,
shape_type=GeometryType.linestring,
z_enabled=True, m_enabled=True, spatial_index=add_index,
geom_name=geom_name)
if is_error:
with raises(ValueError):
gpkg.create_feature_class(**kwargs)
return
fc = gpkg.create_feature_class(**kwargs)
assert fc.has_spatial_index is add_index
assert fc.geometry_column_name == geom_name
if add_index:
assert fc.spatial_index_name == f'rtree_{tbl}_{geom_name}'
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)
assert len(result) == 1
line, primary = result[0]
assert isinstance(line, LineStringZM)
assert line == geom
assert primary == 1
# End test_non_standard_geom_name function


@mark.parametrize('add_index', [
False, True
])
Expand Down Expand Up @@ -758,7 +798,7 @@ def test_escaped_columns(setup_geopackage):
with fc.geopackage.connection as conn:
# noinspection SqlNoDataSourceInspection
conn.executemany(
f"""INSERT INTO {fc.name} (SHAPE, {select.escaped_name},
f"""INSERT INTO {fc.name} ({fc.geometry_column_name}, {select.escaped_name},
{union.escaped_name}, {all_.escaped_name},
{example_dot.escaped_name})
VALUES (?, ?, ?, ?, ?)""", rows)
Expand Down Expand Up @@ -792,7 +832,7 @@ def test_escaped_table(setup_geopackage):
with fc.geopackage.connection as conn:
# noinspection SqlNoDataSourceInspection
conn.executemany(
f"""INSERT INTO {fc.escaped_name} (SHAPE, {select.escaped_name},
f"""INSERT INTO {fc.escaped_name} ({fc.geometry_column_name}, {select.escaped_name},
{union.escaped_name}, {all_.escaped_name})
VALUES (?, ?, ?, ?)""", rows)
# noinspection SqlNoDataSourceInspection
Expand Down

0 comments on commit 159e0cc

Please sign in to comment.