- #2210, ST_MinConvexHull(raster)
- lwgeom_from_geojson in liblwgeom (Sandro Santilli / Vizzuality)
- #1687, ST_Simplify for TopoGeometry (Sandro Santilli / Vizzuality)
+ - #2228, TopoJSON output for TopoGeometry (Sandro Santilli / Vizzuality)
* Enhancements *
- #823, tiger geocoder: Make loader_generate_script download portion
<para><xref linkend="CreateTopoGeom"/>, <xref linkend="ST_CreateTopoGeo" /></para>
</refsection>
</refentry>
+ <refentry id="AsTopoJSON">
+ <refnamediv>
+ <refname>AsTopoJSON</refname>
+
+ <refpurpose>Returns the TopoJSON representation of a topogeometry.</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <funcsynopsis>
+ <funcprototype>
+ <funcdef>text <function>AsTopoJSON</function></funcdef>
+ <paramdef><type>topogeometry </type> <parameter>tg</parameter></paramdef>
+ <paramdef><type>regclass </type> <parameter>edgeMapTable</parameter></paramdef>
+ </funcprototype>
+ </funcsynopsis>
+ </refsynopsisdiv>
+
+ <refsection>
+ <title>Description</title>
+
+ <para>Returns the TopoJSON representation of a topogeometry. If <varname>edgeMapTable</varname> is not null, it will be used as a lookup/storage mapping of edge identifiers to arc indices. This is to be able to allow for a compact "arcs" array in the final document.
+</para>
+
+<para>
+The table, if given, is expected to have an "arc_id" field of type "serial" and an "edge_id" of type integer; the code will query the table for "edge_id" so it is recommended to add an index on that field.
+</para>
+
+ <para>
+A full TopoJSON document will be need to contain, in addition to the snippets returned by this function, the actual arcs plus some headers. See the <ulink url="http://github.com/mbostock/topojson/wiki/Specification">TopoJSON specification</ulink>.
+ </para>
+
+ <!-- use this format if new function -->
+ <para>Availability: 2.1.0 </para>
+ </refsection>
+
+
+ <!-- Optionally add a "See Also" section -->
+ <refsection>
+ <title>See Also</title>
+ <para><xref linkend="ST_AsGeoJSON" /></para>
+ </refsection>
+
+ <refsection>
+ <title>Examples</title>
+<programlisting>
+CREATE TEMP TABLE edgemap(arc_id serial, edge_id int unique);
+
+-- header
+SELECT '{ "type": "Topology", "transform": { "scale": [1,1], "translate": [0,0] }, "objects": {';
+
+-- objects
+SELECT '"' || feature_name || '": ' || AsTopoJSON(feature, 'edgemap')
+FROM features.land_parcels;
+
+-- arcs
+SELECT '}, "arcs": ['
+ UNION ALL
+SELECT (regexp_matches(ST_AsGEOJSON(ST_SnapToGrid(e.geom,1)), '\[.*\]'))[1] as t
+FROM edgemap m, city_data.edge e WHERE e.edge_id = m.edge_id;
+
+-- footer
+SELECT ']}'::text as t
+
+-- Result:
+{ "type": "Topology", "transform": { "scale": [1,1], "translate": [0,0] }, "objects": {
+"P1": { "type": "Polygon", "arcs": [[-1,-2,2,3,4,-6]]}
+"P2": { "type": "Polygon", "arcs": [[-7,-8,0,5,8,-10]]}
+"P3": { "type": "Polygon", "arcs": [[-11,-12,6,9,12,-14]]}
+"P4": { "type": "Polygon", "arcs": [[-15]]}
+"P5": { "type": "Polygon", "arcs": [[-16][16]]}
+"F3": { "type": "Polygon", "arcs": [[4,-6,-18,3]]}
+"F6": { "type": "Polygon", "arcs": [[17,-1,-2,2]]}
+"F3F4": { "type": "Polygon", "arcs": [[4,8,-10,18,-18,3]]}
+"F1": { "type": "Polygon", "arcs": [[-16][16]]}
+}, "arcs": [
+[[21,6],[21,14]]
+[[9,6],[21,6]]
+[[9,6],[9,14]]
+[[9,14],[9,22]]
+[[9,22],[21,22]]
+[[21,14],[21,22]]
+[[35,6],[35,14]]
+[[21,6],[35,6]]
+[[21,22],[35,22]]
+[[35,14],[35,22]]
+[[47,6],[47,14]]
+[[35,6],[47,6]]
+[[35,22],[47,22]]
+[[47,14],[47,22]]
+[[25,30],[31,30],[31,40],[17,40],[17,30],[25,30]]
+[[8,30],[16,30],[16,38],[3,38],[3,30],[8,30]]
+[[4,31],[7,31],[7,34],[4,34],[4,31]]
+[[9,14],[21,14]]
+[[35,14],[21,14]]
+]}
+</programlisting>
+ </refsection>
+ </refentry>
</sect1>
</chapter>
--- /dev/null
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- PostGIS - Spatial Types for PostgreSQL
+-- http://postgis.refractions.net
+--
+-- Copyright (C) 2013 Sandro Santilli <strk@keybit.net>
+--
+-- This is free software; you can redistribute and/or modify it under
+-- the terms of the GNU General Public Licence. See the COPYING file.
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+--
+-- Functions used for TopoJSON export
+--
+-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+--{
+--
+-- API FUNCTION
+--
+-- text AsTopoJSON(TopoGeometry, edgeMapTable)
+--
+-- }{
+CREATE OR REPLACE FUNCTION topology.AsTopoJSON(tg topology.TopoGeometry, edgeMapTable regclass)
+ RETURNS text AS
+$$
+DECLARE
+ visited bool;
+ toponame text;
+ json text;
+ sql text;
+ bounds GEOMETRY;
+ rec RECORD;
+ rec2 RECORD;
+ side int;
+ arcid int;
+ arcs int[];
+BEGIN
+
+ IF tg IS NULL THEN
+ RETURN NULL;
+ END IF;
+
+ -- Get topology name (for subsequent queries)
+ SELECT name FROM topology.topology into toponame
+ WHERE id = tg.topology_id;
+
+ -- Puntual TopoGeometry
+ IF tg.type = 1 THEN
+ -- TODO: implement scale ?
+ --json := ST_AsGeoJSON(topology.Geometry(tg));
+ --return json;
+ RAISE EXCEPTION 'TopoJSON export does not support puntual objects';
+ ELSIF tg.type = 2 THEN -- lineal
+
+ FOR rec IN SELECT (ST_Dump(topology.Geometry(tg))).geom
+ LOOP -- {
+
+ sql := 'SELECT e.*, ST_Line_Locate_Point('
+ || quote_literal(rec.geom::text)
+ || ', ST_Line_Interpolate_Point(e.geom, 0.2)) as pos'
+ || ', ST_Line_Locate_Point('
+ || quote_literal(rec.geom::text)
+ || ', ST_Line_Interpolate_Point(e.geom, 0.8)) as pos2 FROM '
+ || quote_ident(toponame)
+ || '.edge e WHERE ST_Covers('
+ || quote_literal(rec.geom::text)
+ || ', e.geom) ORDER BY pos';
+ -- TODO: add relation to the conditional, to reduce load ?
+ FOR rec2 IN EXECUTE sql
+ LOOP -- {
+
+ IF edgeMapTable IS NOT NULL THEN
+ sql := 'SELECT arc_id-1 FROM ' || edgeMapTable::text || ' WHERE edge_id = ' || rec2.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'
+ INTO arcid;
+ END IF;
+ ELSE
+ arcid := rec2.edge_id;
+ END IF;
+
+ -- edge goes in opposite direction
+ IF rec2.pos2 < rec2.pos THEN
+ arcid := -(arcid+1);
+ END IF;
+
+ arcs := arcs || arcid;
+
+ END LOOP; -- }
+ END LOOP; -- }
+
+ json := '{ "type": "LineString", "arcs": [' || array_to_string(arcs,',') || ']}';
+
+ return json;
+
+ ELSIF tg.type = 3 THEN -- areal
+
+ json := '{ "type": "Polygon", "arcs": [';
+
+ FOR rec IN SELECT (ST_DumpRings((ST_Dump(ST_ForceRHR(
+ topology.Geometry(tg)))).geom)).geom
+ 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
+
+ IF edgeMapTable IS NOT NULL THEN
+ sql := 'SELECT arc_id-1 FROM ' || edgeMapTable::text || ' WHERE edge_id = ' || rec2.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'
+ INTO arcid;
+ END IF;
+ ELSE
+ arcid := rec2.edge_id;
+ END IF;
+
+ -- RAISE DEBUG 'Arc id: %' , arcid;
+
+ -- edge goes in same direction
+ IF rec2.pos2 < rec2.pos THEN
+ arcid := -(arcid+1);
+ END IF;
+
+ arcs := arcs || arcid;
+
+ END LOOP;
+
+ --RAISE DEBUG 'Ring arcs: %' , arcs;
+
+ json := json || '[' || array_to_string(arcs,',') || ']';
+
+ --RAISE DEBUG 'JSON : %' , json;
+
+ END LOOP; -- }
+
+ json := json || ']}';
+ RETURN json;
+
+ ELSIF tg.type = 4 THEN -- collection
+ RAISE EXCEPTION 'Collection TopoGeometries are not supported by AsTopoJSON';
+
+ END IF;
+
+
+ RETURN json;
+
+END
+$$ LANGUAGE 'plpgsql' VOLATILE; -- writes into visited table
+-- } AsTopoJSON(TopoGeometry, visited_table)
+
regress/topogeo_addpoint.sql \
regress/topogeo_addpolygon.sql \
regress/topogeometry_type.sql \
+ regress/topojson.sql \
regress/topo2.5d.sql \
regress/totopogeom.sql \
regress/droptopology.sql \
--- /dev/null
+set client_min_messages to WARNING;
+
+\i load_topology.sql
+\i load_features.sql
+\i hierarchy.sql
+
+--- Lineal non-hierarchical
+SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature)
+ 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)
+ FROM features.big_streets
+ WHERE feature_name IN ('R4', 'R1R2' )
+ ORDER BY feature_name;
+
+--- Areal non-hierarchical
+SELECT feature_name||'-vanilla', topology.AsTopoJSON(feature)
+ 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)
+ FROM features.big_parcels
+ WHERE feature_name IN ('P1P2', 'P3P4')
+ ORDER BY feature_name;
+
+-- Now again with edge mapping {
+CREATE TEMP TABLE edgemap (arc_id serial, edge_id int unique);
+
+--- Lineal non-hierarchical
+SELECT feature_name||'-vanilla', 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||'-vanilla', topology.AsTopoJSON(feature, 'edgemap')
+ FROM features.big_streets
+ WHERE feature_name IN ('R4', 'R1R2' )
+ ORDER BY feature_name;
+
+--- Areal non-hierarchical
+SELECT feature_name||'-vanilla', 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||'-vanilla', topology.AsTopoJSON(feature, 'edgemap')
+ FROM features.big_parcels
+ WHERE feature_name IN ('P1P2', 'P3P4')
+ ORDER BY feature_name;
+
+DROP TABLE edgemap;
+-- End edge mapping }
+
+
+SELECT topology.DropTopology('city_data');
+DROP SCHEMA features CASCADE;
#include "sql/topogeometry/simplify.sql.in"
#include "sql/topogeometry/totopogeom.sql.in"
--- GML
+-- Exports
#include "sql/export/gml.sql.in"
+#include "sql/export/TopoJSON.sql.in"
--=} POSTGIS-SPECIFIC block