atlas_middleware 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +3 -0
- data/README +3 -0
- data/lib/arc_server_soap.rb +47 -0
- data/lib/atlas_middleware.rb +5 -0
- data/lib/cgns_search.rb +30 -0
- data/lib/esri/soap/map_server/classes.rb +4328 -0
- data/lib/esri/soap/map_server/client.rb +326 -0
- data/lib/esri/soap/map_server/map_server.rb +39 -0
- data/lib/esri/soap/map_server/mapping_registry.rb +4300 -0
- data/lib/map_server_legend_info.rb +17 -0
- data/lib/pdf_template.rb +206 -0
- data/lib/postal_code_lookup.rb +54 -0
- data/lib/print_map/job.rb +49 -0
- data/lib/print_map/legend_images_collector.rb +34 -0
- data/lib/print_map/map_exporter.rb +20 -0
- data/lib/print_map/pdf_generator.rb +146 -0
- data/lib/print_map/worker.rb +71 -0
- data/lib/print_map_service.rb +48 -0
- data/lib/rest_proxy.rb +15 -0
- metadata +113 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'arcserver'
|
2
|
+
|
3
|
+
class MapServerLegendInfo
|
4
|
+
def call(env)
|
5
|
+
request = Rack::Request.new(env)
|
6
|
+
|
7
|
+
begin
|
8
|
+
map_server = ArcServer::MapServer.new(request['url'])
|
9
|
+
map_name = map_server.get_default_map_name
|
10
|
+
legend = map_server.get_default_map_name(:map_name => map_name)
|
11
|
+
rescue Exception => e
|
12
|
+
return [500, { "Content-Type" => "text/plain" }, e.message]
|
13
|
+
end
|
14
|
+
|
15
|
+
[200, { "Content-Type" => "application/json" }, legend.to_json]
|
16
|
+
end
|
17
|
+
end
|
data/lib/pdf_template.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
#! /usr/bin/ruby
|
2
|
+
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'prawn'
|
7
|
+
require 'prawn/format'
|
8
|
+
require 'prawn/layout'
|
9
|
+
#require 'prawn/fast_png'
|
10
|
+
require 'open-uri'
|
11
|
+
require 'esri/soap/map_server/map_server'
|
12
|
+
require 'RMagick'
|
13
|
+
require 'perftools'
|
14
|
+
require 'base64'
|
15
|
+
|
16
|
+
module Prawn
|
17
|
+
module Images
|
18
|
+
class PNG
|
19
|
+
alias_method :prawn_fast_png_old_initialize, :initialize
|
20
|
+
|
21
|
+
def initialize(data) #:nodoc:
|
22
|
+
@prawn_fast_png_data = data
|
23
|
+
prawn_fast_png_old_initialize(data)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def unfilter_image_data
|
28
|
+
img = Magick::Image.from_blob(@prawn_fast_png_data).first
|
29
|
+
|
30
|
+
# get only one color value per pixel (Intensity) for grayscale+alpha images
|
31
|
+
format = color_type == 4 ? 'I' : 'RGB'
|
32
|
+
|
33
|
+
img_data = img.export_pixels_to_str(0, 0, width, height, format)
|
34
|
+
alpha_channel = img.export_pixels_to_str(0, 0, width, height, 'A')
|
35
|
+
|
36
|
+
img.destroy!
|
37
|
+
@prawn_fast_png_data = nil
|
38
|
+
|
39
|
+
@img_data = Zlib::Deflate.deflate(img_data)
|
40
|
+
@alpha_channel = Zlib::Deflate.deflate(alpha_channel)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def collect_legend_images
|
48
|
+
images = []
|
49
|
+
service = 'http://sampleserver1.arcgisonline.com/ArcGIS/services/Portland/ESRI_LandBase_WebMercator/MapServer'
|
50
|
+
legend_infos = ESRI::Soap::MapServer.get_legend_info(service, :image_return_type => :data)
|
51
|
+
|
52
|
+
legend_infos.each do |legend_info|
|
53
|
+
if legend_info.legendGroups[0].legendClasses.length == 1
|
54
|
+
img_data = Base64.decode64(Base64.decode64(legend_info.legendGroups[0].legendClasses[0].symbolImage.imageData))
|
55
|
+
images << {
|
56
|
+
:label => legend_info.name,
|
57
|
+
:image => Magick::Image.from_blob(img_data).first
|
58
|
+
}
|
59
|
+
else
|
60
|
+
legend_info.legendGroups[0].legendClasses.each do |legend_class|
|
61
|
+
img_data = Base64.decode64(Base64.decode64(legend_class.symbolImage.imageData))
|
62
|
+
images << {
|
63
|
+
:label => legend_class.label,
|
64
|
+
:image => Magick::Image.from_blob(img_data).first
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
images
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_legend(pdf, opts = {})
|
74
|
+
images = collect_legend_images
|
75
|
+
|
76
|
+
columns = opts[:columns] || 4
|
77
|
+
rows = opts[:rows] || 4
|
78
|
+
gutter = opts[:gutter] || 0
|
79
|
+
|
80
|
+
pdf.define_grid(:columns => columns, :rows => rows, :gutter => gutter)
|
81
|
+
|
82
|
+
pdf.grid.rows.times do |i|
|
83
|
+
pdf.grid.columns.times do |j|
|
84
|
+
cell = pdf.grid(i,j)
|
85
|
+
pdf.bounding_box cell.top_left, :width => cell.width, :height => cell.height do
|
86
|
+
return if images.empty?
|
87
|
+
image = images.shift
|
88
|
+
|
89
|
+
pdf.image StringIO.new(image[:image].to_blob), :at => [pdf.bounds.top_left], :fit => [16, 16]
|
90
|
+
pdf.text_box image[:label],
|
91
|
+
:width => cell.width,
|
92
|
+
:height => cell.height,
|
93
|
+
:overflow => :ellipses,
|
94
|
+
:at => [pdf.bounds.left + 20, pdf.bounds.top - 2]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def portrait_pdf
|
101
|
+
print 'creating portrait pdf...'
|
102
|
+
Prawn::Document.generate '/home/colin/portrait.pdf', :page_layout => :portrait do |pdf|
|
103
|
+
box = pdf.bounds
|
104
|
+
# html tag definitions
|
105
|
+
pdf.tags :h1 => { :font_size => '25', :font_style => :bold }
|
106
|
+
# html style definitions
|
107
|
+
pdf.styles :footer => { :color => "#999", :font_style => :italic }
|
108
|
+
|
109
|
+
# header
|
110
|
+
pdf.bounding_box [box.left, box.top], :width => box.right, :height => 30 do
|
111
|
+
pdf.text "<h1>Title</h1>"
|
112
|
+
end
|
113
|
+
|
114
|
+
# map
|
115
|
+
pdf.bounding_box [box.left, pdf.cursor], :width => box.right, :height => box.width do
|
116
|
+
pdf.stroke_bounds
|
117
|
+
pdf.image "/home/colin/export.png", :at => [box.left, pdf.cursor], :width => box.width, :height => box.width
|
118
|
+
end
|
119
|
+
|
120
|
+
pdf.move_down 10
|
121
|
+
# toc
|
122
|
+
pdf.bounding_box [box.left, pdf.cursor], :width => box.right, :height => 140 do
|
123
|
+
pdf.padded_box 5 do
|
124
|
+
create_legend(pdf, :columns => 4, :rows => 5)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# footer
|
129
|
+
pdf.canvas do
|
130
|
+
footer = {
|
131
|
+
:top_left => [box.absolute_left - 20, box.absolute_bottom - 15],
|
132
|
+
:width => box.right + 40,
|
133
|
+
:height => 15
|
134
|
+
}
|
135
|
+
|
136
|
+
pdf.bounding_box footer[:top_left], :width => footer[:width], :height => footer[:height] do
|
137
|
+
pdf.text '<span class="footer">NB Aquatic Bio-Web</span>'
|
138
|
+
end
|
139
|
+
|
140
|
+
pdf.bounding_box footer[:top_left], :width => footer[:width], :height => footer[:height] do
|
141
|
+
pdf.text "<span class=\"footer\">Generated on #{Time.now.strftime("%Y/%m/%d")}</span>", :align => :right
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
puts 'done'
|
146
|
+
end
|
147
|
+
|
148
|
+
def landscape_pdf
|
149
|
+
print 'creating landscape pdf...'
|
150
|
+
Prawn::Document.generate '/home/colin/landscape.pdf', :page_layout => :landscape do |pdf|
|
151
|
+
box = pdf.bounds
|
152
|
+
# html tag definitions
|
153
|
+
pdf.tags :h1 => { :font_size => '25', :font_style => :bold }
|
154
|
+
# html style definitions
|
155
|
+
pdf.styles :footer => { :color => "#999", :font_style => :italic }
|
156
|
+
|
157
|
+
# map
|
158
|
+
pdf.bounding_box [box.left, box.top], :width => box.height, :height => box.height do
|
159
|
+
pdf.stroke_bounds
|
160
|
+
pdf.image "/home/colin/export.png", :at => [box.left, box.top], :width => box.height, :height => box.height
|
161
|
+
end
|
162
|
+
|
163
|
+
pdf.bounding_box [box.height + 10, box.top], :width => box.right - box.height - 10, :height => box.height do
|
164
|
+
# header
|
165
|
+
pdf.text "<h1>Title</h1>"
|
166
|
+
pdf.move_down 10
|
167
|
+
|
168
|
+
# toc
|
169
|
+
pdf.bounding_box [pdf.bounds.left, pdf.cursor], :width => pdf.bounds.right, :height => pdf.cursor do
|
170
|
+
create_legend(pdf, :columns => 1, :rows => 20)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# footer
|
175
|
+
pdf.canvas do
|
176
|
+
footer = {
|
177
|
+
:top_left => [box.absolute_left - 20, box.absolute_bottom - 15],
|
178
|
+
:width => box.right + 40,
|
179
|
+
:height => 15
|
180
|
+
}
|
181
|
+
|
182
|
+
pdf.bounding_box footer[:top_left], :width => footer[:width], :height => footer[:height] do
|
183
|
+
pdf.text '<span class="footer">NB Aquatic Bio-Web</span>'
|
184
|
+
end
|
185
|
+
|
186
|
+
pdf.bounding_box footer[:top_left], :width => footer[:width], :height => footer[:height] do
|
187
|
+
pdf.text "<span class=\"footer\">Generated on #{Time.now.strftime("%Y/%m/%d")}</span>", :align => :right
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
puts 'done'
|
192
|
+
end
|
193
|
+
|
194
|
+
PerfTools::CpuProfiler.start("/home/colin/pdf_perf") do
|
195
|
+
start_time = Time.now
|
196
|
+
portrait_pdf
|
197
|
+
end_time = Time.now
|
198
|
+
puts end_time - start_time
|
199
|
+
end
|
200
|
+
|
201
|
+
#
|
202
|
+
#start_time = Time.now
|
203
|
+
#landscape_pdf
|
204
|
+
#end_time = Time.now
|
205
|
+
#puts end_time - start_time
|
206
|
+
#end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rack'
|
2
|
+
#require 'rack/cache'
|
3
|
+
require 'activesupport'
|
4
|
+
require 'httparty'
|
5
|
+
|
6
|
+
class PostalCodeLookup
|
7
|
+
class ServiceException < Exception; end
|
8
|
+
|
9
|
+
include HTTParty
|
10
|
+
base_uri "geoservices.cgdi.ca"
|
11
|
+
format :xml
|
12
|
+
default_params :version => '1.0.0',
|
13
|
+
:request => 'GetPostalCode',
|
14
|
+
:sortarea => 'FSA'
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
request = Rack::Request.new(env)
|
18
|
+
|
19
|
+
headers = { "Content-Type" => "text/plain" }
|
20
|
+
r = Rack::Response # shorthand
|
21
|
+
begin
|
22
|
+
response = r.new(200, headers, find(request.params['code']))
|
23
|
+
rescue Exception => e
|
24
|
+
response = r.new(500, headers, e.message)
|
25
|
+
end
|
26
|
+
# response.max_age = 1.month
|
27
|
+
|
28
|
+
response.to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
def find(code)
|
32
|
+
result = PostalCodeLookup.get('/cgi-bin/postalcode/postalcode.cgi', {
|
33
|
+
:query => { :code => code }
|
34
|
+
})
|
35
|
+
|
36
|
+
if error = result["ServiceExceptionReport"]
|
37
|
+
raise ServiceException.new(error['ServiceException'])
|
38
|
+
else
|
39
|
+
postal_code = result['PostalCodeLookup']['PostalCodeResultSet']['PostalCode']
|
40
|
+
location = postal_code["gml:centerOf"]["gml:Point"]
|
41
|
+
epsg = location["srsName"].split('#').pop
|
42
|
+
lng, lat = location["gml:coordinates"].split(',')
|
43
|
+
return {
|
44
|
+
:postal_code => postal_code["gml:name"],
|
45
|
+
:epsg => epsg,
|
46
|
+
:srs_name => location["srsName"],
|
47
|
+
:latitude => lat,
|
48
|
+
:longitude => lng,
|
49
|
+
:placename => postal_code["Placename"],
|
50
|
+
:province_or_territory => postal_code["ProvinceOrTerritory"]
|
51
|
+
}.to_json
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'arcserver'
|
2
|
+
require 'validatable'
|
3
|
+
|
4
|
+
module PrintMap
|
5
|
+
class Job
|
6
|
+
include Validatable
|
7
|
+
include ArcServer::UrlHelper
|
8
|
+
# services validations
|
9
|
+
validates_length_of :services, :minimum => 1, :message => 'at least one service needs to be requested'
|
10
|
+
validates_true_for :services, :logic => lambda {
|
11
|
+
services.all? { |url| map_server?(url) }
|
12
|
+
}, :message => 'all services need to be valid MapServer urls'
|
13
|
+
# bbox validations
|
14
|
+
validates_length_of :bbox, :is => 4, :message => "bad format for 'bbox' - try bbox=xmin,ymin,xmax,ymax"
|
15
|
+
validates_true_for :bbox, :logic => lambda {
|
16
|
+
env['bbox'].to_s.split(',').all? { |b| b.to_s.match(/^[-|+]?\d*\.?\d*$/) }
|
17
|
+
}, :message => 'all bbox values must be valid positive/negative numbers'
|
18
|
+
|
19
|
+
attr_reader :env
|
20
|
+
|
21
|
+
def initialize(env = {})
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def services
|
26
|
+
@services ||= env['services'].to_s.split(',')
|
27
|
+
end
|
28
|
+
|
29
|
+
def bbox
|
30
|
+
@bbox ||= env['bbox'].to_s.split(',').collect{ |b| b.to_f }
|
31
|
+
end
|
32
|
+
|
33
|
+
def page_layout
|
34
|
+
@page_layout ||= (env['page_layout'] || 'portrait').to_sym
|
35
|
+
end
|
36
|
+
|
37
|
+
def page_size
|
38
|
+
@page_size ||= env['page_size'] || 'A4'
|
39
|
+
end
|
40
|
+
|
41
|
+
def dpi
|
42
|
+
@dpi ||= env['dpi'] || 96
|
43
|
+
end
|
44
|
+
|
45
|
+
def title
|
46
|
+
@title = env['title'] || 'Untitled Map'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'arcserver/map_server'
|
2
|
+
|
3
|
+
module PrintMap
|
4
|
+
class LegendImagesCollector
|
5
|
+
def collect(opts = {})
|
6
|
+
puts 'creating map server object'
|
7
|
+
map_server = ArcServer::MapServer.new(opts[:url])
|
8
|
+
|
9
|
+
images = []
|
10
|
+
puts 'getting legend'
|
11
|
+
map_server.get_legend_info.each do |legend_info|
|
12
|
+
puts 'processing result'
|
13
|
+
legend_classes = legend_info[:legend_groups][0][:legend_classes]
|
14
|
+
if legend_classes.length == 1
|
15
|
+
img_data = Base64.decode64(legend_classes[0][:symbol_image][:image_data])
|
16
|
+
images << {
|
17
|
+
:label => legend_info[:name],
|
18
|
+
:data => img_data
|
19
|
+
}
|
20
|
+
else
|
21
|
+
legend_classes.each do |legend_class|
|
22
|
+
img_data = Base64.decode64(legend_class[:symbol_image][:image_data])
|
23
|
+
images << {
|
24
|
+
:label => legend_class[:label],
|
25
|
+
:data => img_data
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
images
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'RMagick'
|
3
|
+
|
4
|
+
module PrintMap
|
5
|
+
class MapExporter
|
6
|
+
def export(opts = {})
|
7
|
+
service = opts[:service]
|
8
|
+
query = {
|
9
|
+
:bbox => opts[:bbox],
|
10
|
+
:f => :image,
|
11
|
+
:format => :png24,
|
12
|
+
:transparent => true,
|
13
|
+
:size => opts[:size],
|
14
|
+
:dpi => opts[:dpi]
|
15
|
+
}
|
16
|
+
response = HTTParty.get("#{service}/export", :query => query)
|
17
|
+
Magick::Image.from_blob(response.body).first
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'prawn'
|
2
|
+
require 'prawn/format'
|
3
|
+
require 'prawn/layout'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
module PrintMap
|
7
|
+
class PdfGenerator
|
8
|
+
def generate(opts = {})
|
9
|
+
title = opts[:title]
|
10
|
+
map_image = opts[:map_image]
|
11
|
+
layout = opts[:page_layout]
|
12
|
+
legend_images = opts[:legend_images]
|
13
|
+
|
14
|
+
pdf = Prawn::Document.new(:page_layout => layout) do |pdf|
|
15
|
+
# html tag definitions
|
16
|
+
pdf.tags :h1 => { :font_size => '25', :font_style => :bold }
|
17
|
+
# html style definitions
|
18
|
+
pdf.styles :footer => { :color => "#999", :font_style => :italic }
|
19
|
+
# generate the pdf
|
20
|
+
send("generate_#{layout}_pdf", pdf, title, map_image, legend_images)
|
21
|
+
write_footer(pdf)
|
22
|
+
end
|
23
|
+
|
24
|
+
pdf
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_portrait_pdf(pdf, title, map_image, legend_images)
|
28
|
+
box = pdf.bounds
|
29
|
+
# header
|
30
|
+
pdf.bounding_box [box.left, box.top], :width => box.right, :height => 30 do
|
31
|
+
pdf.text "<h1>#{title}</h1>"
|
32
|
+
end
|
33
|
+
|
34
|
+
# map
|
35
|
+
pdf.bounding_box [box.left, pdf.cursor], :width => box.right, :height => box.width do
|
36
|
+
pdf.stroke_bounds
|
37
|
+
pdf.image StringIO.new(map_image.to_blob), :at => [box.left, pdf.cursor], :width => box.width, :height => box.width
|
38
|
+
end
|
39
|
+
|
40
|
+
pdf.move_down 10
|
41
|
+
# toc
|
42
|
+
pdf.bounding_box [box.left, pdf.cursor], :width => box.right, :height => 140 do
|
43
|
+
pdf.padded_box 5 do
|
44
|
+
create_legend(pdf, legend_images, :columns => 4, :rows => 5)
|
45
|
+
pdf.stroke_bounds
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def generate_landscape_pdf(pdf, title, image, legend_images)
|
51
|
+
box = pdf.bounds
|
52
|
+
# map
|
53
|
+
pdf.bounding_box [box.left, box.top], :width => box.height, :height => box.height do
|
54
|
+
pdf.stroke_bounds
|
55
|
+
pdf.image StringIO.new(image.to_blob), :at => [box.left, box.top], :width => box.height, :height => box.height
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
pdf.bounding_box [box.height + 10, box.top], :width => box.right - box.height - 10, :height => box.height do
|
60
|
+
# header
|
61
|
+
pdf.text "<h1>#{title}</h1>"
|
62
|
+
pdf.move_down 10
|
63
|
+
|
64
|
+
# toc
|
65
|
+
pdf.bounding_box [pdf.bounds.left, pdf.cursor], :width => pdf.bounds.right, :height => pdf.cursor do
|
66
|
+
create_legend(pdf, legend_images, :columns => 1, :rows => 20)
|
67
|
+
pdf.stroke_bounds
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_footer(pdf)
|
73
|
+
box = pdf.bounds
|
74
|
+
pdf.canvas do
|
75
|
+
footer = {
|
76
|
+
:top_left => [box.absolute_left - 20, box.absolute_bottom - 15],
|
77
|
+
:width => box.right + 40,
|
78
|
+
:height => 15
|
79
|
+
}
|
80
|
+
|
81
|
+
pdf.bounding_box footer[:top_left], :width => footer[:width], :height => footer[:height] do
|
82
|
+
pdf.text '<span class="footer">NB Aquatic Bio-Web</span>'
|
83
|
+
end
|
84
|
+
|
85
|
+
pdf.bounding_box footer[:top_left], :width => footer[:width], :height => footer[:height] do
|
86
|
+
pdf.text "<span class=\"footer\">Generated on #{Time.now.strftime("%Y/%m/%d")}</span>", :align => :right
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_legend(pdf, legend_images, opts = {})
|
92
|
+
columns = opts[:columns] || 4
|
93
|
+
rows = opts[:rows] || 4
|
94
|
+
gutter = opts[:gutter] || 0
|
95
|
+
|
96
|
+
pdf.define_grid(:columns => columns, :rows => rows, :gutter => gutter)
|
97
|
+
|
98
|
+
pdf.grid.rows.times do |i|
|
99
|
+
pdf.grid.columns.times do |j|
|
100
|
+
cell = pdf.grid(i,j)
|
101
|
+
pdf.bounding_box cell.top_left, :width => cell.width, :height => cell.height do
|
102
|
+
return if legend_images.empty?
|
103
|
+
image = legend_images.shift
|
104
|
+
|
105
|
+
pdf.image StringIO.new(image[:data]), :at => [pdf.bounds.top_left], :fit => [16, 16]
|
106
|
+
pdf.text_box image[:label],
|
107
|
+
:width => cell.width,
|
108
|
+
:height => cell.height,
|
109
|
+
:overflow => :ellipses,
|
110
|
+
:at => [pdf.bounds.left + 20, pdf.bounds.top - 2]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module Prawn
|
119
|
+
module Images
|
120
|
+
class PNG
|
121
|
+
alias_method :prawn_fast_png_old_initialize, :initialize
|
122
|
+
|
123
|
+
def initialize(data) #:nodoc:
|
124
|
+
@prawn_fast_png_data = data
|
125
|
+
prawn_fast_png_old_initialize(data)
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def unfilter_image_data
|
130
|
+
img = Magick::Image.from_blob(@prawn_fast_png_data).first
|
131
|
+
|
132
|
+
# get only one color value per pixel (Intensity) for grayscale+alpha images
|
133
|
+
format = color_type == 4 ? 'I' : 'RGB'
|
134
|
+
|
135
|
+
img_data = img.export_pixels_to_str(0, 0, width, height, format)
|
136
|
+
alpha_channel = img.export_pixels_to_str(0, 0, width, height, 'A')
|
137
|
+
|
138
|
+
img.destroy!
|
139
|
+
@prawn_fast_png_data = nil
|
140
|
+
|
141
|
+
@img_data = Zlib::Deflate.deflate(img_data)
|
142
|
+
@alpha_channel = Zlib::Deflate.deflate(alpha_channel)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'prawn'
|
2
|
+
require 'RMagick'
|
3
|
+
require 'print_map/map_exporter'
|
4
|
+
require 'print_map/pdf_generator'
|
5
|
+
require 'print_map/legend_images_collector'
|
6
|
+
|
7
|
+
module PrintMap
|
8
|
+
class Worker
|
9
|
+
attr_reader :current_job
|
10
|
+
attr_reader :map_exporter, :pdf_generator, :legend_images_collector
|
11
|
+
|
12
|
+
def initialize(config = {})
|
13
|
+
@map_exporter = config[:map_exporter] || MapExporter.new
|
14
|
+
@pdf_generator = config[:pdf_generator] || PdfGenerator.new
|
15
|
+
@legend_images_collector = config[:legend_images_collector] || LegendImagesCollector.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def do_job(job)
|
19
|
+
@current_job = job
|
20
|
+
puts 'create composite map image'
|
21
|
+
map_image = create_composite_map_image
|
22
|
+
puts 'collect legend images'
|
23
|
+
legend_images = collect_legend_images
|
24
|
+
puts 'generate pdf'
|
25
|
+
pdf = generate_pdf(map_image, legend_images)
|
26
|
+
puts 'cleanup'
|
27
|
+
map_image.destroy!
|
28
|
+
pdf
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_composite_map_image
|
32
|
+
width, height = 540, 510
|
33
|
+
base_image = Magick::Image.new(width, height)
|
34
|
+
base_image.background_color = 'transparent'
|
35
|
+
base_image.format = 'PNG'
|
36
|
+
|
37
|
+
current_job.services.each do |map_service|
|
38
|
+
export_options = {
|
39
|
+
:service => map_service,
|
40
|
+
:dpi => current_job.dpi,
|
41
|
+
:bbox => current_job.bbox.join(','),
|
42
|
+
:size => [width, height].join(',')
|
43
|
+
}
|
44
|
+
map_service_image = map_exporter.export(export_options)
|
45
|
+
base_image.composite!(map_service_image, Magick::CenterGravity, Magick::OverCompositeOp)
|
46
|
+
map_service_image.destroy!
|
47
|
+
end
|
48
|
+
|
49
|
+
base_image
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate_pdf(map_image, legend_images)
|
53
|
+
opts = {
|
54
|
+
:title => current_job.title,
|
55
|
+
:map_image => map_image,
|
56
|
+
:page_layout => current_job.page_layout,
|
57
|
+
:legend_images => legend_images
|
58
|
+
}
|
59
|
+
pdf_generator.generate(opts)
|
60
|
+
end
|
61
|
+
|
62
|
+
def collect_legend_images
|
63
|
+
images = []
|
64
|
+
current_job.services.each do |url|
|
65
|
+
puts 'fetching legends at ' + url
|
66
|
+
images << legend_images_collector.collect(:url => url)
|
67
|
+
end
|
68
|
+
images.flatten
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'print_map/job'
|
2
|
+
require 'print_map/worker'
|
3
|
+
|
4
|
+
class PrintMapService
|
5
|
+
attr_reader :worker, :create_job
|
6
|
+
|
7
|
+
def initialize(opts = {})
|
8
|
+
@worker = opts[:worker] ||= PrintMap::Worker.new
|
9
|
+
@create_job = opts[:create_job] ||= proc { |opts| PrintMap::Job.new(opts) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
puts 'calling print map'
|
14
|
+
job = create_job.call(Rack::Request.new(env).params)
|
15
|
+
if job.valid?
|
16
|
+
puts 'valid job'
|
17
|
+
pdf = worker.do_job(job)
|
18
|
+
puts 'rendering to client'
|
19
|
+
# now stream the pdf to the client
|
20
|
+
[
|
21
|
+
200, {
|
22
|
+
"Content-Type" => "application/pdf",
|
23
|
+
"Content-Disposition" => "attachment; filename=\"#{job.title}.pdf\""
|
24
|
+
},
|
25
|
+
pdf.render
|
26
|
+
]
|
27
|
+
else
|
28
|
+
create_invalid_job_response(job)
|
29
|
+
end
|
30
|
+
|
31
|
+
rescue Exception => e
|
32
|
+
create_error_response(e)
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_invalid_job_response(job)
|
36
|
+
body = "Print map failed because of the following errors:\n#{job.errors.full_messages.join("\n")}"
|
37
|
+
puts 'failed to generate print map:'
|
38
|
+
puts body
|
39
|
+
[500, { "Content-Type" => "text/plain" }, body]
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_error_response(e)
|
43
|
+
body = "An internal error occurred:\n#{e.message}"
|
44
|
+
puts 'failed to generate print map:'
|
45
|
+
puts body
|
46
|
+
[500, { "Content-Type" => "text/plain" }, body]
|
47
|
+
end
|
48
|
+
end
|
data/lib/rest_proxy.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'httparty'
|
3
|
+
|
4
|
+
class RestProxy
|
5
|
+
def call(env)
|
6
|
+
request = Rack::Request.new(env)
|
7
|
+
url = request.params.delete('url')
|
8
|
+
call_proxy(url)
|
9
|
+
end
|
10
|
+
|
11
|
+
def call_proxy(url)
|
12
|
+
response = HTTParty.get(url, :query => { :f => :json })
|
13
|
+
[response.code, response.headers, response.body]
|
14
|
+
end
|
15
|
+
end
|