# HG changeset patch # User Tom Gottfried # Date 1583943083 -3600 # Node ID e8b2dc771f9e2df9ef88e16dba5bbaddcf9d0a5f # Parent 0b97f5301a1732e695771caedd25673104c7096b Store axis as MultiLinestring MultiLinestrings could already be imported but we stored them as multiple Linestrings with identical attributes and even stored Linestrings with self-intersections as multiple single Linestrings with identical attributes. Avoid both by storing as MultiLinestring. In passing, removed unnecessary processing steps in the INSERT statemet for the sys_admin case and ensured that attempts to convert to valid simple features are made after transformation, which might lead to invalid features. Since isrsrange_axis() relies on single Linestrings for linear referencing, add an extra ST_Dump(). diff -r 0b97f5301a17 -r e8b2dc771f9e pkg/imports/wx.go --- a/pkg/imports/wx.go Wed Mar 11 15:54:37 2020 +0100 +++ b/pkg/imports/wx.go Wed Mar 11 17:11:23 2020 +0100 @@ -100,16 +100,18 @@ SELECT users.current_user_area_utm() AS a ) INSERT INTO waterway.waterway_axis (wtwaxs, objnam, nobjnam) -SELECT dmp.geom, $3, $4 +SELECT + ST_Multi(ST_Node(ST_CollectionExtract(ST_Transform(new_ax, 4326), 2))), + $3, $4 FROM ST_GeomFromWKB($1, $2::integer) AS new_line (new_line), - ST_Dump(ST_Transform(ST_CollectionExtract( + LATERAL (SELECT CASE WHEN pg_has_role('sys_admin', 'MEMBER') - THEN ST_Node(ST_Transform(new_line, - best_utm(ST_Transform(new_line, 4326)))) + THEN new_line ELSE ST_Intersection((SELECT a FROM resp), ST_Node(ST_Transform(new_line, (SELECT ST_SRID(a) FROM resp)))) - END, - 2), 4326)) AS dmp + END) AS new_ax (new_ax) + -- Do nothing if intersection is empty: + WHERE NOT ST_IsEmpty(new_ax) RETURNING id ` ) @@ -222,47 +224,34 @@ nobjnam = sql.NullString{String: *props.NObjNnm, Valid: true} } + var ls multiLineSlice switch feature.Geometry.Type { case "LineString": var l lineSlice if err := json.Unmarshal(*feature.Geometry.Coordinates, &l); err != nil { return err } - if err := storeLinestring( - ctx, - savepoint, - feedback, - l, - epsg, - props, - nobjnam, - &outside, - &features, - insertStmt); err != nil { - return err - } + ls = append(ls, l) case "MultiLineString": - var ls []lineSlice if err := json.Unmarshal(*feature.Geometry.Coordinates, &ls); err != nil { return err } - for _, l := range ls { - if err := storeLinestring( - ctx, - savepoint, - feedback, - l, - epsg, - props, - nobjnam, - &outside, - &features, - insertStmt); err != nil { - return err - } - } default: unsupported[feature.Geometry.Type]++ + continue + } + if err := storeLinestring( + ctx, + savepoint, + feedback, + ls, + epsg, + props, + nobjnam, + &outside, + &features, + insertStmt); err != nil { + return err } } return nil @@ -302,7 +291,7 @@ ctx context.Context, savepoint func(func() error) error, feedback Feedback, - l lineSlice, + l multiLineSlice, epsg int, props waterwayAxisProperties, nobjnam sql.NullString, diff -r 0b97f5301a17 -r e8b2dc771f9e schema/gemma.sql --- a/schema/gemma.sql Wed Mar 11 15:54:37 2020 +0100 +++ b/schema/gemma.sql Wed Mar 11 17:11:23 2020 +0100 @@ -620,7 +620,7 @@ CREATE TABLE waterway_axis ( id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, - wtwaxs geography(LINESTRING, 4326) NOT NULL + wtwaxs geography(MULTILINESTRING, 4326) NOT NULL CHECK(ST_IsSimple(CAST(wtwaxs AS geometry))), -- TODO: Do we need to check data set quality (DRC 2.1.6)? objnam varchar NOT NULL, diff -r 0b97f5301a17 -r e8b2dc771f9e schema/gemma_tests.sql --- a/schema/gemma_tests.sql Wed Mar 11 15:54:37 2020 +0100 +++ b/schema/gemma_tests.sql Wed Mar 11 17:11:23 2020 +0100 @@ -26,8 +26,8 @@ SELECT throws_ok($$ INSERT INTO waterway.waterway_axis (wtwaxs, objnam) VALUES - (ST_GeogFromText('LINESTRING(0 0, 1 1)'), 'test'), - (ST_GeogFromText('LINESTRING(0 0, 1 1)'), 'test') + (ST_GeogFromText('MULTILINESTRING((0 0, 1 1))'), 'test'), + (ST_GeogFromText('MULTILINESTRING((0 0, 1 1))'), 'test') $$, 23505, NULL, 'No duplicate geometries can be inserted into waterway_axis'); diff -r 0b97f5301a17 -r e8b2dc771f9e schema/isrs_functions.sql --- a/schema/isrs_functions.sql Wed Mar 11 15:54:37 2020 +0100 +++ b/schema/isrs_functions.sql Wed Mar 11 17:11:23 2020 +0100 @@ -61,9 +61,11 @@ z = best_utm(stretch); CREATE TEMP TABLE axis AS - SELECT id, wtwaxs, ST_Boundary(wtwaxs) AS bdr - FROM (SELECT id, ST_Transform(wtwaxs::geometry, z) AS wtwaxs - FROM waterway.waterway_axis) AS axs; + SELECT row_number() OVER () AS id, + geom AS wtwaxs, + ST_Boundary(geom) AS bdr + FROM waterway.waterway_axis, + ST_Dump(ST_Transform(wtwaxs::geometry, z)); CREATE INDEX axs_bdr ON axis USING GiST (bdr); ANALYZE axis; diff -r 0b97f5301a17 -r e8b2dc771f9e schema/tap_tests_data.sql --- a/schema/tap_tests_data.sql Wed Mar 11 15:54:37 2020 +0100 +++ b/schema/tap_tests_data.sql Wed Mar 11 17:11:23 2020 +0100 @@ -124,22 +124,23 @@ ); INSERT INTO waterway.waterway_axis (wtwaxs, objnam) VALUES ( - ST_SetSRID(ST_CurveToLine( - 'CIRCULARSTRING(0 0, 0.5 0.5, 0.6 0.4)'), + ST_SetSRID( + ST_Multi(ST_CurveToLine('CIRCULARSTRING(0 0, 0.5 0.5, 0.6 0.4)')), 4326), 'testriver' ), ( - ST_SetSRID(ST_CurveToLine('CIRCULARSTRING(0.6 0.4, 1 0, 1.5 -0.00001)'), + ST_SetSRID( + ST_Multi(ST_CurveToLine('CIRCULARSTRING(0.6 0.4, 1 0, 1.5 -0.00001)')), 4326), 'testriver' ), ( - ST_SetSRID('LINESTRING(0.5 0.5, 1 1)'::geometry, 4326), + ST_SetSRID('MULTILINESTRING((0.5 0.5, 1 1))'::geometry, 4326), 'testriver' ), ( - ST_SetSRID('LINESTRING(1.5 0, 1.55001 0)'::geometry, 4326), + ST_SetSRID('MULTILINESTRING((1.5 0, 1.55001 0))'::geometry, 4326), 'testriver' ), ( - ST_SetSRID('LINESTRING(1.55 0, 2 0)'::geometry, 4326), + ST_SetSRID('MULTILINESTRING((1.55 0, 2 0))'::geometry, 4326), 'testriver' ); diff -r 0b97f5301a17 -r e8b2dc771f9e schema/updates/1423/01.axis_as_multilinestring.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1423/01.axis_as_multilinestring.sql Wed Mar 11 17:11:23 2020 +0100 @@ -0,0 +1,11 @@ +-- Cannot alter type of a column used in a trigger definition +DROP TRIGGER waterway_axis_wtwaxs_unique ON waterway.waterway_axis; + +ALTER TABLE waterway.waterway_axis + ALTER wtwaxs TYPE geography(MULTILINESTRING, 4326) + USING ST_Multi(CAST(wtwaxs AS geometry)); + +-- Re-create trigger +CREATE CONSTRAINT TRIGGER waterway_axis_wtwaxs_unique + AFTER INSERT OR UPDATE OF wtwaxs ON waterway.waterway_axis + FOR EACH ROW EXECUTE FUNCTION prevent_st_equals('wtwaxs'); diff -r 0b97f5301a17 -r e8b2dc771f9e schema/updates/1423/02.adapt_func.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/schema/updates/1423/02.adapt_func.sql Wed Mar 11 17:11:23 2020 +0100 @@ -0,0 +1,105 @@ +CREATE OR REPLACE FUNCTION ISRSrange_axis( + stretch isrsrange, + tolerance float + -- in m, up to which linestrings will be connected at their boundary +) RETURNS geometry +AS $$ +DECLARE z int; +DECLARE result_geom geometry; +BEGIN + -- Find best matchting UTM zone + z = best_utm(stretch); + + CREATE TEMP TABLE axis AS + SELECT row_number() OVER () AS id, + geom AS wtwaxs, + ST_Boundary(geom) AS bdr + FROM waterway.waterway_axis, + ST_Dump(ST_Transform(wtwaxs::geometry, z)); + CREATE INDEX axs_bdr ON axis USING GiST (bdr); + ANALYZE axis; + + WITH RECURSIVE + -- In order to guarantee the following ST_Covers to work, + -- snap distance mark coordinates to axis + points0 AS ( + SELECT ST_ClosestPoint( + wtwaxs, + ST_Transform(geom, z)) AS geom + FROM ST_Dump(ISRSrange_points(stretch)), ( + SELECT ST_Collect(wtwaxs) AS wtwaxs + FROM axis) AS ax), + -- Ensure two distinct points on axis have been found + points AS ( + SELECT geom + FROM points0 + WHERE 2 = (SELECT count(DISTINCT geom) FROM points0)), + axis_snapped AS ( + -- Iteratively connect non-contiguous axis chunks + -- to find the contiguous axis on which given distance marks lie + (SELECT ARRAY[id] AS ids, wtwaxs + FROM axis, points + WHERE ST_Intersects( + ST_Buffer(axis.wtwaxs, 0.0001), points.geom) + FETCH FIRST ROW ONLY) + UNION + -- Connect endpoint of next linestring with closest + -- endpoint of merged linestring until a contiguous + -- linestring connecting both distance marks is build up + (SELECT refids || id, + ST_LineMerge(ST_Collect(ARRAY( + -- Linestring build up so far + SELECT refgeom + UNION + -- Fill eventual gap + SELECT ST_ShortestLine( + ST_Boundary(refgeom), bdr) + UNION + -- Linestring to be added + SELECT geom))) + FROM axis_snapped AS axis_snapped (refids, refgeom), + axis AS axis (id, geom, bdr), + (SELECT ST_Collect(points.geom) AS pts + FROM points) AS points + WHERE id <> ALL(refids) + AND ST_DWithin( + ST_Boundary(refgeom), bdr, tolerance) + AND NOT ST_Covers(ST_Buffer(refgeom, 0.0001), points.pts) + ORDER BY ST_Boundary(refgeom) <-> bdr + FETCH FIRST ROW ONLY)), + axis_segment AS ( + -- Fetch end result from snapping + SELECT wtwaxs AS line + FROM axis_snapped, + (SELECT ST_Collect(points.geom) AS pts + FROM points) AS points + -- Return end result only if both distance marks were connected + WHERE ST_Covers(ST_Buffer(wtwaxs, 0.0001), points.pts)) + -- Use linear referencing to clip axis between distance marks. + -- Simplification is used to work-around the problem, that + -- ST_LineSubstring might generate very small line segments at an + -- end of the resulting linestring, that significantly differ from + -- the direction of the input linestring due to finite precision + -- of the calculation. The generated small segment of the + -- resulting line would lead e.g. to unexpected results in an area + -- generated by ISRSrange_area(). + SELECT ST_SimplifyPreserveTopology(ST_LineSubstring( + axis_segment.line, min(fractions.f), max(fractions.f)), + 0.0001) AS line + INTO STRICT result_geom + FROM axis_segment, LATERAL ( + SELECT ST_LineLocatePoint(axis_segment.line, points.geom) AS f + FROM points) AS fractions + GROUP BY axis_segment.line; + + -- Drop temporary table to avoid side effects on PostgreSQL's MVCC, + -- because otherwise subsequent invocations of the function will not see + -- changes on the underlying waterway.waterway_axis that might have + -- occured. + DROP TABLE axis; + + RETURN result_geom; +END; + $$ + LANGUAGE plpgsql + PARALLEL RESTRICTED; diff -r 0b97f5301a17 -r e8b2dc771f9e schema/version.sql --- a/schema/version.sql Wed Mar 11 15:54:37 2020 +0100 +++ b/schema/version.sql Wed Mar 11 17:11:23 2020 +0100 @@ -1,1 +1,1 @@ -INSERT INTO gemma_schema_version(version) VALUES (1422); +INSERT INTO gemma_schema_version(version) VALUES (1423);