How to render an SVG from PostGIS using ST_AsSVG
The Postgis function ST_AsSVG simply converts geometries into their SVG point or path equivalents, and does not allow you to add any attributes/styling directly. For example, a query with a Point, Linestring and Polygon:
WITH shapes (geom, attribute) AS
(VALUES(
(SELECT ST_MakePoint(0,0)),1),
((ST_MakeLine(ST_MakePoint(0,0), ST_MakePoint(10,10))), 2),
((SELECT ST_MakeBox2d(ST_MakePoint(0,0), ST_MakePoint(10,10))), 3)
)
SELECT ST_AsSVG(geom) from shapes;
returns:
cx="0" cy="0"
M 0 0 L 10 -10
M 0 0 L 0 -10 10 -10 10 0 Z
which is a point, an unclosed linestring and a closed line, ie, a polygon. M means move to, L means line to and Z means close. There are other geometry types in SVG, but this is what ST_AsSVG returns (see docs for the Multi version of the same).
By itself, this isn't very helpful, as you are missing the path tags
and any styling information, a full example of which would look something like:
<path d="M 175 200 l 150 0" stroke="green" stroke-width="3" fill="none" />
<path d="M 100 350 q 150 -300 300 0" stroke="blue" stroke-width="5" fill="none" />
See w3schools SVG Tutorial for more information.
So, if you want to actually create valid SVG using a Postgis query, you will need to add path tags and color, line, fill, etc formatting. This can be accomplished by using the concat operator
and a case statement
to set the formatting. For example,
WITH shapes (geom, attribute) AS (
VALUES(
(SELECT ST_MakeLine(ST_MakePoint(0,0), ST_MakePoint(10,10))), 2),
((SELECT ST_Envelope(ST_MakeBox2d(ST_MakePoint(0,0), ST_MakePoint(10,10)))), 3)
)
SELECT
concat(
'<path d= "',
ST_AsSVG(geom,1), '" ',
CASE WHEN attribute = 0 THEN 'stroke="red" stroke-width="3" fill="none"'
ELSE 'stroke="black" stroke-width="2" fill="green"' END,
' />')
FROM shapes;
which returns:
<path d= "M 0 0 l 10 -10" stroke="black" stroke-width="2" fill="green" />
<path d= "M 0 0 l 0 -10 10 0 0 10 z" stroke="black" stroke-width="2" fill="green" />
Finally, if you wrap all of that in array_to_string
and array_agg
, and add the head and footer, ie, the <svg height="400" width="450">
and </svg>
, you will end up with one block of SVG representing all your geometries, eg,
WITH shapes (geom, attribute) AS (
VALUES(
(SELECT ST_MakeLine(ST_MakePoint(0,0), ST_MakePoint(10,10))), 2),
((SELECT ST_Envelope(ST_MakeBox2d(ST_MakePoint(0,0), st_makepoint(10,10)))), 3)
),
paths (svg) as (
SELECT concat(
'<path d= "',
ST_AsSVG(geom,1), '" ',
CASE WHEN attribute = 0 THEN 'stroke="red" stroke-width="3" fill="none"'
ELSE 'stroke="black" stroke-width="2" fill="green"' END,
' />')
FROM shapes
)
SELECT concat(
'<svg height="400" width="450">',
array_to_string(array_agg(svg),''),
'</svg>')
FROM paths;
which now returns the single record:
<svg height="400" width="450"><path d= "M 0 0 l 10 -10" stroke="black" stroke-width="2" fill="green" /><path d= "M 0 0 l 0 -10 10 0 0 10 z" stroke="black" stroke-width="2" fill="green" /></svg>
TL;DR, other options
At which point you are probably thinking, TL;DR, and it is true this is a lot of work, and the case statements to make the stroke color, width, etc, could get very long and unwieldy.
There are other options. Geoserver (with a Postgis/vector data source) supports SVG as an output format and you can use SLDs or CartoCSS within Geoserver to style them up, then just request a WMS with SVG as output format and you are done. As @Scro also said, you can use tools like TileMill to do the styling for you (not tested). So, after a very long answer, I am basically suggesting that you should not use ST_AsSVG directly for anything other than very quick and dirty SVGs.