Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust guess_scale_denominator to custom geometry column name #763

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ Body parameters:
- *db_connection*, string
- exactly one of `file` or `db_connection` must be set
- format `postgresql://<username>:<password>@<host>:<port>/<dbname>?schema=<schema_name>&table=<table_name>&geo_column=<geo_column_name>` is expected with URI scheme `postgresql` and query parameters `schema`, `table`, and `geo_column` specified
- published table is required to have one-column primary key
- *name*, string
- computer-friendly identifier of the layer
- must be unique among all layers of one workspace
Expand Down
3 changes: 2 additions & 1 deletion src/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

@dataclass
class TableUri:
def __init__(self, *, db_uri_str, schema, table, geo_column):
def __init__(self, *, db_uri_str, schema, table, geo_column, primary_key_column):
self.db_uri_str = db_uri_str
self.schema = schema
self.table = table
self.geo_column = geo_column
self.primary_key_column = primary_key_column

_db_uri_str: str
_db_uri: parse.ParseResult
Expand Down
2 changes: 2 additions & 0 deletions src/layman/common/prime_db_schema/publications.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def get_publication_infos_with_metainfo(workspace_name=None, pub_type=None, styl
schema=external_table_uri['schema'],
table=external_table_uri['table'],
geo_column=external_table_uri['geo_column'],
primary_key_column=external_table_uri['primary_key_column'],
) if external_table_uri else None,
'_is_external_table': bool(external_table_uri),
'native_bounding_box': [xmin, ymin, xmax, ymax],
Expand Down Expand Up @@ -404,6 +405,7 @@ def insert_publication(workspace_name, info):
'schema': info["external_table_uri"].schema,
'table': info["external_table_uri"].table,
'geo_column': info["external_table_uri"].geo_column,
'primary_key_column': info["external_table_uri"].primary_key_column,
}) if info.get("external_table_uri") else None

