The problem
I’ve been having a little poke around with Mapnik today (awesome software!). One of the things on my todo list has been to sort out rendering issues with roads we have been having. Our last iteration described roads something like this:
A style…
<Style name="Freeway30th_style">
<Rule>
<LineSymbolizer>
<CssParameter name="stroke">rgb(169,170,153)</CssParameter>
<CssParameter name="stroke-width">12.26</CssParameter>
<CssParameter name="stroke-linejoin">bevel</CssParameter>
<CssParameter name="stroke-linecap">round</CssParameter>
<CssParameter name="stroke-opacity">1</CssParameter>
</LineSymbolizer>
<LineSymbolizer>
<CssParameter name="stroke">rgb(255,172,88)</CssParameter>
<CssParameter name="stroke-width">12.16</CssParameter>
<CssParameter name="stroke-linejoin">miter</CssParameter>
<CssParameter name="stroke-linecap">round</CssParameter>
</LineSymbolizer>
</Rule>
</Style>
…and this layer definition…
<Layer name="Freeway30th" srs="+init=epsg:&srid;" maxzoom="39105.90277777778">
<StyleName>Freeway30th_style</StyleName>
<Datasource>
<Parameter name="dbname">&dbname;</Parameter>
<Parameter name="estimate_extent">0</Parameter>
<Parameter name="extent">&extent;</Parameter>
<Parameter name="geometry_field">&geometry_field;</Parameter>
<Parameter name="host">&host;</Parameter>
<Parameter name="password">&password;</Parameter>
<Parameter name="port">&port;</Parameter>
<Parameter name="srid">&srid;</Parameter>
<Parameter name="table">(SELECT * FROM "l_roads" WHERE "type" = \
'Freeway' ORDER BY LENGTH(&geometry_field;) DESC) as "l_roads"</Parameter>
<Parameter name="type">&datasourcetype;</Parameter>
<Parameter name="user">&password;</Parameter>
</Datasource>
</Layer>
With the idea being to render freeways with a gray outline and orange center. Unfortunately, it doesnt produce good results:
The problem being those little line ends you see making gray splodges at the end of each segment.
The solution
Michael Migurski’s blog discusses this issue a little in this article but doesnt directly explain how to achieve the desired effect. So here is what you do:
First the styles are split into two…
<Style name="Freeway30th_style-bottom">
<Rule>
<LineSymbolizer>
<CssParameter name="stroke">rgb(169,170,153)</CssParameter>
<CssParameter name="stroke-width">12.26</CssParameter>
<CssParameter name="stroke-linejoin">bevel</CssParameter>
<CssParameter name="stroke-linecap">round</CssParameter>
<CssParameter name="stroke-opacity">1</CssParameter>
</LineSymbolizer>
</Rule>
</Style>
<Style name="Freeway30th_style-top">
<Rule>
<LineSymbolizer>
<CssParameter name="stroke">rgb(255,172,88)</CssParameter>
<CssParameter name="stroke-width">12.16</CssParameter>
<CssParameter name="stroke-linejoin">miter</CssParameter>
<CssParameter name="stroke-linecap">round</CssParameter>
</LineSymbolizer>
</Rule>
</Style>
and then the layer is now rendered as two layers, the bottom layer first, then the top:
<Layer name="Freeway30th-bottom" srs="+init=epsg:&srid;" maxzoom="39105.90277777778">
<StyleName>Freeway30th_style-bottom</StyleName>
<Datasource>
<Parameter name="dbname">&dbname;</Parameter>
<Parameter name="estimate_extent">0</Parameter>
<Parameter name="extent">&extent;</Parameter>
<Parameter name="geometry_field">&geometry_field;</Parameter>
<Parameter name="host">&host;</Parameter>
<Parameter name="password">&password;</Parameter>
<Parameter name="port">&port;</Parameter>
<Parameter name="srid">&srid;</Parameter>
<Parameter name="table">(SELECT * FROM "l_roads" WHERE "type" = \
'Freeway' ORDER BY LENGTH(&geometry_field;) DESC) as "l_roads"</Parameter>
<Parameter name="type">&datasourcetype;</Parameter>
<Parameter name="user">&password;</Parameter>
</Datasource>
</Layer>
<Layer name="Freeway30th-top" srs="+init=epsg:&srid;" maxzoom="39105.90277777778">
<StyleName>Freeway30th_style-top</StyleName>
<Datasource>
<Parameter name="dbname">&dbname;</Parameter>
<Parameter name="estimate_extent">0</Parameter>
<Parameter name="extent">&extent;</Parameter>
<Parameter name="geometry_field">&geometry_field;</Parameter>
<Parameter name="host">&host;</Parameter>
<Parameter name="password">&password;</Parameter>
<Parameter name="port">&port;</Parameter>
<Parameter name="srid">&srid;</Parameter>
<Parameter name="table">(SELECT * FROM "l_roads" WHERE "type" = \
'Freeway' ORDER BY LENGTH(&geometry_field;) DESC) as "l_roads"</Parameter>
<Parameter name="type">&datasourcetype;</Parameter>
<Parameter name="user">&password;</Parameter>
</Datasource>
</Layer>
A much cleaner rendering!
Note
This approach consumes more cpu time and hits your database harder than the ‘messier’ approach shown first.
Also you can see in the example above, I have adopted Michaels approach of rendering long lines first.
Have fun with your mapnik maps!


Hi Tim,
Glad you are having fun with road casings in mapnik. One thing to note: you can (and should) attach both styles to one layer (this is the multiple styles/rules per layer concept of SLD that I’d like to see in QGIS). This way you don’t have to duplicate the layer definition in XML and (available in Mapnik trunk) the features that are pulled from your database will be intelligently cached in memory to be re-used for each style. This should be substantially faster than hitting the datasource twice.
You can see an example of road casings in the 2010 FOSS4G benchmarks stylesheet too (look for the “motorway” layer): http://svn.osgeo.org/osgeo/foss4g/benchmarking/mapnik/2010/stylesheets/spain-vector.xml
Hi Dane
Ok so with trunk I can have a single layer definition like this:
<Layer name=”Freeway30th” srs=”+init=epsg:&srid;” maxzoom=”39105.90277777778″>
<StyleName>Freeway30th_style-bottom</StyleName>
<StyleName>Freeway30th_style-top</StyleName>
<Datasource>
<Parameter name=”dbname”>&dbname;</Parameter>
<Parameter name=”estimate_extent”>0</Parameter>
<Parameter name=”extent”>&extent;</Parameter>
<Parameter name=”geometry_field”>&geometry_field;</Parameter>
<Parameter name=”host”>&host;</Parameter>
<Parameter name=”password”>&password;</Parameter>
<Parameter name=”port”>&port;</Parameter>
<Parameter name=”srid”>&srid;</Parameter>
<Parameter name=”table”>(SELECT * FROM “l_roads” WHERE “type” = \
‘Freeway’ ORDER BY LENGTH(&geometry_field;) DESC) as “l_roads”</Parameter>
<Parameter name=”type”>&datasourcetype;</Parameter>
<Parameter name=”user”>&password;</Parameter>
</Datasource>
</Layer>
I tested it with the 0.7 version of mapnik provided in Ubuntu 10.04 and this syntax is not useable yet, I’ll look forward to using it when the current stuff in svn makes it way into apt!
Regards
Tim
That syntax should work wth any Mapnik version – it has been supported since the beginning. What error did you get? Email me if that is easier (dane@dbsgeo.com). The only thing specific to trunk is that that syntax will be faster when rendering than previous Mapnik versions.