Skip to content

Commit

Permalink
Add test for ExtractWithinDistance with PostgreSQL layer inputs
Browse files Browse the repository at this point in the history
This is a test for the bug reported in #45352
Tests matching and mismatching CRS for target and reference layers

(cherry picked from commit edfccd9)
  • Loading branch information
strk authored and nyalldawson committed Jan 31, 2022
1 parent 2378ef9 commit 6c08116
Showing 1 changed file with 218 additions and 1 deletion.
219 changes: 218 additions & 1 deletion tests/src/python/test_provider_postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,34 @@
from datetime import datetime

from qgis.core import (
QgsApplication,
QgsVectorLayer,
QgsVectorLayerExporter,
QgsFeatureRequest,
QgsFeatureSource,
QgsFeature,
QgsFieldConstraints,
QgsDataProvider,
NULL,
QgsVectorLayerUtils,
QgsSettings,
QgsTransactionGroup,
QgsReadWriteContext,
QgsRectangle,
QgsDefaultValue,
QgsCoordinateReferenceSystem,
QgsProcessingUtils,
QgsProcessingContext,
QgsProcessingFeedback,
QgsProject,
QgsWkbTypes,
QgsGeometry,
QgsProviderRegistry,
QgsVectorDataProvider,
QgsDataSourceUri,
QgsProviderConnectionException,
NULL,
)
from qgis.analysis import QgsNativeAlgorithms
from qgis.gui import QgsGui, QgsAttributeForm
from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir, QObject, QByteArray, QTemporaryDir
from qgis.PyQt.QtWidgets import QLabel
Expand Down Expand Up @@ -125,6 +130,23 @@ def __del__(self):

return ScopedBackup(self, schema, table)

def temporarySchema(self, name):

class TemporarySchema():
def __init__(self, tester, name):
self.tester = tester
self.name = name + '_tmp'

def __enter__(self):
self.tester.execSQLCommand('DROP SCHEMA IF EXISTS {} CASCADE'.format(self.name))
self.tester.execSQLCommand('CREATE SCHEMA {}'.format(self.name))
return self.name

def __exit__(self, type, value, traceback):
self.tester.execSQLCommand('DROP SCHEMA {} CASCADE'.format(self.name))

return TemporarySchema(self, 'qgis_test_' + name)