data = (id_workspace,
Expand Down
51 changes: 27 additions & 24 deletions src/layman/layer/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ def get_number_of_features(schema, table_name, conn_cur=None):
return rows[0][0]


def get_text_data(schema, table_name, conn_cur=None):
def get_text_data(schema, table_name, primary_key, conn_cur=None):
_, cur = conn_cur or db_util.get_connection_cursor()
col_names = get_text_column_names(schema, table_name, conn_cur=conn_cur)
if len(col_names) == 0:
Expand All @@ -270,11 +270,12 @@ def get_text_data(schema, table_name, conn_cur=None):
statement = sql.SQL("""
select {fields}
from {table}
order by ogc_fid
order by {primary_key}
limit {limit}
""").format(
fields=sql.SQL(',').join([sql.Identifier(col) for col in col_names]),
table=sql.Identifier(schema, table_name),
primary_key=sql.Identifier(primary_key),
limit=sql.Literal(limit),
)
try:
Expand All @@ -297,8 +298,8 @@ def get_text_data(schema, table_name, conn_cur=None):
return col_texts, limit


def get_text_languages(schema, table_name, *, conn_cur=None):
texts, num_rows = get_text_data(schema, table_name, conn_cur)
def get_text_languages(schema, table_name, primary_key, *, conn_cur=None):
texts, num_rows = get_text_data(schema, table_name, primary_key, conn_cur)
all_langs = set()
for text in texts:
# skip short texts
Expand All @@ -312,61 +313,61 @@ def get_text_languages(schema, table_name, *, conn_cur=None):
return sorted(list(all_langs))


def get_most_frequent_lower_distance_query(schema, table_name):
def get_most_frequent_lower_distance_query(schema, table_name, primary_key, geometry_column):
query = sql.SQL("""
with t1 as (
select
row_number() over (partition by ogc_fid) AS dump_id,
row_number() over (partition by {primary_key}) AS dump_id,
sub_view.*
from (
SELECT
ogc_fid, (st_dump(wkb_geometry)).geom as geometry
{primary_key}, (st_dump({geometry_column})).geom as geometry
FROM {table}
) sub_view
order by ST_NPoints(geometry), ogc_fid, dump_id
order by ST_NPoints(geometry), {primary_key}, dump_id
limit 5000
)
, t2 as (
select
row_number() over (partition by ogc_fid, dump_id) AS ring_id,
row_number() over (partition by {primary_key}, dump_id) AS ring_id,
sub_view.*
from (
(
SELECT
dump_id, ogc_fid, ST_ExteriorRing((ST_DumpRings(geometry)).geom) as geometry
dump_id, {primary_key}, ST_ExteriorRing((ST_DumpRings(geometry)).geom) as geometry
FROM t1
where st_geometrytype(geometry) = 'ST_Polygon'
) union all (
SELECT
dump_id, ogc_fid, geometry
dump_id, {primary_key}, geometry
FROM t1
where st_geometrytype(geometry) = 'ST_LineString'
)
) sub_view
order by ST_NPoints(geometry), ogc_fid, dump_id, ring_id
order by ST_NPoints(geometry), {primary_key}, dump_id, ring_id
limit 5000
)
, t2cumsum as (
select *, --ST_NPoints(geometry),
sum(ST_NPoints(geometry)) over (order by ST_NPoints(geometry), ogc_fid, dump_id, ring_id
sum(ST_NPoints(geometry)) over (order by ST_NPoints(geometry), {primary_key}, dump_id, ring_id
rows between unbounded preceding and current row) as cum_sum_points
from t2
)
, t3 as (
SELECT ogc_fid, dump_id, ring_id, (ST_DumpPoints(st_transform(geometry, 4326))).*
SELECT {primary_key}, dump_id, ring_id, (ST_DumpPoints(st_transform(geometry, 4326))).*
FROM t2cumsum
where cum_sum_points < 50000
)
, t4 as MATERIALIZED (
select t3.ogc_fid, t3.dump_id, t3.ring_id, t3.path[1] as point_idx, t3.geom as point1, t3p2.geom as point2
select t3.{primary_key}, t3.dump_id, t3.ring_id, t3.path[1] as point_idx, t3.geom as point1, t3p2.geom as point2
from t3
inner join t3 t3p2 on (t3.ogc_fid = t3p2.ogc_fid and
inner join t3 t3p2 on (t3.{primary_key} = t3p2.{primary_key} and
t3.dump_id = t3p2.dump_id and
t3.ring_id = t3p2.ring_id and
t3.path[1] + 1 = t3p2.path[1])
)
, tdist as (
SELECT ogc_fid, dump_id, ring_id, point_idx,
SELECT {primary_key}, dump_id, ring_id, point_idx,
ST_DistanceSphere(point1, point2) as distance
FROM t4
)
Expand Down Expand Up @@ -402,14 +403,16 @@ def get_most_frequent_lower_distance_query(schema, table_name):
limit 1
""").format(
table=sql.Identifier(schema, table_name),
primary_key=sql.Identifier(primary_key),
geometry_column=sql.Identifier(geometry_column),
)
return query


def get_most_frequent_lower_distance(schema, table_name, conn_cur=None):
def get_most_frequent_lower_distance(schema, table_name, primary_key, geometry_column, conn_cur=None):
_, cur = conn_cur or db_util.get_connection_cursor()

query = get_most_frequent_lower_distance_query(schema, table_name)
query = get_most_frequent_lower_distance_query(schema, table_name, primary_key, geometry_column)

# print(f"\nget_most_frequent_lower_distance v1\nusername={username}, layername={layername}")
# print(query)
Expand Down Expand Up @@ -448,8 +451,8 @@ def get_most_frequent_lower_distance(schema, table_name, conn_cur=None):
]


def guess_scale_denominator(schema, table_name, *, conn_cur=None):
distance = get_most_frequent_lower_distance(schema, table_name, conn_cur=conn_cur)
def guess_scale_denominator(schema, table_name, primary_key, geometry_column, *, conn_cur=None):
distance = get_most_frequent_lower_distance(schema, table_name, primary_key, geometry_column, conn_cur=conn_cur)
log_sd_list = [math.log10(sd) for sd in SCALE_DENOMINATORS]
if distance is not None:
coef = 2000 if distance > 100 else 1000
Expand Down Expand Up @@ -524,7 +527,7 @@ def ensure_attributes(attribute_tuples):
return missing_attributes


