From: Sandro Santilli Date: Wed, 20 Mar 2013 11:44:37 +0000 (+0000) Subject: Speedup areal TopoJSON output routine to use edge walking X-Git-Tag: 2.1.0beta2~161 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=30284fe572e23abaf50bc356a6d35c8460a07af4;p=postgis Speedup areal TopoJSON output routine to use edge walking Now it takes 6% of the time to do the same thing ! Tweak tests to expect new arcs numbering and order. Also fixes missing comma separating polygon ring arcs. git-svn-id: http://svn.osgeo.org/postgis/trunk@11187 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/topology/sql/export/TopoJSON.sql.in b/topology/sql/export/TopoJSON.sql.in index f1cd1637e..fb6c87fb3 100644 --- a/topology/sql/export/TopoJSON.sql.in +++ b/topology/sql/export/TopoJSON.sql.in @@ -14,6 +14,8 @@ -- -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/* #define POSTGIS_TOPOLOGY_DEBUG 1 */ + --{ -- -- API FUNCTION @@ -25,7 +27,6 @@ CREATE OR REPLACE FUNCTION topology.AsTopoJSON(tg topology.TopoGeometry, edgeMap RETURNS text AS $$ DECLARE - visited bool; toponame text; json text; sql text; @@ -35,6 +36,10 @@ DECLARE side int; arcid int; arcs int[]; + arctxt TEXT[]; + old_search_path TEXT; + faces int[]; + visited_edges int[]; BEGIN IF tg IS NULL THEN @@ -100,69 +105,133 @@ BEGIN json := '{ "type": "Polygon", "arcs": ['; - FOR rec IN SELECT (ST_DumpRings((ST_Dump(ST_ForceRHR( - topology.Geometry(tg)))).geom)).geom + EXECUTE 'SHOW search_path' INTO old_search_path; + EXECUTE 'SET search_path TO ' || quote_ident(toponame) || ',' || old_search_path; + + SELECT array_agg(id) as f + FROM ( SELECT (GetTopoGeomElements(tg))[1] as id ) as f + INTO faces; + +#ifdef POSTGIS_TOPOLOGY_DEBUG + RAISE DEBUG 'Faces: %', faces; +#endif + + visited_edges := ARRAY[]::int[]; + LOOP -- { arcs := NULL; - bounds = ST_Boundary(rec.geom); - sql := 'SELECT e.*, ST_Line_Locate_Point(' - || quote_literal(bounds::text) - || ', ST_Line_Interpolate_Point(e.geom, 0.2)) as pos' - || ', ST_Line_Locate_Point(' - || quote_literal(bounds::text) - || ', ST_Line_Interpolate_Point(e.geom, 0.8)) as pos2 FROM ' - || quote_ident(toponame) - || '.edge e WHERE ST_Covers(' - || quote_literal(bounds::text) - || ', e.geom) ORDER BY pos'; - - -- RAISE DEBUG 'SQL: %', sql; - - FOR rec2 IN EXECUTE sql - LOOP + FOR rec in -- { +WITH RECURSIVE +_edges AS ( + SELECT e.*, + e.left_face = ANY ( faces ) as lf, + e.right_face = ANY ( faces ) as rf + FROM edge e + WHERE ( e.left_face = ANY ( faces ) OR + e.right_face = ANY ( faces ) ) +), +_leftmost_non_dangling_edge AS ( + SELECT * FROM _edges e + WHERE ( e.lf or e.rf ) AND ( e.lf != e.rf ) + AND NOT e.edge_id = ANY ( visited_edges ) + -- TODO: and not in visited ? + ORDER BY geom LIMIT 1 +), +_edgepath AS ( + SELECT + CASE + WHEN e.lf THEN lme.edge_id + ELSE -lme.edge_id + END as signed_edge_id, + false as back, + + e.lf = e.rf as dangling, + e.lf, e.rf, + e.next_right_edge, e.next_left_edge + + FROM _edges e, _leftmost_non_dangling_edge lme + WHERE e.edge_id = abs(lme.edge_id) + UNION + SELECT + CASE + WHEN p.dangling AND NOT p.back THEN -p.signed_edge_id + WHEN p.signed_edge_id < 0 THEN p.next_right_edge + ELSE p.next_left_edge + END, -- signed_edge_id + CASE + WHEN p.dangling AND NOT p.back THEN true + ELSE false + END, -- back + + e.lf = e.rf, -- dangling + e.lf, e.rf, + e.next_right_edge, e.next_left_edge + + FROM _edges e, _edgepath p + WHERE + e.edge_id = CASE + WHEN p.dangling AND NOT p.back THEN abs(p.signed_edge_id) + WHEN p.signed_edge_id < 0 THEN abs(p.next_right_edge) + ELSE abs(p.next_left_edge) + END +) +SELECT abs(signed_edge_id) as edge_id, signed_edge_id FROM _edgepath WHERE NOT dangling + LOOP -- }{ + +#ifdef POSTGIS_TOPOLOGY_DEBUG + RAISE DEBUG 'Edge id: %' , rec.signed_edge_id; +#endif IF edgeMapTable IS NOT NULL THEN - sql := 'SELECT arc_id-1 FROM ' || edgeMapTable::text || ' WHERE edge_id = ' || rec2.edge_id; + sql := 'SELECT arc_id-1 FROM ' || edgeMapTable::text || ' WHERE edge_id = ' || rec.edge_id; EXECUTE sql INTO arcid; IF arcid IS NULL THEN EXECUTE 'INSERT INTO ' || edgeMapTable::text - || '(edge_id) VALUES (' || rec2.edge_id || ') RETURNING arc_id-1' + || '(edge_id) VALUES (' || rec.edge_id || ') RETURNING arc_id-1' INTO arcid; END IF; ELSE - arcid := rec2.edge_id; + arcid := rec.edge_id-1; END IF; - -- RAISE DEBUG 'Arc id: %' , arcid; - - -- edge goes in same direction - IF rec2.pos2 < rec2.pos THEN - arcid := -(arcid+1); + -- Swap sign, use two's complement for negative edges + IF rec.signed_edge_id >= 0 THEN + arcid := - ( arcid + 1 ); END IF; - arcs := arcs || arcid; +#ifdef POSTGIS_TOPOLOGY_DEBUG + RAISE DEBUG 'ARC id: %' , arcid; +#endif - END LOOP; + visited_edges := visited_edges || rec.edge_id; + arcs := arcid || arcs; - --RAISE DEBUG 'Ring arcs: %' , arcs; + END LOOP; -- } + +#ifdef POSTGIS_TOPOLOGY_DEBUG + RAISE DEBUG 'ARCS: %' , arcs; +#endif - json := json || '[' || array_to_string(arcs,',') || ']'; + IF arcs IS NULL THEN + EXIT; -- end of loop + END IF; - --RAISE DEBUG 'JSON : %' , json; + arctxt := arctxt || ( '[' || array_to_string(arcs,',') || ']' ); END LOOP; -- } - json := json || ']}'; - RETURN json; + json := json || array_to_string(arctxt, ',') || ']}'; + + EXECUTE 'SET search_path TO ' || old_search_path; + ELSIF tg.type = 4 THEN -- collection RAISE EXCEPTION 'Collection TopoGeometries are not supported by AsTopoJSON'; END IF; - RETURN json; END diff --git a/topology/test/regress/topojson.sql b/topology/test/regress/topojson.sql index 4c50596c9..e723aa779 100644 --- a/topology/test/regress/topojson.sql +++ b/topology/test/regress/topojson.sql @@ -5,25 +5,25 @@ set client_min_messages to WARNING; \i hierarchy.sql --- Lineal non-hierarchical -SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature, NULL) +SELECT 'L1-vanilla', feature_name, topology.AsTopoJSON(feature, NULL) FROM features.city_streets WHERE feature_name IN ('R3', 'R4', 'R1', 'R2' ) ORDER BY feature_name; --- Lineal hierarchical -SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature, NULL) +SELECT 'L2-vanilla', feature_name, topology.AsTopoJSON(feature, NULL) FROM features.big_streets WHERE feature_name IN ('R4', 'R1R2' ) ORDER BY feature_name; --- Areal non-hierarchical -SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature, NULL) +SELECT 'A1-vanilla', feature_name, topology.AsTopoJSON(feature, NULL) FROM features.land_parcels WHERE feature_name IN ('P1', 'P2', 'P3', 'P4', 'P5' ) ORDER BY feature_name; --- Areal hierarchical -SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature) +SELECT 'A2-vanilla', feature_name, topology.AsTopoJSON(feature, NULL) FROM features.big_parcels WHERE feature_name IN ('P1P2', 'P3P4') ORDER BY feature_name; @@ -32,25 +32,28 @@ SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature) CREATE TEMP TABLE edgemap (arc_id serial, edge_id int unique); --- Lineal non-hierarchical -SELECT feature_name||'-edgemap', topology.AsTopoJSON(feature, 'edgemap') +SELECT 'L1-edgemap', feature_name, topology.AsTopoJSON(feature, 'edgemap') FROM features.city_streets WHERE feature_name IN ('R3', 'R4', 'R1', 'R2' ) ORDER BY feature_name; --- Lineal hierarchical -SELECT feature_name||'-edgemap', topology.AsTopoJSON(feature, 'edgemap') +TRUNCATE edgemap; SELECT NULLIF(setval('edgemap_arc_id_seq', 1, false), 1); +SELECT 'L2-edgemap', feature_name, topology.AsTopoJSON(feature, 'edgemap') FROM features.big_streets WHERE feature_name IN ('R4', 'R1R2' ) ORDER BY feature_name; --- Areal non-hierarchical -SELECT feature_name||'-edgemap', topology.AsTopoJSON(feature, 'edgemap') +TRUNCATE edgemap; SELECT NULLIF(setval('edgemap_arc_id_seq', 1, false), 1); +SELECT 'A1-edgemap', feature_name, topology.AsTopoJSON(feature, 'edgemap') FROM features.land_parcels WHERE feature_name IN ('P1', 'P2', 'P3', 'P4', 'P5' ) ORDER BY feature_name; --- Areal hierarchical -SELECT feature_name||'-edgemap', topology.AsTopoJSON(feature, 'edgemap') +TRUNCATE edgemap; SELECT NULLIF(setval('edgemap_arc_id_seq', 1, false), 1); +SELECT 'A2-edgemap', feature_name, topology.AsTopoJSON(feature, 'edgemap') FROM features.big_parcels WHERE feature_name IN ('P1P2', 'P3P4') ORDER BY feature_name; diff --git a/topology/test/regress/topojson_expected b/topology/test/regress/topojson_expected index 2ceb66c39..1037875fe 100644 --- a/topology/test/regress/topojson_expected +++ b/topology/test/regress/topojson_expected @@ -14,29 +14,30 @@ features.big_parcels.the_geom SRID:0 TYPE:MULTIPOLYGON DIMS:2 5 6 features.big_signs.the_geom SRID:0 TYPE:MULTIPOINT DIMS:2 -R1-vanilla|{ "type": "LineString", "arcs": [9,-11]} -R2-vanilla|{ "type": "LineString", "arcs": [4,-6]} -R3-vanilla|{ "type": "LineString", "arcs": [25]} -R4-vanilla|{ "type": "LineString", "arcs": [3]} -R1R2-vanilla|{ "type": "LineString", "arcs": [9,-11,4,-6]} -R4-vanilla|{ "type": "LineString", "arcs": [3]} -P1-vanilla|{ "type": "Polygon", "arcs": [[-21,-13,22,21,6,-20]]} -P2-vanilla|{ "type": "Polygon", "arcs": [[-19,-14,20,19,7,-18]]} -P3-vanilla|{ "type": "Polygon", "arcs": [[-17,-15,18,17,8,-16]]} -P4-vanilla|{ "type": "Polygon", "arcs": [[-3]]} -P5-vanilla|{ "type": "Polygon", "arcs": [[-2][26]]} -ERROR: function topology.astopojson(topogeometry) does not exist at character 34 -R1-edgemap|{ "type": "LineString", "arcs": [0,-2]} -R2-edgemap|{ "type": "LineString", "arcs": [2,-4]} -R3-edgemap|{ "type": "LineString", "arcs": [4]} -R4-edgemap|{ "type": "LineString", "arcs": [5]} -R1R2-edgemap|{ "type": "LineString", "arcs": [0,-2,2,-4]} -R4-edgemap|{ "type": "LineString", "arcs": [5]} -P1-edgemap|{ "type": "Polygon", "arcs": [[-7,-8,8,9,10,-12]]} -P2-edgemap|{ "type": "Polygon", "arcs": [[-13,-14,6,11,14,-16]]} -P3-edgemap|{ "type": "Polygon", "arcs": [[-17,-18,12,15,18,-20]]} -P4-edgemap|{ "type": "Polygon", "arcs": [[-21]]} -P5-edgemap|{ "type": "Polygon", "arcs": [[-22][22]]} -P1P2-edgemap|{ "type": "Polygon", "arcs": [[-8,8,9,10,14,-16,-13,-14]]} -P3P4-edgemap|{ "type": "Polygon", "arcs": [[-17,-18,12,15,18,-20][-21]]} +L1-vanilla|R1|{ "type": "LineString", "arcs": [9,-11]} +L1-vanilla|R2|{ "type": "LineString", "arcs": [4,-6]} +L1-vanilla|R3|{ "type": "LineString", "arcs": [25]} +L1-vanilla|R4|{ "type": "LineString", "arcs": [3]} +L2-vanilla|R1R2|{ "type": "LineString", "arcs": [9,-11,4,-6]} +L2-vanilla|R4|{ "type": "LineString", "arcs": [3]} +A1-vanilla|P1|{ "type": "Polygon", "arcs": [[20,5,-19,-20,-12,21]]} +A1-vanilla|P2|{ "type": "Polygon", "arcs": [[18,6,-17,-18,-13,19]]} +A1-vanilla|P3|{ "type": "Polygon", "arcs": [[16,7,-15,-16,-14,17]]} +A1-vanilla|P4|{ "type": "Polygon", "arcs": [[-2]]} +A1-vanilla|P5|{ "type": "Polygon", "arcs": [[-1],[25]]} +A2-vanilla|P1P2|{ "type": "Polygon", "arcs": [[20,5,6,-17,-18,-13,-12,21]]} +A2-vanilla|P3P4|{ "type": "Polygon", "arcs": [[-2],[16,7,-15,-16,-14,17]]} +L1-edgemap|R1|{ "type": "LineString", "arcs": [0,-2]} +L1-edgemap|R2|{ "type": "LineString", "arcs": [2,-4]} +L1-edgemap|R3|{ "type": "LineString", "arcs": [4]} +L1-edgemap|R4|{ "type": "LineString", "arcs": [5]} +L2-edgemap|R1R2|{ "type": "LineString", "arcs": [0,-2,2,-4]} +L2-edgemap|R4|{ "type": "LineString", "arcs": [4]} +A1-edgemap|P1|{ "type": "Polygon", "arcs": [[5,4,-4,-3,-2,0]]} +A1-edgemap|P2|{ "type": "Polygon", "arcs": [[3,9,-9,-8,-7,2]]} +A1-edgemap|P3|{ "type": "Polygon", "arcs": [[8,13,-13,-12,-11,7]]} +A1-edgemap|P4|{ "type": "Polygon", "arcs": [[-15]]} +A1-edgemap|P5|{ "type": "Polygon", "arcs": [[-16],[16]]} +A2-edgemap|P1P2|{ "type": "Polygon", "arcs": [[7,6,5,-5,-4,-3,-2,0]]} +A2-edgemap|P3P4|{ "type": "Polygon", "arcs": [[-9],[4,12,-12,-11,-10,3]]} Topology 'city_data' dropped