Since we're consuming vectors in a browser, let's have a look at TopoJSON too. TopoJSON is an extension of GeoJSON--the main difference is it encodes geometries shared by multiple features only once, so it is possible to reduce the footprint of the returned data. It is a popular format when it comes to serving vector tiles too. We will not get into detail on how to set up our own tile server; instead we will make our PostGIS output the data for us.
Let's get some data into the database first: download http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip and load it into the webgis.countries table:
shp2pgsql -s 4326 ne_50m_admin_0_countries webgis.countries | psql -h localhost -p 5434 -U postgres -d mastering_postgis
We will need to process the data before we can use the topology.AsTopoJSON function because it accepts topo geometry. So let's do the data preparation first:
--creates a new topology schema and registers it in the topology.topology table
select topology.CreateTopology('topo_countries', 4326);
--a new table that will hold topogeom
create table webgis.countries_topo(git serial primary key, country varchar);
--add topogeom col to a table and registers it as a layer in the topology.layer table
select topology.AddTopoGeometryColumn('topo_countries', 'webgis', 'countries_topo', 'topo', 'MULTIPOLYGON');
--this will convert the original country geoms into topo geoms
insert into webgis.countries_topo (country, topo)
select
name,
topology.toTopoGeom(
geom,
'topo_countries',
--third param is topology layer identifier; we obtain it automatically based on our topology name
(select layer_id from topology.layer where schema_name = 'webgis' and table_name = 'countries_topo' limit 1),
0.00001 --note: precision param. needed, so we avoid problems with invlaid geoms
)
from
webgis.countries;
The preceding query simply prepares a new table with topo geometry based on the original geometry imported from a shapefile. Once our topo geometry is ready, we can move on to generating TopoJSON.
The following code is a simple adaptation of the example presented on the AsTopoJSON function documentation page (http://postgis.net/docs/manual-dev/AsTopoJSON.html). It adds a file export and does some cleanup too, so we do not leave the mess behind:
DROP TABLE IF EXISTS edgemap;
CREATE TEMP TABLE edgemap(arc_id serial, edge_id int unique);
DROP TABLE IF EXISTS topojson;
CREATE TEMP TABLE topojson(json_parts varchar);
INSERT INTO topojson
-- header
SELECT '{ "type": "Topology", "transform": { "scale": [1,1], "translate": [0,0] }, "objects": {'
-- objects already stitched together
UNION ALL select array_to_string( array_agg(json_parts), E', ')
from (SELECT '"' || country || '": ' || topology.AsTopoJSON(topo, 'edgemap') as json_parts
FROM webgis.countries_topo) as json_parts;
-- arcs
WITH edges AS (
SELECT m.arc_id, e.geom FROM edgemap m, topo_countries.edge e
WHERE e.edge_id = m.edge_id
), points AS (
SELECT arc_id, (st_dumppoints(geom)).* FROM edges
), compare AS (
SELECT p2.arc_id,
CASE WHEN p1.path IS NULL THEN p2.geom
ELSE ST_Translate(p2.geom, -ST_X(p1.geom), -ST_Y(p1.geom))
END AS geom
FROM points p2 LEFT OUTER JOIN points p1
ON ( p1.arc_id = p2.arc_id AND p2.path[1] = p1.path[1]+1 )
ORDER BY arc_id, p2.path
), arcsdump AS (
SELECT arc_id, (regexp_matches( ST_AsGeoJSON(geom), '\[.*\]'))[1] as t
FROM compare
), arcs AS (
SELECT arc_id, '[' || array_to_string(array_agg(t), ',') || ']' as a FROM arcsdump
GROUP BY arc_id
ORDER BY arc_id
)
INSERT INTO topojson
SELECT '}, "arcs": [' UNION ALL
SELECT array_to_string(array_agg(a), E', ') from arcs
-- json footer part
UNION ALL SELECT ']}'::text;
--finally dump the topojson
COPY (SELECT array_to_string( array_agg(json_parts), E' ') FROM (SELECT json_parts FROM topojson) AS json_parts) TO 'f:\topojson.json';
--cleanup the temp stuff
DROP TABLE IF EXISTS edgemap;
DROP TABLE IF EXISTS topojson;
At this stage, our TopoJSON file should be ready, so the final thing is to display it on a map.