[For some reason I can't get the command and config excerpts below to look like I want, which is basically what I'd get if I put them in a <pre> block. I've done the best I can.]
I've been getting back to map rendering, and one of the things that bothers me about pretty much everyone's rendering is that they either don't render US route shields or they go through hackery (in my opinion) to turn road ref tags into a shield plus a number.
I've wanted for a while to use the data in route relations for shield rendering. At least in the US, they use separate tags for network and route number, which makes picking the right shield easier. They also handle the case of a single road belonging to multiple routes better than the road's ref tag does.
The drawbacks are in the way that osm2pgsql puts relations into the database. When a way is a road and a member of a route relation, osm2pgsql creates one row with the road's tags and a separate row with the same path as the road but the tags from the relation. As far as I can tell, there's no simple link between them. This means that you can't use the road's highway key to determine visibility of the shield, which means, among other things, that you have to possibility of a shield being rendered at a lower zoom level than its road.
If the data was imported in slim mode, the planet_osm_rels table does contain a link between a road and its relations. I tried using it directly, but that proved to be too slow for everyday use. I ended up creating another table and running queries off of that. The rest of this diary is about what I did.
My specific requirements were: if a road is a member of a correctly-tagged route relation, use the appropriate shield with the relation's ref tag. If a road is a member of an incorrectly-tagged route relation, show the relation's ref tag on a generic shield. If the road is not a member of any route relations but has its own ref tag, show that on a generic shield. Highway names are not relevant to this rendering; they can be rendered from the planet_osm_line table directly. A nice additional feature would be if contiguous ways with the same route membership were aggregated before Mapnik saw them so the shields were evenly spaced along the way. (The normal rendering tends to bunch up shields (and names) along short ways like bridges and tunnels.)
With that in mind, I made a table with the absolute minimum amount of information necessary:
CREATE TABLE planet_osm_route (
highway TEXT,
road_ref TEXT,
network TEXT,
route_ref TEXT,
way GEOMETRY);
Next was populating it with data from the osm2pgsql import. The following statement takes all the roads that are in route relations, cross joins each road with each relation it's in, groups them together by route and original road ref, then groups them again just by route. The reason for the double grouping is that I throw all the original ref tags into the road_ref field and I'd like the minimize the duplication there. I use a chain of PostGIS functions to group all the road LINESTRINGs into a MULTILINESTRING, merge contiguous lines together within the MULTILINESTRING, and then split the resulting geometry up into separate LINESTRINGs again. This is somewhat resource intensive, but I really like the results.
INSERT INTO planet_osm_route
SELECT highway, string_agg(road_ref, ';') AS road_ref, network, route_ref,
(ST_Dump(ST_LineMerge(ST_CollectionExtract(ST_Collect(way),2)))).geom AS way
FROM (SELECT road.highway, road.ref AS road_ref,
route.network, route.ref AS route_ref, route.osm_id,
ST_Collect(road.way) AS way
FROM planet_osm_line AS road
JOIN (SELECT id, unnest(parts) AS part
FROM planet_osm_rels) AS rels
ON road.osm_id = rels.part
JOIN (SELECT DISTINCT ref, osm_id, network FROM planet_osm_line) AS route
ON -rels.id = route.osm_id
WHERE road.highway IS NOT NULL
AND route.ref IS NOT NULL AND route.network IS NOT NULL
GROUP BY route.osm_id, road.highway, road.ref, route.network,
route.ref) AS route_by_ref
GROUP BY osm_id, highway, network, route_ref
ORDER BY way;
That took about four hours for a US extract on my system, despite a bunch of things that were running and maxing out the disks to begin with.
The next step is to add in the roads that aren't in route relations. I did the same dance with the ways to get them as combined as possible.
INSERT INTO planet_osm_route
SELECT road.highway, road.ref AS road_ref,
route.network, route.ref AS route_ref,
(ST_Dump(ST_LineMerge(ST_Collect(road.way)))).geom AS way
FROM planet_osm_line AS road
LEFT JOIN (SELECT id, unnest(parts) AS part
FROM planet_osm_rels) AS rels
ON road.osm_id = rels.part
LEFT JOIN planet_osm_line route
ON -rels.id = route.osm_id
WHERE road.highway IS NOT NULL
AND road.ref IS NOT NULL
AND (route.ref IS NULL OR route.network IS NULL)
GROUP BY road.highway, road.ref, route.network, route.ref
ORDER BY way;
That took about half an hour on my system.
Finally, I added the necessary index on the table. (I did this last at the recommendation of pretty much every PostgreSQL document that mentions bulk imports.)
CREATE INDEX planet_osm_route_index ON planet_osm_route USING GIST (way GIST_GEOMETRY_OPS)
That took about an hour.
Those are really the hard bits. Setting up the rendering was easier.
For the shields, I grabbed blank Interstate, US, and generic state shields from Wikipedia (which has them under a public domain license) and rendered the SVGs to PNGs at a height of 24 pixels. At that height, the numbers for interstates should be 10 points tall and the US highways should be 12 points tall.
The Mapnik stylesheets should be pretty obvious from here, but I'll give an overview of them anyway. For clarity's sake, I'll cut out the rules for selective rendering based on highway type, and some of the more repetitive bits.
<Style name="interstateshields">
<Rule>
<Filter>[route_len] = 1 or [route_len] = 2</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/I-blank.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[route_len] = 3</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/I-blank_wide.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[route_len] > 3</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/shield-black-8.png"
type="png" min_distance="45" spacing="750" />
</Rule>
</Style>
<Style name="usshields">
<Rule>
<Filter>[route_len] = 1 or [route_len] = 2</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="12"
fill="black" placement="line"
file="symbols/US_blank.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[route_len] = 3</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="12"
fill="black" placement="line"
file="symbols/US_blank_wide.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[route_len] > 3</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/shield-black-8.png"
type="png" min_distance="45" spacing="750" />
</Rule>
</Style>
<Style name="stateshields">
<Rule>
<Filter>[route_len] = 1 or [route_len] = 2</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="12"
fill="black" placement="line"
file="symbols/State_blank.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[route_len] = 3</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="10"
fill="black" placement="line"
file="symbols/State_blank.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[route_len] > 3</Filter>
<ShieldSymbolizer name="route_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/shield-black-8.png"
type="png" min_distance="45" spacing="750" />
</Rule>
</Style>
<Style name="nonetworkroads">
<Rule>
<Filter>[road_len] = 1 or [road_len] = 2</Filter>
<ShieldSymbolizer name="road_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/shield-black-2.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[road_len] = 3</Filter>
<ShieldSymbolizer name="road_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/shield-black-3.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<Rule>
<Filter>[road_len] = 4</Filter>
<ShieldSymbolizer name="road_ref" face_name="DejaVu Sans Bold" size="10"
fill="white" placement="line"
file="symbols/shield-black-4.png"
type="png" min_distance="45" spacing="750" />
</Rule>
<!-- And so on... -->
</Style>
<Layer name="interstateshields" status="on">
<StyleName>interstateshields</StyleName>
<Datasource>
&dbsettings;
&extents;
<Parameter name="table">
(select highway, route_ref, length(route_ref) AS route_len, way
from planet_osm_route
where highway IS NOT NULL and network = 'US:I'
) as interstateshields
</Parameter>
</Datasource>
</Layer>
<Layer name="usshields" status="on">
<StyleName>usshields</StyleName>
<Datasource>
&dbsettings;
&extents;
<Parameter name="table">
(select highway, route_ref, length(route_ref) AS route_len, way
from planet_osm_route
where highway IS NOT NULL and network IN ('US:US', 'US')
) as usshields
</Parameter>
</Datasource>
</Layer>
<Layer name="stateshields" status="on">
<StyleName>stateshields</StyleName>
<Datasource>
&dbsettings;
&extents;
<!-- Add other states as needed. -->
<Parameter name="table">
(select highway, route_ref, length(route_ref) AS route_len, way
from planet_osm_route
where highway IS NOT NULL and network IN ('US:MD', 'US:PA', 'US:DE', 'US:VA', 'US:WV')
) as stateshields
</Parameter>
</Datasource>
</Layer>
<Layer name="nonetworkroads" status="on">
<StyleName>nonetworkroads</StyleName>
<Datasource>
&dbsettings;
&extents;
<Parameter name="table">
(select highway, road_ref, length(road_ref) AS road_len, way
from planet_osm_route
where highway IS NOT NULL and network IS NULL
) as nonetworkroads
</Parameter>
</Datasource>
</Layer>
The rendered result has been everything I've wanted in shield rendering, and all i need to do is spend about six hours dumping and reloading the table every time I do a batch of updates to my database.
I hope other people will find some utility in the work I've done.
Edit: By request, a screenshot that should illustrate several of the different classes of routes I'm mapping.
This rendering has I-70 (an Interstate with a route relation), US 40 (a US highway with a route relation), US 29 (a US highway with a route relation whose ref tag is incorrect), MD 108 (a state road with a route relation), and MD 103 and MD 104 (state roads without route relations but with ref tags on the roads). MD 100 (a state road without a route relation) is also in the rendering, but Mapnik puts its closest shield just off the image to the lower right.