def getSource(self):
# create temporary table for edit tests
self.execSQLCommand(
Expand Down Expand Up @@ -2870,6 +2892,201 @@ def testChangeAttributeWithDefaultValue(self):
self.assertEqual(feat["thetext3"], "blabla")
self.assertEqual(feat["thenumber"], 4)

def testExtractWithinDistanceAlgorithm(self):

with self.temporarySchema('extract_within_distance') as schema:

# Create and populate target table in PseudoWebMercator CRS
self.execSQLCommand(
'CREATE TABLE {}.target_3857 (id serial primary key, g geometry(linestring, 3857))'
.format(schema))
# -- first line (id=1)
self.execSQLCommand(
"INSERT INTO {}.target_3857 (g) values('SRID=3857;LINESTRING(0 0, 1000 1000)')"
.format(schema))
# -- secodn line is a great circle line on the right (id=2)
self.execSQLCommand(
"INSERT INTO {}.target_3857 (g) values( ST_Transform('SRID=4326;LINESTRING(80 0,160 80)'::geometry, 3857) )"
.format(schema))

# Create and populate reference table in PseudoWebMercator CRS
self.execSQLCommand(
'CREATE TABLE {}.reference_3857 (id serial primary key, g geometry(point, 3857))'
.format(schema))
self.execSQLCommand(
"INSERT INTO {}.reference_3857 (g) values('SRID=3857;POINT(500 999)')"
.format(schema))
self.execSQLCommand(
"INSERT INTO {}.reference_3857 (g) values('SRID=3857;POINT(501 999)')"
.format(schema))
self.execSQLCommand(
# -- this reference (id=3) is ON the first line (id=1) in webmercator
# -- and probably very close in latlong WGS84
"INSERT INTO {}.reference_3857 (g) values('SRID=3857;POINT(500 500)')"
.format(schema))
self.execSQLCommand(
# -- this reference (id=4) is at ~ 5 meters from second line (id=2) in webmercator
"INSERT INTO {}.reference_3857 (g) values('SRID=3857;POINT(12072440.688888172 5525668.358321408)')"
.format(schema))
self.execSQLCommand(
"INSERT INTO {}.reference_3857 (g) values('SRID=3857;POINT(503 999)')"
.format(schema))
self.execSQLCommand(
"INSERT INTO {}.reference_3857 (g) values('SRID=3857;POINT(504 999)')"
.format(schema))

# Create and populate target and reference table in WGS84 latlong
self.execSQLCommand(
'CREATE TABLE {0}.target_4326 AS SELECT id, ST_Transform(g, 4326)::geometry(linestring,4326) as g FROM {0}.target_3857'
.format(schema))
self.execSQLCommand(
'CREATE TABLE {0}.reference_4326 AS SELECT id, ST_Transform(g, 4326)::geometry(point,4326) as g FROM {0}.reference_3857'
.format(schema))

# Create target and reference layers
vl_target_3857 = QgsVectorLayer(
'{} sslmode=disable key=id srid=3857 type=LINESTRING table="{}"."target_3857" (g) sql='
.format(self.dbconn, schema),
'target_3857', 'postgres')
self.assertTrue(vl_target_3857.isValid(), "Could not create a layer from the '{}.target_3857' table using dbconn '{}'" .format(schema, self.dbconn))
vl_reference_3857 = QgsVectorLayer(
'{} sslmode=disable key=id srid=3857 type=POINT table="{}"."reference_3857" (g) sql='
.format(self.dbconn, schema),
'reference_3857', 'postgres')
self.assertTrue(vl_reference_3857.isValid(), "Could not create a layer from the '{}.reference_3857' table using dbconn '{}'" .format(schema, self.dbconn))
vl_target_4326 = QgsVectorLayer(
'{} sslmode=disable key=id srid=4326 type=LINESTRING table="{}"."target_4326" (g) sql='
.format(self.dbconn, schema),
'target_4326', 'postgres')
self.assertTrue(vl_target_4326.isValid(), "Could not create a layer from the '{}.target_4326' table using dbconn '{}'" .format(schema, self.dbconn))
vl_reference_4326 = QgsVectorLayer(
'{} sslmode=disable key=id srid=4326 type=POINT table="{}"."reference_4326" (g) sql='
.format(self.dbconn, schema),
'reference_4326', 'postgres')
self.assertTrue(vl_reference_4326.isValid(), "Could not create a layer from the '{}.reference_4326' table using dbconn '{}'" .format(schema, self.dbconn))

# Create the ExtractWithinDistance algorithm
# TODO: move registry initialization in class initialization ?
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
registry = QgsApplication.instance().processingRegistry()
alg = registry.createAlgorithmById('native:extractwithindistance')
self.assertIsNotNone(alg)

# Utility feedback and context objects
class ConsoleFeedBack(QgsProcessingFeedback):
_error = ''

def reportError(self, error, fatalError=False):
self._error = error
print(error)

feedback = ConsoleFeedBack()
context = QgsProcessingContext()

# ----------------------------------------------------------------
# Run the ExtractWithinDistance algorithm with matching
# target_3857 and reference_3857 CRSs

parameters = {
# extract features from here:
'INPUT': vl_target_3857,
# extracted features must be within given
# distance from this layer:
'REFERENCE': vl_reference_3857,
# distance (in INPUT units)
'DISTANCE': 10, # meters
'OUTPUT': 'memory:result'
}

# Note: the following returns true also in case of errors ...
result = alg.run(parameters, context, feedback)
self.assertEqual(result[1], True)

result_layer_name = result[0]['OUTPUT']
vl_result = QgsProcessingUtils.mapLayerFromString(result_layer_name, context)
self.assertTrue(vl_result.isValid())

extracted_fids = [f['id'] for f in vl_result.getFeatures()]
self.assertEqual(set(extracted_fids), {1, 2})

# ----------------------------------------------------------------
# Run the ExtractWithinDistance algorithm with matching
# target_4326 and reference_4326 CRSs

parameters = {
# extract features from here:
'INPUT': vl_target_4326,
# extracted features must be within given
# distance from this layer:
'REFERENCE': vl_reference_4326,
# distance (in INPUT units)
'DISTANCE': 9e-5, # degrees
'OUTPUT': 'memory:result'
}

# Note: the following returns true also in case of errors ...
result = alg.run(parameters, context, feedback)
self.assertEqual(result[1], True)

result_layer_name = result[0]['OUTPUT']
vl_result = QgsProcessingUtils.mapLayerFromString(result_layer_name, context)
self.assertTrue(vl_result.isValid())

extracted_fids = [f['id'] for f in vl_result.getFeatures()]
self.assertEqual(set(extracted_fids), {1})

# ----------------------------------------------------------------
# Run the ExtractWithinDistance algorithm with
# target in 4326 CRS and reference in 3857 CRS

parameters = {
# extract features from here:
'INPUT': vl_target_4326,
# extracted features must be within given
# distance from this layer:
'REFERENCE': vl_reference_3857,
# distance (in INPUT units)
'DISTANCE': 9e-5, # degrees
'OUTPUT': 'memory:result'
}

# Note: the following returns true also in case of errors ...
result = alg.run(parameters, context, feedback)
self.assertEqual(result[1], True)

result_layer_name = result[0]['OUTPUT']
vl_result = QgsProcessingUtils.mapLayerFromString(result_layer_name, context)
self.assertTrue(vl_result.isValid())

extracted_fids = [f['id'] for f in vl_result.getFeatures()]
self.assertEqual(set(extracted_fids), {1})

# ----------------------------------------------------------------
# Run the ExtractWithinDistance algorithm with
# target in 3857 CRS and reference in 4326 CRS

parameters = {
# extract features from here:
'INPUT': vl_target_3857,
# extracted features must be within given
# distance from this layer:
'REFERENCE': vl_reference_4326,
# distance (in INPUT units)
'DISTANCE': 10, # meters
'OUTPUT': 'memory:result'
}

# Note: the following returns true also in case of errors ...
result = alg.run(parameters, context, feedback)
self.assertEqual(result[1], True)

result_layer_name = result[0]['OUTPUT']
vl_result = QgsProcessingUtils.mapLayerFromString(result_layer_name, context)
self.assertTrue(vl_result.isValid())

extracted_fids = [f['id'] for f in vl_result.getFeatures()]
self.assertEqual(set(extracted_fids), {1, 2}) # Bug ?


class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):

Expand Down

0 comments on commit 6c08116

Please sign in to comment.