def get_bbox(schema, table_name, conn_cur=None, column='wkb_geometry'):
def get_bbox(schema, table_name, conn_cur=None, column=settings.OGR_DEFAULT_GEOMETRY_COLUMN):
query = sql.SQL('''
with tmp as (select ST_Extent(l.{column}) as bbox
from {table} l
Expand All @@ -542,14 +545,14 @@ def get_bbox(schema, table_name, conn_cur=None, column='wkb_geometry'):
return result


def get_crs(schema, table_name, conn_cur=None, column='wkb_geometry'):
def get_crs(schema, table_name, conn_cur=None, column=settings.OGR_DEFAULT_GEOMETRY_COLUMN):
query = 'select Find_SRID(%s, %s, %s);'
srid = db_util.run_query(query, (schema, table_name, column), conn_cur=conn_cur)[0][0]
crs = db_util.get_crs(srid)
return crs


def get_geometry_types(schema, table_name, *, column_name='wkb_geometry', conn_cur=None):
def get_geometry_types(schema, table_name, *, column_name=settings.OGR_DEFAULT_GEOMETRY_COLUMN, conn_cur=None):
conn, cur = conn_cur or db_util.get_connection_cursor()
query = sql.SQL("""
select distinct ST_GeometryType({column}) as geometry_type_name
Expand Down
15 changes: 8 additions & 7 deletions src/layman/layer/db/db_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ def test_data_language(boundary_table):
col_names = db.get_text_column_names(workspace, table_name)
assert set(col_names) == set(['featurecla', 'name', 'name_alt'])
with layman.app_context():
text_data, _ = db.get_text_data(workspace, table_name)
text_data, _ = db.get_text_data(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY)
# print(f"num_rows={num_rows}")
assert len(text_data) == 1
assert text_data[0].startswith(' '.join(['International boundary (verify)'] * 100))
with layman.app_context():
langs = db.get_text_languages(workspace, table_name)
langs = db.get_text_languages(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY)
assert langs == ['eng']


Expand Down Expand Up @@ -214,7 +214,7 @@ def test_data_language_roads(road_table):
'vym_tahy_p'
])
with layman.app_context():
langs = db.get_text_languages(workspace, table_name)
langs = db.get_text_languages(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY)
assert langs == ['cze']


Expand All @@ -226,7 +226,7 @@ def test_populated_places_table(populated_places_table):
col_names = db.get_text_column_names(workspace, table_name)
assert len(col_names) == 31
with layman.app_context():
langs = db.get_text_languages(workspace, table_name)
langs = db.get_text_languages(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY)
assert set(langs) == set(['chi', 'eng', 'rus'])


Expand All @@ -238,7 +238,7 @@ def test_data_language_countries(country_table):
col_names = db.get_text_column_names(workspace, table_name)
assert len(col_names) == 63
with layman.app_context():
langs = db.get_text_languages(workspace, table_name)
langs = db.get_text_languages(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY)
assert set(langs) == set([
'ara',
'ben',
Expand Down Expand Up @@ -266,13 +266,14 @@ def test_data_language_countries2(country110m_table):
# assert len(col_names) == 63
with layman.app_context():
table_name = db.get_table_name(workspace, layername)
langs = db.get_text_languages(workspace, table_name)
langs = db.get_text_languages(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY)
assert set(langs) == set(['eng'])


def guess_scale_denominator(workspace, layer):
table_name = db.get_table_name(workspace, layer)
return db.guess_scale_denominator(workspace, table_name)
return db.guess_scale_denominator(workspace, table_name, settings.OGR_DEFAULT_PRIMARY_KEY,
settings.OGR_DEFAULT_GEOMETRY_COLUMN)


def test_guess_scale_denominator(country110m_table, country50m_table, country10m_table,
Expand Down
4 changes: 2 additions & 2 deletions src/layman/layer/db/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,6 @@ def delete_layer(workspace, layername, conn_cur=None):


def set_layer_srid(schema, table_name, srid, *, conn_cur=None):
query = '''SELECT UpdateGeometrySRID(%s, %s, 'wkb_geometry', %s);'''
params = (schema, table_name, srid)
query = '''SELECT UpdateGeometrySRID(%s, %s, %s, %s);'''
params = (schema, table_name, settings.OGR_DEFAULT_GEOMETRY_COLUMN, srid)
db_util.run_query(query, params, conn_cur=conn_cur)
6 changes: 4 additions & 2 deletions src/layman/layer/micka/csw.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,13 @@ def get_template_path_and_values(workspace, layername, http_method):
table_name = table_uri.table
conn_cur = db_util.create_connection_cursor(db_uri_str=table_uri.db_uri_str)
try:
languages = db.get_text_languages(table_uri.schema, table_name, conn_cur=conn_cur)
languages = db.get_text_languages(table_uri.schema, table_name, table_uri.primary_key_column,
conn_cur=conn_cur)
except LaymanError:
languages = []
try:
scale_denominator = db.guess_scale_denominator(table_uri.schema, table_name, conn_cur=conn_cur)
scale_denominator = db.guess_scale_denominator(table_uri.schema, table_name, table_uri.primary_key_column,
table_uri.geo_column, conn_cur=conn_cur)
except LaymanError:
scale_denominator = None
spatial_resolution = {
Expand Down
3 changes: 2 additions & 1 deletion src/layman/layer/prime_db_schema/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ def get_layer_info(workspace, layername):
db_uri_str=settings.PG_URI_STR,
schema=workspace,
table=f'layer_{uuid.replace("-", "_")}',
geo_column='wkb_geometry'
geo_column=settings.OGR_DEFAULT_GEOMETRY_COLUMN,
primary_key_column=settings.OGR_DEFAULT_PRIMARY_KEY,
) if info['_file_type'] == settings.FILE_TYPE_VECTOR and not info.get('_table_uri') else info.get('_table_uri')

return info
Expand Down
2 changes: 1 addition & 1 deletion src/layman/layer/qgis/layer-template.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{extent}
</extent>
<id>{layer_name}_{layer_uuid}</id>
<datasource>dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='ogc_fid'
<datasource>dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='{primary_key_column}'
srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table="{db_schema}"."{db_table}" ({geo_column})
</datasource>
<keywordList>
Expand Down
2 changes: 1 addition & 1 deletion src/layman/layer/qgis/project-template.qgs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</projectCrs>
<layer-tree-group>
<customproperties/>
<layer-tree-layer source="dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='ogc_fid' srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table=&quot;{db_schema}&quot;.&quot;{db_table}&quot; ({geo_column})" providerKey="postgres" legend_exp="" checked="Qt::Checked" id="{layer_name}_{layer_uuid}" expanded="1" name="{layer_name}">
<layer-tree-layer source="dbname='{db_name}' host={db_host} port={db_port} user='{db_user}' password='{db_password}' sslmode=disable key='{primary_key_column}' srid={srid} type={source_type} checkPrimaryKeyUnicity='1' table=&quot;{db_schema}&quot;.&quot;{db_table}&quot; ({geo_column})" providerKey="postgres" legend_exp="" checked="Qt::Checked" id="{layer_name}_{layer_uuid}" expanded="1" name="{layer_name}">
<customproperties/>
</layer-tree-layer>
<custom-order enabled="0">
Expand Down
2 changes: 2 additions & 0 deletions src/layman/layer/qgis/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def fill_layer_template(layer, uuid, native_bbox, crs, qml_xml, source_type, att
source_type=source_type,
db_schema=db_schema,
db_table=table_name,
primary_key_column=table_uri.primary_key_column,
geo_column=geo_column,
layer_name=layer_name,
layer_uuid=uuid,
Expand Down Expand Up @@ -137,6 +138,7 @@ def fill_project_template(layer, layer_uuid, layer_qml, crs, epsg_codes, extent,
source_type=source_type,
db_schema=db_schema,
db_table=table_name,
primary_key_column=table_uri.primary_key_column,
geo_column=geo_column,
layer_name=layer_name,
layer_uuid=layer_uuid,
Expand Down
2 changes: 1 addition & 1 deletion src/layman/layer/qgis/wms.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def save_qgs_file(workspace, layer):
db_types = db.get_geometry_types(db_schema, table_name, conn_cur=conn_cur)
db_cols = [
col for col in db.get_all_column_infos(db_schema, table_name, conn_cur=conn_cur, omit_geometry_columns=True)
if col.name not in ['ogc_fid']
if col.name != table_uri.primary_key_column
]
source_type = util.get_source_type(db_types, qml_geometry)
layer_qml = util.fill_layer_template(layer, uuid, layer_bbox, crs, qml, source_type, db_cols, table_uri)
Expand Down
42 changes: 42 additions & 0 deletions src/layman/layer/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,52 @@ def parse_and_validate_external_table_uri_str(external_table_uri_str):
}
})

# https://stackoverflow.com/a/20537829
query = f'''
SELECT
pg_attribute.attname,
format_type(pg_attribute.atttypid, pg_attribute.atttypmod)
FROM pg_index, pg_class, pg_attribute, pg_namespace
WHERE
pg_class.relname = %s AND
indrelid = pg_class.oid AND
nspname = %s AND
pg_class.relnamespace = pg_namespace.oid AND
pg_attribute.attrelid = pg_class.oid AND
pg_attribute.attnum = any(pg_index.indkey)
AND indisprimary'''
query_res = db_util.run_query(query, (table, schema), conn_cur=conn_cur, log_query=True)
primary_key_columns = [r[0] for r in query_res]
if len(query_res) == 0:
raise LaymanError(2, {
'parameter': 'db_connection',
'message': 'No primary key found in the table.',
'expected': 'Table with one-column primary key.',
'found': {
'db_connection': external_table_uri_str,
'schema': schema,
'table': table,
'primary_key_columns': primary_key_columns,
}
})
if len(query_res) > 1:
raise LaymanError(2, {
'parameter': 'db_connection',
'message': 'Table with multi-column primary key.',
'expected': 'Table with one-column primary key.',
'found': {
'db_connection': external_table_uri_str,
'schema': schema,
'table': table,
'primary_key_columns': primary_key_columns,
}
})

result = TableUri(db_uri_str=db_uri_str,
schema=schema,
table=table,
geo_column=geo_column,
primary_key_column=primary_key_columns[0],
)

return result
Loading