Skip to content
This repository was archived by the owner on Aug 27, 2023. It is now read-only.

Commit 9597039

Browse files
committed
First release of experimental PDF renderer
0 parents  commit 9597039

18 files changed

+19667
-0
lines changed

README.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
MapPDF - Ruby map PDF renderer
2+
==============================
3+
4+
This is an experimental PDF renderer from MapCSS stylesheets.
5+
6+
== Dependencies ==
7+
8+
* Jochen Topf's OSMlib (http://osmlib.rubyforge.org/)
9+
* Prawn (http://prawn.majesticseacreature.com/)
10+
* Ruby MapCSS parser (https://github.com/systemed/mapcss_ruby)
11+
* RQuad (https://github.com/iterationlabs/rquad)
12+
13+
NOTE: current Prawn will not work perfectly. You need a slight patch to lib/prawn/images.rb -
14+
see comments in lib/pdf_renderer/point_item.rb. You'll still get a map without this patch,
15+
but the icons will be offset.
16+
17+
== How to use ==
18+
19+
See pdf_test.rb. In short:
20+
21+
1. (OSMlib) Read your OSM data into a database
22+
2. (mapcss_ruby) Create a 'parent objects' dictionary
23+
3. (mapcss_ruby) Read the MapCSS file into a RuleSet
24+
4. (mappdf_ruby) Create a MapSpec with the bounding box and map area parameters
25+
5. (Prawn) Create a PDF
26+
6. (mappdf_ruby) Tell the MapSpec to draw onto it
27+
28+
== To do ==
29+
30+
* Dashes for casing
31+
* Dash decoration
32+
* Better text offset
33+
* Bold, italic, underline
34+
* Labels for areas
35+
36+
== Licence and author ==
37+
38+
WTFPL. You can do whatever the fuck you want with this code. Code by Richard Fairhurst, autumn 2011.
39+
40+
OpenStreetMap data by OpenStreetMap contributors (CC-BY-SA).

charlbury.osm

Lines changed: 18784 additions & 0 deletions
Large diffs are not rendered by default.

icons/parking_cycle.png

481 Bytes
Loading

icons/pub.png

519 Bytes
Loading

lib/pdf_renderer.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module PDFRenderer #:nodoc:
2+
VERSION = "0.1"
3+
end
4+
5+
require "pdf_renderer/display_list"
6+
require "pdf_renderer/map_spec"
7+
require "pdf_renderer/drawing_item"
8+
require "pdf_renderer/casing_item"
9+
require "pdf_renderer/stroke_item"
10+
require "pdf_renderer/text_item"
11+
require "pdf_renderer/fill_item"
12+
require "pdf_renderer/point_item"
13+
require "pdf_renderer/canvas_item"
14+
require "pdf_renderer/collision_object"

lib/pdf_renderer/canvas_item.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module PDFRenderer
2+
class CanvasItem < DrawingItem
3+
4+
def draw(pdf, spec)
5+
pdf.fill_color(sprintf("%06X",@style.get(@tags,'fill_color')))
6+
pdf.transparent(@style.get(@tags,'fill_opacity',1).to_f) do
7+
pdf.rectangle [spec.boxoriginx, spec.boxoriginy],spec.boxwidth,-spec.boxheight
8+
pdf.fill
9+
end
10+
end
11+
end
12+
end

lib/pdf_renderer/casing_item.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module PDFRenderer
2+
class CasingItem < DrawingItem
3+
4+
def draw(pdf, spec)
5+
pdf.line_width=@style.get(@tags,'width').to_f + @style.get(@tags,'casing_width').to_f
6+
defaultcap = @style.get(@tags,'linecap')
7+
defaultjoin= @style.get(@tags,'linejoin')
8+
pdf.cap_style=case @style.get(@tags,'casing_linecap')
9+
when 'none' then :butt
10+
when 'square' then :projecting_square
11+
when 'round' then :round
12+
else (defaultcap ? defaultcap : :butt)
13+
end
14+
pdf.join_style=case @style.get(@tags,'casing_linejoin')
15+
when 'miter' then :miter
16+
when 'bevel' then :bevel
17+
when 'round' then :round
18+
else (defaultjoin ? defaultjoin : :round)
19+
end
20+
pdf.stroke_color(sprintf("%06X",@style.get(@tags,'casing_color')))
21+
# do dash with pdf.stroke_dash or implement separately
22+
# etc.
23+
StrokeItem.draw_line(pdf, spec, @entity)
24+
pdf.stroke
25+
end
26+
27+
end
28+
end

lib/pdf_renderer/collision_object.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module PDFRenderer
2+
class CollisionObject
3+
4+
attr_accessor :left, :right, :top, :bottom, :item, :sub_id
5+
6+
def initialize(x,y,xradius,yradius,item=nil,sub_id=nil)
7+
@left =x-xradius
8+
@right =x+xradius
9+
@top =y+yradius
10+
@bottom=y-yradius
11+
@item =item
12+
@sub_id =sub_id
13+
end
14+
15+
def collides_with(cx,cy,cxradius,cyradius)
16+
cleft =cx-cxradius
17+
cright =cx+cxradius
18+
ctop =cy+cyradius
19+
cbottom=cy-cyradius
20+
((cleft >@left && cleft <@right) ||
21+
(cright>@left && cright<@right) ||
22+
(cleft <@left && cright>@right)) &&
23+
((cbottom>@bottom && cbottom<@top) ||
24+
(ctop >@bottom && ctop <@top) ||
25+
(cbottom<@bottom && ctop >@top))
26+
end
27+
end
28+
end

lib/pdf_renderer/display_list.rb

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
module PDFRenderer
2+
class DisplayList
3+
4+
attr_accessor :rules, :spec
5+
6+
def initialize(rules,spec)
7+
@rules=rules
8+
@spec=spec
9+
@list=[]
10+
end
11+
12+
def compile_way(way)
13+
# ** calculate midpoint and length
14+
pathlength=0
15+
16+
# Get tags
17+
tags = TagsBinding.new(way)
18+
if (way.is_closed?) then tags[':area']='yes' end
19+
20+
# Get stylelist
21+
stylelist=@rules.get_styles(way, tags, @spec.scale)
22+
layer = parse_layer(stylelist, tags)
23+
24+
# ** Do multipolygon stuff
25+
26+
# Add entry for each subpart
27+
stylelist.subparts.each do |subpart|
28+
if stylelist.shapestyles[subpart] then
29+
s=stylelist.shapestyles[subpart]
30+
filled=(s.defined('fill_color') || s.defined('fill_image')) # ** multipolygon stuff
31+
32+
if s.defined('width') then add_item(layer, StrokeItem.new(s, way, tags)) end
33+
if filled then add_item(layer, FillItem.new(s, way, tags)) end
34+
if s.defined('casing_width') then add_item(layer, CasingItem.new(s, way, tags)) end
35+
end
36+
if stylelist.textstyles[subpart] then
37+
add_item(layer, TextItem.new(stylelist.textstyles[subpart], way, nil, tags, pathlength))
38+
end
39+
end
40+
end
41+
42+
def compile_poi(node)
43+
dictionary=StyleParser::Dictionary.instance
44+
45+
# Get tags
46+
tags = TagsBinding.new(node)
47+
if !dictionary.has_parent_ways(node) then tags[':poi']='yes'
48+
elsif dictionary.num_parent_ways(node)>1 then tags[':junction']='yes' end
49+
# ** do hasInterestingTags
50+
51+
# Find style
52+
stylelist=@rules.get_styles(node, tags, @spec.scale)
53+
layer = parse_layer(stylelist,tags)
54+
55+
# Add entry for each subpart
56+
stylelist.subparts.each do |subpart|
57+
pointitem = nil
58+
if stylelist.pointstyles[subpart] then
59+
pointitem=PointItem.new(stylelist.pointstyles[subpart],
60+
stylelist.shapestyles[subpart], node, tags)
61+
add_item(layer, pointitem)
62+
end
63+
if stylelist.textstyles[subpart] then
64+
add_item(layer, TextItem.new(stylelist.textstyles[subpart], node, pointitem, tags))
65+
end
66+
end
67+
end
68+
69+
def compile_canvas
70+
stylelist=@rules.get_styles(nil, {}, spec.scale)
71+
stylelist.subparts.each do |subpart|
72+
if stylelist.shapestyles[subpart] then
73+
add_item(@spec.minlayer, CanvasItem.new(stylelist.shapestyles[subpart], nil))
74+
end
75+
end
76+
end
77+
78+
def add_item(layer,item)
79+
sublayer=item.get_sublayer
80+
l=layer-@spec.minlayer
81+
82+
if !@list[l] then @list[l]=[] end
83+
if !@list[l][sublayer] then @list[l][sublayer]=[] end
84+
@list[l][sublayer]<<item
85+
end
86+
87+
def draw(pdf)
88+
for layer in 0..(@list.length-1)
89+
if @list[layer] then
90+
draw_items_of_class(pdf,@list[layer],CanvasItem)
91+
draw_items_of_class(pdf,@list[layer],FillItem)
92+
draw_items_of_class(pdf,@list[layer],CasingItem)
93+
draw_items_of_class(pdf,@list[layer],StrokeItem)
94+
draw_items_of_class(pdf,@list[layer],PointItem)
95+
draw_items_of_class(pdf,@list[layer],TextItem)
96+
end
97+
end
98+
end
99+
100+
def draw_items_of_class(pdf, items, itemclass)
101+
items.each do |sublayer_items|
102+
if sublayer_items then
103+
sublayer_items.each do |item|
104+
if item.instance_of?(itemclass) then item.draw(pdf,@spec) end
105+
end
106+
end
107+
end
108+
end
109+
110+
# ----- Get layer (may come from override in declarations, or from layer tag)
111+
112+
def parse_layer(stylelist,tags)
113+
layer=stylelist.layer_override
114+
if layer.nil? and tags.has_key?('layer') then layer=[[tags['layer'].to_i,@spec.minlayer].max,@spec.maxlayer].min end
115+
layer.nil? ? 0 : layer
116+
end
117+
118+
end
119+
end

lib/pdf_renderer/drawing_item.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module PDFRenderer
2+
class DrawingItem
3+
4+
def initialize(style, entity, tags=nil)
5+
@entity=entity
6+
@style=style
7+
@tags=tags ? tags : (entity ? entity.tags : {} )
8+
end
9+
10+
def get_sublayer
11+
@style.sublayer
12+
end
13+
end
14+
end

lib/pdf_renderer/fill_item.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module PDFRenderer
2+
class FillItem < DrawingItem
3+
4+
def draw(pdf, spec)
5+
dictionary=StyleParser::Dictionary.instance
6+
return if dictionary.is_member_of(@entity,'multipolygon','inner')
7+
multipolygons=dictionary.parent_relations_of_type(@entity,'multipolygon','outer')
8+
9+
pdf.fill_color(sprintf("%06X",@style.get(@tags,'fill_color')))
10+
pdf.transparent(@style.get(@tags,'fill_opacity',1).to_f) do
11+
StrokeItem.draw_line(pdf, spec, @entity)
12+
multipolygons.each do |multi|
13+
dictionary.relation_loaded_members(@entity.db,multi,'inner').each do |obj|
14+
if obj.type=='way' then StrokeItem.draw_line(pdf, spec, obj) end
15+
end
16+
end
17+
pdf.add_content("f*") # like pdf.fill, but for even-odd winding
18+
end
19+
end
20+
21+
end
22+
end

0 commit comments

Comments
 (0)