atlas_middleware 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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