GeoAlchemy2 0.17.1__py3-none-any.whl → 0.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geoalchemy2/__init__.py +4 -24
- geoalchemy2/_functions.py +175 -158
- geoalchemy2/_functions_helpers.py +1 -0
- geoalchemy2/admin/dialects/common.py +22 -0
- geoalchemy2/admin/dialects/geopackage.py +36 -0
- geoalchemy2/admin/dialects/mariadb.py +27 -26
- geoalchemy2/admin/dialects/mysql.py +36 -8
- geoalchemy2/admin/dialects/postgresql.py +101 -31
- geoalchemy2/admin/dialects/sqlite.py +74 -5
- geoalchemy2/comparator.py +4 -4
- geoalchemy2/elements.py +72 -7
- geoalchemy2/functions.py +1 -1
- geoalchemy2/functions.pyi +666 -0
- geoalchemy2/types/__init__.py +54 -36
- geoalchemy2/types/dialects/mariadb.py +17 -2
- geoalchemy2/types/dialects/mysql.py +2 -2
- {GeoAlchemy2-0.17.1.dist-info → geoalchemy2-0.18.0.dist-info}/METADATA +5 -6
- geoalchemy2-0.18.0.dist-info/RECORD +35 -0
- {GeoAlchemy2-0.17.1.dist-info → geoalchemy2-0.18.0.dist-info}/WHEEL +1 -1
- GeoAlchemy2-0.17.1.dist-info/RECORD +0 -35
- {GeoAlchemy2-0.17.1.dist-info → geoalchemy2-0.18.0.dist-info}/entry_points.txt +0 -0
- {GeoAlchemy2-0.17.1.dist-info → geoalchemy2-0.18.0.dist-info/licenses}/COPYING.rst +0 -0
- {GeoAlchemy2-0.17.1.dist-info → geoalchemy2-0.18.0.dist-info}/top_level.txt +0 -0
@@ -48,6 +48,7 @@ def _generate_stubs() -> str:
|
|
48
48
|
# this file is automatically generated
|
49
49
|
from typing import List
|
50
50
|
|
51
|
+
import sqlalchemy
|
51
52
|
from sqlalchemy.sql import functions
|
52
53
|
from sqlalchemy.sql.elements import ColumnElement
|
53
54
|
from sqlalchemy.sql.selectable import FromClause
|
@@ -3,9 +3,11 @@
|
|
3
3
|
import sqlalchemy
|
4
4
|
from packaging import version
|
5
5
|
from sqlalchemy import Column
|
6
|
+
from sqlalchemy import String
|
6
7
|
from sqlalchemy.sql import expression
|
7
8
|
from sqlalchemy.types import TypeDecorator
|
8
9
|
|
10
|
+
from geoalchemy2.elements import WKBElement
|
9
11
|
from geoalchemy2.types import Geometry
|
10
12
|
|
11
13
|
_SQLALCHEMY_VERSION_BEFORE_14 = version.parse(sqlalchemy.__version__) < version.parse("1.4")
|
@@ -102,3 +104,23 @@ def before_drop(table, bind, **kw):
|
|
102
104
|
|
103
105
|
def after_drop(table, bind, **kw):
|
104
106
|
return # pragma: no cover
|
107
|
+
|
108
|
+
|
109
|
+
def compile_bin_literal(wkb_clause):
|
110
|
+
"""Compile a binary literal for WKBElement."""
|
111
|
+
wkb_data = wkb_clause.value
|
112
|
+
if isinstance(wkb_data, bytes | memoryview | WKBElement):
|
113
|
+
if isinstance(wkb_data, memoryview):
|
114
|
+
wkb_data = wkb_data.tobytes()
|
115
|
+
if isinstance(wkb_data, bytes):
|
116
|
+
wkb_data = WKBElement._wkb_to_hex(wkb_data)
|
117
|
+
elif isinstance(wkb_data, WKBElement):
|
118
|
+
wkb_data = wkb_data.desc
|
119
|
+
|
120
|
+
wkb_clause = expression.bindparam(
|
121
|
+
key=wkb_clause.key,
|
122
|
+
value=wkb_data,
|
123
|
+
type_=String(),
|
124
|
+
unique=True,
|
125
|
+
)
|
126
|
+
return wkb_clause
|
@@ -16,6 +16,7 @@ from geoalchemy2 import functions
|
|
16
16
|
from geoalchemy2.admin.dialects.common import _check_spatial_type
|
17
17
|
from geoalchemy2.admin.dialects.common import _format_select_args
|
18
18
|
from geoalchemy2.admin.dialects.common import _spatial_idx_name
|
19
|
+
from geoalchemy2.admin.dialects.common import compile_bin_literal
|
19
20
|
from geoalchemy2.admin.dialects.common import setup_create_drop
|
20
21
|
from geoalchemy2.admin.dialects.sqlite import _SQLITE_FUNCTIONS
|
21
22
|
from geoalchemy2.admin.dialects.sqlite import get_col_dim
|
@@ -383,3 +384,38 @@ def create_spatial_ref_sys_view(bind):
|
|
383
384
|
FROM gpkg_spatial_ref_sys;"""
|
384
385
|
)
|
385
386
|
)
|
387
|
+
|
388
|
+
|
389
|
+
def _compile_GeomFromWKB_gpkg(element, compiler, *, identifier, **kw):
|
390
|
+
# Store the SRID
|
391
|
+
clauses = list(element.clauses)
|
392
|
+
try:
|
393
|
+
srid = clauses[1].value
|
394
|
+
except (IndexError, TypeError, ValueError):
|
395
|
+
srid = element.type.srid
|
396
|
+
|
397
|
+
if kw.get("literal_binds", False):
|
398
|
+
wkb_clause = compile_bin_literal(clauses[0])
|
399
|
+
prefix = "unhex("
|
400
|
+
suffix = ")"
|
401
|
+
else:
|
402
|
+
wkb_clause = clauses[0]
|
403
|
+
prefix = ""
|
404
|
+
suffix = ""
|
405
|
+
|
406
|
+
compiled = compiler.process(wkb_clause, **kw)
|
407
|
+
|
408
|
+
if srid > 0:
|
409
|
+
return "{}({}{}{}, {})".format(identifier, prefix, compiled, suffix, srid)
|
410
|
+
else:
|
411
|
+
return "{}({}{}{})".format(identifier, prefix, compiled, suffix)
|
412
|
+
|
413
|
+
|
414
|
+
@compiles(functions.ST_GeomFromWKB, "geopackage") # type: ignore
|
415
|
+
def _gpkg_ST_GeomFromWKB(element, compiler, **kw):
|
416
|
+
return _compile_GeomFromWKB_gpkg(element, compiler, identifier="GeomFromWKB", **kw)
|
417
|
+
|
418
|
+
|
419
|
+
@compiles(functions.ST_GeomFromEWKB, "geopackage") # type: ignore
|
420
|
+
def _gpkg_ST_GeomFromEWKB(element, compiler, **kw):
|
421
|
+
return _compile_GeomFromWKB_gpkg(element, compiler, identifier="GeomFromEWKB", **kw)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
from sqlalchemy.ext.compiler import compiles
|
4
4
|
|
5
5
|
from geoalchemy2 import functions
|
6
|
+
from geoalchemy2.admin.dialects.common import compile_bin_literal
|
6
7
|
from geoalchemy2.admin.dialects.mysql import after_create # noqa
|
7
8
|
from geoalchemy2.admin.dialects.mysql import after_drop # noqa
|
8
9
|
from geoalchemy2.admin.dialects.mysql import before_create # noqa
|
@@ -10,15 +11,15 @@ from geoalchemy2.admin.dialects.mysql import before_drop # noqa
|
|
10
11
|
from geoalchemy2.admin.dialects.mysql import reflect_geometry_column # noqa
|
11
12
|
from geoalchemy2.elements import WKBElement
|
12
13
|
from geoalchemy2.elements import WKTElement
|
13
|
-
from geoalchemy2.shape import to_shape
|
14
14
|
|
15
15
|
|
16
16
|
def _cast(param):
|
17
17
|
if isinstance(param, memoryview):
|
18
18
|
param = param.tobytes()
|
19
19
|
if isinstance(param, bytes):
|
20
|
-
|
21
|
-
|
20
|
+
param = WKBElement(param)
|
21
|
+
if isinstance(param, WKBElement):
|
22
|
+
param = param.as_wkb().desc
|
22
23
|
return param
|
23
24
|
|
24
25
|
|
@@ -72,46 +73,46 @@ register_mariadb_mapping(_MARIADB_FUNCTIONS)
|
|
72
73
|
|
73
74
|
|
74
75
|
def _compile_GeomFromText_MariaDB(element, compiler, **kw):
|
75
|
-
|
76
|
+
identifier = "ST_GeomFromText"
|
76
77
|
compiled = compiler.process(element.clauses, **kw)
|
77
78
|
try:
|
78
79
|
clauses = list(element.clauses)
|
79
80
|
data_element = WKTElement(clauses[0].value)
|
80
|
-
srid =
|
81
|
+
srid = data_element.srid
|
81
82
|
if srid <= 0:
|
82
|
-
srid =
|
83
|
-
if len(clauses) > 1 and srid > 0:
|
84
|
-
clauses[1].value = srid
|
83
|
+
srid = element.type.srid
|
85
84
|
except Exception:
|
86
|
-
srid =
|
85
|
+
srid = element.type.srid
|
87
86
|
|
88
87
|
if srid > 0:
|
89
|
-
res = "{}({}, {})".format(
|
88
|
+
res = "{}({}, {})".format(identifier, compiled, srid)
|
90
89
|
else:
|
91
|
-
res = "{}({})".format(
|
90
|
+
res = "{}({})".format(identifier, compiled)
|
92
91
|
return res
|
93
92
|
|
94
93
|
|
95
94
|
def _compile_GeomFromWKB_MariaDB(element, compiler, **kw):
|
96
|
-
|
97
|
-
|
95
|
+
identifier = "ST_GeomFromWKB"
|
96
|
+
# Store the SRID
|
97
|
+
clauses = list(element.clauses)
|
98
98
|
try:
|
99
|
-
|
100
|
-
|
101
|
-
srid =
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
99
|
+
srid = clauses[1].value
|
100
|
+
except (IndexError, TypeError, ValueError):
|
101
|
+
srid = element.type.srid
|
102
|
+
|
103
|
+
if kw.get("literal_binds", False):
|
104
|
+
wkb_clause = compile_bin_literal(clauses[0])
|
105
|
+
else:
|
106
|
+
wkb_clause = clauses[0]
|
107
|
+
prefix = "unhex("
|
108
|
+
suffix = ")"
|
109
|
+
|
110
|
+
compiled = compiler.process(wkb_clause, **kw)
|
109
111
|
|
110
112
|
if srid > 0:
|
111
|
-
|
113
|
+
return "{}({}{}{}, {})".format(identifier, prefix, compiled, suffix, srid)
|
112
114
|
else:
|
113
|
-
|
114
|
-
return res
|
115
|
+
return "{}({}{}{})".format(identifier, prefix, compiled, suffix)
|
115
116
|
|
116
117
|
|
117
118
|
@compiles(functions.ST_GeomFromText, "mariadb") # type: ignore
|
@@ -1,16 +1,29 @@
|
|
1
1
|
"""This module defines specific functions for MySQL dialect."""
|
2
2
|
|
3
3
|
from sqlalchemy import text
|
4
|
+
from sqlalchemy.dialects.mysql.base import ischema_names as _mysql_ischema_names
|
4
5
|
from sqlalchemy.ext.compiler import compiles
|
5
6
|
from sqlalchemy.sql.sqltypes import NullType
|
6
7
|
|
7
8
|
from geoalchemy2 import functions
|
8
9
|
from geoalchemy2.admin.dialects.common import _check_spatial_type
|
9
10
|
from geoalchemy2.admin.dialects.common import _spatial_idx_name
|
11
|
+
from geoalchemy2.admin.dialects.common import compile_bin_literal
|
10
12
|
from geoalchemy2.admin.dialects.common import setup_create_drop
|
11
13
|
from geoalchemy2.types import Geography
|
12
14
|
from geoalchemy2.types import Geometry
|
13
15
|
|
16
|
+
# Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems.
|
17
|
+
_mysql_ischema_names["geometry"] = Geometry
|
18
|
+
_mysql_ischema_names["point"] = Geometry
|
19
|
+
_mysql_ischema_names["linestring"] = Geometry
|
20
|
+
_mysql_ischema_names["polygon"] = Geometry
|
21
|
+
_mysql_ischema_names["multipoint"] = Geometry
|
22
|
+
_mysql_ischema_names["multilinestring"] = Geometry
|
23
|
+
_mysql_ischema_names["multipolygon"] = Geometry
|
24
|
+
_mysql_ischema_names["geometrycollection"] = Geometry
|
25
|
+
|
26
|
+
|
14
27
|
_POSSIBLE_TYPES = [
|
15
28
|
"geometry",
|
16
29
|
"point",
|
@@ -124,6 +137,7 @@ def after_create(table, bind, **kw):
|
|
124
137
|
if (
|
125
138
|
_check_spatial_type(col.type, (Geometry, Geography), dialect)
|
126
139
|
and col.type.spatial_index is True
|
140
|
+
and col.computed is None
|
127
141
|
):
|
128
142
|
# If the index does not exist, define it and create it
|
129
143
|
if not [i for i in table.indexes if col in i.columns.values()]:
|
@@ -175,25 +189,39 @@ register_mysql_mapping(_MYSQL_FUNCTIONS)
|
|
175
189
|
|
176
190
|
|
177
191
|
def _compile_GeomFromText_MySql(element, compiler, **kw):
|
178
|
-
|
192
|
+
identifier = "ST_GeomFromText"
|
179
193
|
compiled = compiler.process(element.clauses, **kw)
|
180
194
|
srid = element.type.srid
|
181
195
|
|
182
196
|
if srid > 0:
|
183
|
-
return "{}({}, {})".format(
|
197
|
+
return "{}({}, {})".format(identifier, compiled, srid)
|
184
198
|
else:
|
185
|
-
return "{}({})".format(
|
199
|
+
return "{}({})".format(identifier, compiled)
|
186
200
|
|
187
201
|
|
188
202
|
def _compile_GeomFromWKB_MySql(element, compiler, **kw):
|
189
|
-
|
190
|
-
|
191
|
-
|
203
|
+
# Store the SRID
|
204
|
+
clauses = list(element.clauses)
|
205
|
+
try:
|
206
|
+
srid = clauses[1].value
|
207
|
+
except (IndexError, TypeError, ValueError):
|
208
|
+
srid = element.type.srid
|
209
|
+
|
210
|
+
if kw.get("literal_binds", False):
|
211
|
+
wkb_clause = compile_bin_literal(clauses[0])
|
212
|
+
prefix = "unhex("
|
213
|
+
suffix = ")"
|
214
|
+
else:
|
215
|
+
wkb_clause = clauses[0]
|
216
|
+
prefix = ""
|
217
|
+
suffix = ""
|
218
|
+
|
219
|
+
compiled = compiler.process(wkb_clause, **kw)
|
192
220
|
|
193
221
|
if srid > 0:
|
194
|
-
return "{}({}, {})".format(element.identifier, compiled, srid)
|
222
|
+
return "{}({}{}{}, {})".format(element.identifier, prefix, compiled, suffix, srid)
|
195
223
|
else:
|
196
|
-
return "{}({})".format(element.identifier, compiled)
|
224
|
+
return "{}({}{}{})".format(element.identifier, prefix, compiled, suffix)
|
197
225
|
|
198
226
|
|
199
227
|
@compiles(functions.ST_GeomFromText, "mysql") # type: ignore
|
@@ -1,20 +1,37 @@
|
|
1
1
|
"""This module defines specific functions for Postgresql dialect."""
|
2
2
|
|
3
|
+
import sqlalchemy
|
4
|
+
from packaging import version
|
3
5
|
from sqlalchemy import Index
|
4
6
|
from sqlalchemy import text
|
7
|
+
from sqlalchemy.dialects.postgresql.base import ischema_names as _postgresql_ischema_names
|
8
|
+
from sqlalchemy.ext.compiler import compiles
|
5
9
|
from sqlalchemy.sql import func
|
6
10
|
from sqlalchemy.sql import select
|
7
11
|
|
12
|
+
from geoalchemy2 import functions
|
8
13
|
from geoalchemy2.admin.dialects.common import _check_spatial_type
|
9
14
|
from geoalchemy2.admin.dialects.common import _format_select_args
|
10
15
|
from geoalchemy2.admin.dialects.common import _spatial_idx_name
|
16
|
+
from geoalchemy2.admin.dialects.common import compile_bin_literal
|
11
17
|
from geoalchemy2.admin.dialects.common import setup_create_drop
|
12
18
|
from geoalchemy2.types import Geography
|
13
19
|
from geoalchemy2.types import Geometry
|
20
|
+
from geoalchemy2.types import Raster
|
21
|
+
|
22
|
+
_SQLALCHEMY_VERSION_BEFORE_2 = version.parse(sqlalchemy.__version__) < version.parse("2")
|
23
|
+
|
24
|
+
# Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems.
|
25
|
+
_postgresql_ischema_names["geometry"] = Geometry
|
26
|
+
_postgresql_ischema_names["geography"] = Geography
|
27
|
+
_postgresql_ischema_names["raster"] = Raster
|
14
28
|
|
15
29
|
|
16
30
|
def check_management(column):
|
17
31
|
"""Check if the column should be managed."""
|
32
|
+
if _check_spatial_type(column.type, Raster):
|
33
|
+
# Raster columns are not managed
|
34
|
+
return _SQLALCHEMY_VERSION_BEFORE_2
|
18
35
|
return getattr(column.type, "use_typmod", None) is False
|
19
36
|
|
20
37
|
|
@@ -24,27 +41,34 @@ def create_spatial_index(bind, table, col):
|
|
24
41
|
postgresql_ops = {col.name: "gist_geometry_ops_nd"}
|
25
42
|
else:
|
26
43
|
postgresql_ops = {}
|
44
|
+
if _check_spatial_type(col.type, Raster):
|
45
|
+
col_func = func.ST_ConvexHull(col)
|
46
|
+
else:
|
47
|
+
col_func = col
|
27
48
|
idx = Index(
|
28
49
|
_spatial_idx_name(table.name, col.name),
|
29
|
-
|
50
|
+
col_func,
|
30
51
|
postgresql_using="gist",
|
31
52
|
postgresql_ops=postgresql_ops,
|
32
53
|
_column_flag=True,
|
33
54
|
)
|
34
|
-
|
55
|
+
if bind is not None:
|
56
|
+
idx.create(bind=bind)
|
57
|
+
return idx
|
35
58
|
|
36
59
|
|
37
60
|
def reflect_geometry_column(inspector, table, column_info):
|
38
61
|
"""Reflect a column of type Geometry with Postgresql dialect."""
|
39
|
-
if not
|
62
|
+
if not _check_spatial_type(column_info.get("type"), (Geometry, Geography, Raster)):
|
40
63
|
return
|
41
64
|
geo_type = column_info["type"]
|
42
65
|
geometry_type = geo_type.geometry_type
|
43
66
|
coord_dimension = geo_type.dimension
|
44
|
-
if geometry_type
|
45
|
-
|
46
|
-
|
47
|
-
|
67
|
+
if geometry_type is not None:
|
68
|
+
if geometry_type.endswith("ZM"):
|
69
|
+
coord_dimension = 4
|
70
|
+
elif geometry_type[-1] in ["Z", "M"]:
|
71
|
+
coord_dimension = 3
|
48
72
|
|
49
73
|
# Query to check a given column has spatial index
|
50
74
|
if table.schema is not None:
|
@@ -52,31 +76,39 @@ def reflect_geometry_column(inspector, table, column_info):
|
|
52
76
|
else:
|
53
77
|
schema_part = ""
|
54
78
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
79
|
+
# Check if the column has a spatial index (the regular expression checks for the column name
|
80
|
+
# in the index definition, which is required for functional indexes)
|
81
|
+
has_index_query = """SELECT EXISTS (
|
82
|
+
SELECT 1
|
83
|
+
FROM pg_class t
|
84
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
85
|
+
JOIN pg_index ix ON t.oid = ix.indrelid
|
86
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
87
|
+
JOIN pg_am am ON i.relam = am.oid
|
88
|
+
WHERE
|
89
|
+
t.relname = '{table_name}'{schema_part}
|
90
|
+
AND am.amname = 'gist'
|
91
|
+
AND (
|
92
|
+
EXISTS (
|
93
|
+
SELECT 1
|
94
|
+
FROM pg_attribute a
|
95
|
+
WHERE a.attrelid = t.oid
|
96
|
+
AND a.attnum = ANY(ix.indkey)
|
97
|
+
AND a.attname = '{col_name}'
|
98
|
+
)
|
99
|
+
OR pg_get_indexdef(
|
100
|
+
ix.indexrelid
|
101
|
+
) ~ '(^|[^a-zA-Z0-9_])("?{col_name}"?)($|[^a-zA-Z0-9_])'
|
102
|
+
)
|
103
|
+
);""".format(
|
104
|
+
table_name=table.name, col_name=column_info["name"], schema_part=schema_part
|
74
105
|
)
|
75
106
|
spatial_index = inspector.bind.execute(text(has_index_query)).scalar()
|
76
107
|
|
77
108
|
# Set attributes
|
78
|
-
column_info["type"]
|
79
|
-
|
109
|
+
if not _check_spatial_type(column_info["type"], Raster):
|
110
|
+
column_info["type"].geometry_type = geometry_type
|
111
|
+
column_info["type"].dimension = coord_dimension
|
80
112
|
column_info["type"].spatial_index = bool(spatial_index)
|
81
113
|
|
82
114
|
# Spatial indexes are automatically reflected with PostgreSQL dialect
|
@@ -95,7 +127,7 @@ def before_create(table, bind, **kw):
|
|
95
127
|
for idx in current_indexes:
|
96
128
|
for col in table.info["_saved_columns"]:
|
97
129
|
if (
|
98
|
-
_check_spatial_type(col.type, Geometry, dialect) and check_management(col)
|
130
|
+
_check_spatial_type(col.type, (Geometry, Raster), dialect) and check_management(col)
|
99
131
|
) and col in idx.columns.values():
|
100
132
|
table.indexes.remove(idx)
|
101
133
|
if idx.name != _spatial_idx_name(table.name, col.name) or not getattr(
|
@@ -124,9 +156,9 @@ def after_create(table, bind, **kw):
|
|
124
156
|
stmt = stmt.execution_options(autocommit=True)
|
125
157
|
bind.execute(stmt)
|
126
158
|
|
127
|
-
# Add spatial indices for the Geometry and
|
159
|
+
# Add spatial indices for the Geometry, Geography and Raster columns
|
128
160
|
if (
|
129
|
-
_check_spatial_type(col.type, (Geometry, Geography), dialect)
|
161
|
+
_check_spatial_type(col.type, (Geometry, Geography, Raster), dialect)
|
130
162
|
and col.type.spatial_index is True
|
131
163
|
):
|
132
164
|
# If the index does not exist, define it and create it
|
@@ -146,6 +178,9 @@ def before_drop(table, bind, **kw):
|
|
146
178
|
|
147
179
|
# Drop the managed Geometry columns
|
148
180
|
for col in gis_cols:
|
181
|
+
if _check_spatial_type(col.type, Raster):
|
182
|
+
# Raster columns are dropped with the table, no need to drop them separately
|
183
|
+
continue
|
149
184
|
args = [table.schema] if table.schema else []
|
150
185
|
args.extend([table.name, col.name])
|
151
186
|
|
@@ -160,3 +195,38 @@ def after_drop(table, bind, **kw):
|
|
160
195
|
saved_cols = table.info.pop("_saved_columns", None)
|
161
196
|
if saved_cols is not None:
|
162
197
|
table.columns = saved_cols
|
198
|
+
|
199
|
+
|
200
|
+
def _compile_GeomFromWKB_Postgresql(element, compiler, **kw):
|
201
|
+
# Store the SRID
|
202
|
+
clauses = list(element.clauses)
|
203
|
+
try:
|
204
|
+
srid = clauses[1].value
|
205
|
+
except (IndexError, TypeError, ValueError):
|
206
|
+
srid = element.type.srid
|
207
|
+
|
208
|
+
if kw.get("literal_binds", False):
|
209
|
+
wkb_clause = compile_bin_literal(clauses[0])
|
210
|
+
prefix = "decode("
|
211
|
+
suffix = ", 'hex')"
|
212
|
+
else:
|
213
|
+
wkb_clause = clauses[0]
|
214
|
+
prefix = ""
|
215
|
+
suffix = ""
|
216
|
+
|
217
|
+
compiled = compiler.process(wkb_clause, **kw)
|
218
|
+
|
219
|
+
if srid > 0:
|
220
|
+
return "{}({}{}{}, {})".format(element.identifier, prefix, compiled, suffix, srid)
|
221
|
+
else:
|
222
|
+
return "{}({}{}{})".format(element.identifier, prefix, compiled, suffix)
|
223
|
+
|
224
|
+
|
225
|
+
@compiles(functions.ST_GeomFromWKB, "postgresql") # type: ignore
|
226
|
+
def _PostgreSQL_ST_GeomFromWKB(element, compiler, **kw):
|
227
|
+
return _compile_GeomFromWKB_Postgresql(element, compiler, **kw)
|
228
|
+
|
229
|
+
|
230
|
+
@compiles(functions.ST_GeomFromEWKB, "postgresql") # type: ignore
|
231
|
+
def _PostgreSQL_ST_GeomFromEWKB(element, compiler, **kw):
|
232
|
+
return _compile_GeomFromWKB_Postgresql(element, compiler, **kw)
|
@@ -4,6 +4,7 @@ import os
|
|
4
4
|
from typing import Optional
|
5
5
|
|
6
6
|
from sqlalchemy import text
|
7
|
+
from sqlalchemy.dialects.sqlite.base import ischema_names as _sqlite_ischema_names
|
7
8
|
from sqlalchemy.ext.compiler import compiles
|
8
9
|
from sqlalchemy.sql import func
|
9
10
|
from sqlalchemy.sql import select
|
@@ -12,12 +13,26 @@ from geoalchemy2 import functions
|
|
12
13
|
from geoalchemy2.admin.dialects.common import _check_spatial_type
|
13
14
|
from geoalchemy2.admin.dialects.common import _format_select_args
|
14
15
|
from geoalchemy2.admin.dialects.common import _spatial_idx_name
|
16
|
+
from geoalchemy2.admin.dialects.common import compile_bin_literal
|
15
17
|
from geoalchemy2.admin.dialects.common import setup_create_drop
|
16
18
|
from geoalchemy2.types import Geography
|
17
19
|
from geoalchemy2.types import Geometry
|
20
|
+
from geoalchemy2.types import Raster
|
18
21
|
from geoalchemy2.types import _DummyGeometry
|
19
22
|
from geoalchemy2.utils import authorized_values_in_docstring
|
20
23
|
|
24
|
+
# Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems.
|
25
|
+
_sqlite_ischema_names["GEOMETRY"] = Geometry
|
26
|
+
_sqlite_ischema_names["POINT"] = Geometry
|
27
|
+
_sqlite_ischema_names["LINESTRING"] = Geometry
|
28
|
+
_sqlite_ischema_names["POLYGON"] = Geometry
|
29
|
+
_sqlite_ischema_names["MULTIPOINT"] = Geometry
|
30
|
+
_sqlite_ischema_names["MULTILINESTRING"] = Geometry
|
31
|
+
_sqlite_ischema_names["MULTIPOLYGON"] = Geometry
|
32
|
+
_sqlite_ischema_names["CURVE"] = Geometry
|
33
|
+
_sqlite_ischema_names["GEOMETRYCOLLECTION"] = Geometry
|
34
|
+
_sqlite_ischema_names["RASTER"] = Raster
|
35
|
+
|
21
36
|
|
22
37
|
def load_spatialite_driver(dbapi_conn, *args):
|
23
38
|
"""Load SpatiaLite extension in SQLite connection.
|
@@ -171,7 +186,7 @@ def get_col_dim(col):
|
|
171
186
|
"""Get dimension of the column type."""
|
172
187
|
if col.type.dimension == 4:
|
173
188
|
dimension = "XYZM"
|
174
|
-
elif col.type.dimension == 2:
|
189
|
+
elif col.type.dimension == 2 or col.type.geometry_type is None:
|
175
190
|
dimension = "XY"
|
176
191
|
else:
|
177
192
|
if col.type.geometry_type.endswith("M"):
|
@@ -183,6 +198,9 @@ def get_col_dim(col):
|
|
183
198
|
|
184
199
|
def create_spatial_index(bind, table, col):
|
185
200
|
"""Create spatial index on the given column."""
|
201
|
+
if col.computed is not None:
|
202
|
+
# Do not create spatial index for computed columns
|
203
|
+
return
|
186
204
|
stmt = select(*_format_select_args(func.CreateSpatialIndex(table.name, col.name)))
|
187
205
|
stmt = stmt.execution_options(autocommit=True)
|
188
206
|
bind.execute(stmt)
|
@@ -190,6 +208,10 @@ def create_spatial_index(bind, table, col):
|
|
190
208
|
|
191
209
|
def disable_spatial_index(bind, table, col):
|
192
210
|
"""Disable spatial indexes if present."""
|
211
|
+
if col.computed is not None:
|
212
|
+
# Do not disable spatial index for computed columns because it can not exist
|
213
|
+
return
|
214
|
+
# Check if the spatial index is enabled
|
193
215
|
stmt = select(*_format_select_args(func.CheckSpatialIndex(table.name, col.name)))
|
194
216
|
if bind.execute(stmt).fetchone()[0] is not None:
|
195
217
|
stmt = select(*_format_select_args(func.DisableSpatialIndex(table.name, col.name)))
|
@@ -276,7 +298,7 @@ def before_create(table, bind, **kw):
|
|
276
298
|
current_indexes = set(table.indexes)
|
277
299
|
for idx in current_indexes:
|
278
300
|
for col in table.info["_saved_columns"]:
|
279
|
-
if
|
301
|
+
if _check_spatial_type(col.type, Geometry, dialect) and col in idx.columns.values():
|
280
302
|
table.indexes.remove(idx)
|
281
303
|
if idx.name != _spatial_idx_name(table.name, col.name) or not getattr(
|
282
304
|
col.type, "spatial_index", False
|
@@ -293,11 +315,17 @@ def after_create(table, bind, **kw):
|
|
293
315
|
table.columns = table.info.pop("_saved_columns")
|
294
316
|
for col in table.columns:
|
295
317
|
# Add the managed Geometry columns with RecoverGeometryColumn()
|
296
|
-
if _check_spatial_type(col.type, Geometry, dialect):
|
318
|
+
if _check_spatial_type(col.type, Geometry, dialect) and col.computed is None:
|
297
319
|
col.type = col._actual_type
|
298
320
|
del col._actual_type
|
299
321
|
dimension = get_col_dim(col)
|
300
|
-
args = [
|
322
|
+
args = [
|
323
|
+
table.name,
|
324
|
+
col.name,
|
325
|
+
col.type.srid,
|
326
|
+
col.type.geometry_type or "GEOMETRY",
|
327
|
+
dimension,
|
328
|
+
]
|
301
329
|
|
302
330
|
stmt = select(*_format_select_args(func.RecoverGeometryColumn(*args)))
|
303
331
|
stmt = stmt.execution_options(autocommit=True)
|
@@ -322,6 +350,9 @@ def before_drop(table, bind, **kw):
|
|
322
350
|
dialect, gis_cols, regular_cols = setup_create_drop(table, bind)
|
323
351
|
|
324
352
|
for col in gis_cols:
|
353
|
+
if col.computed is not None:
|
354
|
+
# Computed columns are not managed
|
355
|
+
continue
|
325
356
|
# Disable spatial indexes if present
|
326
357
|
disable_spatial_index(bind, table, col)
|
327
358
|
|
@@ -341,7 +372,7 @@ def after_drop(table, bind, **kw):
|
|
341
372
|
# the ST_ prefix.
|
342
373
|
_SQLITE_FUNCTIONS = {
|
343
374
|
"ST_GeomFromEWKT": "GeomFromEWKT",
|
344
|
-
"ST_GeomFromEWKB": "GeomFromEWKB",
|
375
|
+
# "ST_GeomFromEWKB": "GeomFromEWKB",
|
345
376
|
"ST_AsBinary": "AsBinary",
|
346
377
|
"ST_AsEWKB": "AsEWKB",
|
347
378
|
"ST_AsGeoJSON": "AsGeoJSON",
|
@@ -372,3 +403,41 @@ def register_sqlite_mapping(mapping):
|
|
372
403
|
|
373
404
|
|
374
405
|
register_sqlite_mapping(_SQLITE_FUNCTIONS)
|
406
|
+
|
407
|
+
|
408
|
+
def _compile_GeomFromWKB_SQLite(element, compiler, *, identifier, **kw):
|
409
|
+
element.identifier = identifier
|
410
|
+
|
411
|
+
# Store the SRID
|
412
|
+
clauses = list(element.clauses)
|
413
|
+
try:
|
414
|
+
srid = clauses[1].value
|
415
|
+
element.type.srid = srid
|
416
|
+
except (IndexError, TypeError, ValueError):
|
417
|
+
srid = element.type.srid
|
418
|
+
|
419
|
+
if kw.get("literal_binds", False):
|
420
|
+
wkb_clause = compile_bin_literal(clauses[0])
|
421
|
+
prefix = "unhex("
|
422
|
+
suffix = ")"
|
423
|
+
else:
|
424
|
+
wkb_clause = clauses[0]
|
425
|
+
prefix = ""
|
426
|
+
suffix = ""
|
427
|
+
|
428
|
+
compiled = compiler.process(wkb_clause, **kw)
|
429
|
+
|
430
|
+
if srid > 0:
|
431
|
+
return "{}({}{}{}, {})".format(identifier, prefix, compiled, suffix, srid)
|
432
|
+
else:
|
433
|
+
return "{}({}{}{})".format(identifier, prefix, compiled, suffix)
|
434
|
+
|
435
|
+
|
436
|
+
@compiles(functions.ST_GeomFromWKB, "sqlite") # type: ignore
|
437
|
+
def _SQLite_ST_GeomFromWKB(element, compiler, **kw):
|
438
|
+
return _compile_GeomFromWKB_SQLite(element, compiler, identifier="GeomFromWKB", **kw)
|
439
|
+
|
440
|
+
|
441
|
+
@compiles(functions.ST_GeomFromEWKB, "sqlite") # type: ignore
|
442
|
+
def _SQLite_ST_GeomFromEWKB(element, compiler, **kw):
|
443
|
+
return _compile_GeomFromWKB_SQLite(element, compiler, identifier="GeomFromEWKB", **kw)
|
geoalchemy2/comparator.py
CHANGED
@@ -37,7 +37,7 @@ Using the ORM::
|
|
37
37
|
Session.query(Cls).order_by(Cls.geom.distance_box('POINT(0 0)')).limit(10)
|
38
38
|
"""
|
39
39
|
|
40
|
-
from typing import
|
40
|
+
from typing import Any
|
41
41
|
|
42
42
|
from sqlalchemy import types as sqltypes
|
43
43
|
from sqlalchemy.dialects.postgresql import DOUBLE_PRECISION
|
@@ -65,7 +65,7 @@ SAME: custom_op = custom_op("~=")
|
|
65
65
|
DISTANCE_CENTROID: custom_op = custom_op("<->")
|
66
66
|
DISTANCE_BOX: custom_op = custom_op("<#>")
|
67
67
|
|
68
|
-
_COMPARATOR_INPUT_TYPE =
|
68
|
+
_COMPARATOR_INPUT_TYPE = str | WKBElement | WKTElement
|
69
69
|
|
70
70
|
|
71
71
|
class BaseComparator(UserDefinedType.Comparator):
|
@@ -80,13 +80,13 @@ class BaseComparator(UserDefinedType.Comparator):
|
|
80
80
|
|
81
81
|
key = None
|
82
82
|
|
83
|
-
def __getattr__(self, name):
|
83
|
+
def __getattr__(self, name: str) -> Any:
|
84
84
|
# Function names that don't start with "ST_" are rejected.
|
85
85
|
# This is not to mess up with SQLAlchemy's use of
|
86
86
|
# hasattr/getattr on Column objects.
|
87
87
|
|
88
88
|
if not name.lower().startswith("st_"):
|
89
|
-
raise AttributeError
|
89
|
+
raise AttributeError(f"Attribute {name} not found")
|
90
90
|
|
91
91
|
# We create our own _FunctionGenerator here, and use it in place of
|
92
92
|
# SQLAlchemy's "func" object. This is to be able to "bind" the
|