atlas_middleware 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,3 +1,24 @@
1
- == AtlasRack
1
+ == Atlas Middleware
2
2
 
3
- You should document your project here.
3
+ Rack middleware components to support NBADW Atlas maps.
4
+
5
+ Includes the following:
6
+
7
+ * Place Name Search
8
+ Powered by Natural Resources Canada's CGNS (Canadian Geographical Names Service), an
9
+ on-line web service providing information about Canada's geographical names including
10
+ location, status, classification and other details with each official geographical name.
11
+
12
+ * Postal Code Lookup
13
+ Powered by the CGDI Postal Code Lookup Service which returns geometries for known forward
14
+ sortation area (FSA) postal codes.
15
+
16
+ * ArcGIS Server REST Proxy
17
+ Provides proxied access to ESRI's ArcGIS Server 9.3 REST interface. Useful for
18
+ handling XHR requests without having to worry about the same origin security policy
19
+ enforced by browsers.
20
+
21
+ * MapServer Legend Info
22
+ Uses ArcGIS Server's SOAP API to load legend information (layer names and symbols)
23
+ for MapServer services.
24
+
@@ -1,7 +1,7 @@
1
1
  require 'rack'
2
2
  require 'httparty'
3
3
 
4
- class RestProxy
4
+ class ArcgisServerRestProxy
5
5
  def call(env)
6
6
  request = Rack::Request.new(env)
7
7
  url = request.params.delete('url')
data/lib/cgns_search.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require 'rack'
2
- #require 'rack/cache'
3
2
  require 'activesupport'
4
3
  require 'httparty'
5
4
 
@@ -13,18 +12,29 @@ class CgnsSearch
13
12
  request = Rack::Request.new(env)
14
13
 
15
14
  headers = { "Content-Type" => "application/json" }
16
- r = Rack::Response
17
15
  begin
18
- response = r.new(find(request.params), 200, headers)
16
+ response = Rack::Response.new(find(request.params), 200, headers)
17
+ response = maybe_cache_response(response)
19
18
  rescue Exception => e
20
- response = r.new(e.message, 500, headers)
19
+ response = Rack::Response.new({ :error => e.message }.to_json, 500, headers)
21
20
  end
22
- #response.max_age = 1.month
23
21
 
24
22
  response.to_a
25
23
  end
26
-
24
+
25
+ private
27
26
  def find(query = {})
28
27
  CgnsSearch.get('/gnss-srt/api', { :query => query }).to_json
29
28
  end
29
+
30
+ def maybe_cache_response(response)
31
+ begin
32
+ # using rack cache
33
+ cached_response = Rack::Cache::Response.new(response.status, response.headers, response.body)
34
+ cached_response.max_age = 1.month
35
+ cached_response
36
+ rescue
37
+ response # just return the original response
38
+ end
39
+ end
30
40
  end
@@ -0,0 +1,56 @@
1
+ require 'arcserver'
2
+ require 'RMagick'
3
+
4
+ class ExportMapServices
5
+ def call(env)
6
+ request = Rack::Request.new(env)
7
+
8
+ begin
9
+ response = Rack::Response.new(export_map_services(request), 200, headers)
10
+ response = maybe_cache_response(response)
11
+ rescue Exception => e
12
+ response = Rack::Response.new({ :error => e.message }.to_json, 500, headers)
13
+ end
14
+
15
+ response.to_a
16
+ end
17
+
18
+ private
19
+ def export_map_services(request)
20
+ opts = {
21
+ :bbox => request['bbox'],
22
+ :f => request['f'],
23
+ :format => request['format'],
24
+ :transparent => request['transparent'],
25
+ :size => request['size'],
26
+ :dpi => request['dpi']
27
+ }
28
+
29
+ base_image = generate_base_image(opts)
30
+ request['urls'].split(',').each do |url|
31
+ map_service = ArcServer::MapServer.new(url)
32
+ exported_image = map_service.export(opts)
33
+ # merge(base_image, exported_image)
34
+ end
35
+
36
+
37
+ end
38
+
39
+ def generate_base_image(opts)
40
+ base_image = Magick::Image.new(width, height)
41
+ base_image.background_color = 'transparent'
42
+ base_image.format = 'PNG'
43
+ base_image
44
+ end
45
+
46
+ def maybe_cache_response(response)
47
+ begin
48
+ # using rack cache
49
+ cached_response = Rack::Cache::Response.new(response.status, response.headers, response.body)
50
+ cached_response.max_age = 1.week
51
+ cached_response
52
+ rescue
53
+ response # just return the original response
54
+ end
55
+ end
56
+ end
@@ -1,17 +1,37 @@
1
1
  require 'arcserver'
2
+ require 'activesupport'
2
3
 
3
4
  class MapServerLegendInfo
4
5
  def call(env)
5
6
  request = Rack::Request.new(env)
6
-
7
+ headers = { "Content-Type" => "application/json" }
8
+
7
9
  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)
10
+ response = Rack::Response.new(get_legends(request['url']), 200, headers)
11
+ response = maybe_cache_response(response)
11
12
  rescue Exception => e
12
- return [500, { "Content-Type" => "text/plain" }, e.message]
13
+ response = Rack::Response.new({ :error => e.message }.to_json, 500, headers)
13
14
  end
14
15
 
15
- [200, { "Content-Type" => "application/json" }, legend.to_json]
16
+ response.to_a
17
+ end
18
+
19
+ private
20
+ def get_legends(map_server_url)
21
+ map_server = ArcServer::MapServer.new(map_server_url)
22
+ map_name = map_server.get_default_map_name
23
+ legend = map_server.get_legend_info(:map_name => map_name)
24
+ legend.to_json
25
+ end
26
+
27
+ def maybe_cache_response(response)
28
+ begin
29
+ # using rack cache
30
+ cached_response = Rack::Cache::Response.new(response.status, response.headers, response.body)
31
+ cached_response.max_age = 1.week
32
+ cached_response
33
+ rescue
34
+ response # just return the original response
35
+ end
16
36
  end
17
37
  end
@@ -1,5 +1,4 @@
1
1
  require 'rack'
2
- #require 'rack/cache'
3
2
  require 'activesupport'
4
3
  require 'httparty'
5
4
 
@@ -16,22 +15,19 @@ class PostalCodeLookup
16
15
  def call(env)
17
16
  request = Rack::Request.new(env)
18
17
 
19
- headers = { "Content-Type" => "text/plain" }
20
- r = Rack::Response # shorthand
18
+ headers = { "Content-Type" => "application/json" }
21
19
  begin
22
- response = r.new(200, headers, find(request.params['code']))
20
+ response = Rack::Response.new(find(request.params['code']), 200, headers)
21
+ response = maybe_cache_response(response)
23
22
  rescue Exception => e
24
- response = r.new(500, headers, e.message)
23
+ response = Rack::Response.new({ :error => e.message }.to_json, 500, headers)
25
24
  end
26
- # response.max_age = 1.month
27
25
 
28
26
  response.to_a
29
27
  end
30
28
 
31
29
  def find(code)
32
- result = PostalCodeLookup.get('/cgi-bin/postalcode/postalcode.cgi', {
33
- :query => { :code => code }
34
- })
30
+ result = PostalCodeLookup.get('/cgi-bin/postalcode/postalcode.cgi', { :query => { :code => code } })
35
31
 
36
32
  if error = result["ServiceExceptionReport"]
37
33
  raise ServiceException.new(error['ServiceException'])
@@ -42,13 +38,24 @@ class PostalCodeLookup
42
38
  lng, lat = location["gml:coordinates"].split(',')
43
39
  return {
44
40
  :postal_code => postal_code["gml:name"],
45
- :epsg => epsg,
41
+ :epsg => epsg.to_i,
46
42
  :srs_name => location["srsName"],
47
- :latitude => lat,
48
- :longitude => lng,
43
+ :latitude => lat.to_f,
44
+ :longitude => lng.to_f,
49
45
  :placename => postal_code["Placename"],
50
46
  :province_or_territory => postal_code["ProvinceOrTerritory"]
51
47
  }.to_json
52
48
  end
53
49
  end
50
+
51
+ def maybe_cache_response(response)
52
+ begin
53
+ # using rack cache
54
+ cached_response = Rack::Cache::Response.new(response.status, response.headers, response.body)
55
+ cached_response.max_age = 1.month
56
+ cached_response
57
+ rescue
58
+ response # just return the original response
59
+ end
60
+ end
54
61
  end
@@ -0,0 +1,139 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'print_map/job'
3
+
4
+ module PrintMap
5
+ class JobTest < Test::Unit::TestCase
6
+ context "created job" do
7
+ should "not contain any services if none were specified" do
8
+ job = Job.new
9
+ assert job.services.empty?
10
+ end
11
+
12
+ should "not contain any services if 'services' parameter is blank" do
13
+ job = Job.new('services' => '')
14
+ end
15
+
16
+ should "be able to accept a single service" do
17
+ job = Job.new('services' => 's')
18
+ assert_equal 1, job.services.length
19
+ assert_equal 's', job.services[0]
20
+ end
21
+
22
+ should "be able to accept multiple services" do
23
+ job = Job.new('services' => 's1,s2')
24
+ assert_equal 2, job.services.length
25
+ assert_equal 's1', job.services[0]
26
+ assert_equal 's2', job.services[1]
27
+ end
28
+
29
+ should "have an empty bounding box if parameter is not specified" do
30
+ job = Job.new
31
+ assert job.bbox.empty?
32
+ end
33
+
34
+ should "have an empty bounding box if 'bbox' parameter is blank" do
35
+ job = Job.new('bbox' => '')
36
+ assert job.bbox.empty?
37
+ end
38
+
39
+ should "be able to configure the bounding box" do
40
+ job = Job.new('bbox' => "1,2,3,4")
41
+ assert_equal [1,2,3,4], job.bbox
42
+ end
43
+
44
+ should "use :portrait as default page layout" do
45
+ job = Job.new
46
+ assert_equal :portrait, job.page_layout
47
+ end
48
+
49
+ should "be able to set page layout to :portrait" do
50
+ job = Job.new('page_layout' => 'portrait')
51
+ assert_equal :portrait, job.page_layout
52
+ end
53
+
54
+ should "be able to set page layout to :landscape" do
55
+ job = Job.new('page_layout' => 'landscape')
56
+ assert_equal :landscape, job.page_layout
57
+ end
58
+
59
+ should "use A4 paper as default page size" do
60
+ job = Job.new
61
+ assert_equal 'A4', job.page_size
62
+ end
63
+
64
+ should "be able to set the page size" do
65
+ job = Job.new('page_size' => 'LEGAL')
66
+ assert_equal 'LEGAL', job.page_size
67
+ end
68
+
69
+ should "use 96 as the default DPI" do
70
+ job = Job.new
71
+ assert_equal 96, job.dpi
72
+ end
73
+
74
+ should "be able to set the DPI" do
75
+ job = Job.new('dpi' => 300)
76
+ assert_equal 300, job.dpi
77
+ end
78
+
79
+ should "use 'Untitled Map' as the default title" do
80
+ job = Job.new
81
+ assert_equal 'Untitled Map', job.title
82
+ end
83
+
84
+ should "be able to set the title" do
85
+ job = Job.new('title' => 'My Title')
86
+ assert_equal 'My Title', job.title
87
+ end
88
+ end
89
+
90
+ context "when validating a job" do
91
+ should "require at least one service" do
92
+ job = Job.new
93
+ job.validate_only("length_of/services")
94
+ assert_match /at least one service needs to be requested/, job.errors.on(:services)
95
+ end
96
+
97
+ should "require all service urls to be MapServer urls" do
98
+ job = Job.new('services' => 'http://not.a.map/server/url')
99
+ job.validate_only("true_for/services")
100
+ assert_match /all services need to be valid MapServer urls/, job.errors.on(:services)
101
+ end
102
+
103
+ should "require bbox to contain four values" do
104
+ job = Job.new('bbox' => '1,2,3')
105
+ job.validate_only("length_of/bbox")
106
+ assert_match /bad format for 'bbox'/, job.errors.on(:bbox)
107
+
108
+ job = Job.new('bbox' => '1,2,3,4,5')
109
+ job.validate_only("length_of/bbox")
110
+ assert_match /bad format for 'bbox'/, job.errors.on(:bbox)
111
+
112
+ job = Job.new('bbox' => '1,2,3,4')
113
+ job.validate_only('length_of/bbox')
114
+ assert_nil job.errors.on(:bbox)
115
+ end
116
+
117
+ should "require bbox to be composed of only positive or negative numbers" do
118
+ job = Job.new('bbox' => 'a,b,c,d')
119
+ job.validate_only('true_for/bbox')
120
+ assert_match /all bbox values must be valid positive\/negative number/, job.errors.on(:bbox)
121
+
122
+ job = Job.new('bbox' => '0,1,0.1,-0.1')
123
+ job.validate_only('true_for/bbox')
124
+ assert_nil job.errors.on(:bbox)
125
+ end
126
+
127
+ should "be able to pass validations" do
128
+ job = Job.new(
129
+ 'bbox' => [0, 1, 0.1, -0.1].join(','),
130
+ 'services' => [
131
+ 'http://sampleserver1.arcgisonline.com/ArcGIS/services/Portland/ESRI_LandBase_WebMercator/MapServer',
132
+ 'http://sampleserver2.arcgisonline.com/ArcGIS/services/Portland/ESRI_LandBase_WebMercator/MapServer'
133
+ ].join(',')
134
+ )
135
+ assert job.valid?, job.errors.full_messages.join(', ')
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,47 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require 'print_map/worker'
3
+ require 'print_map/job'
4
+ require 'print_map/legend_images_collector'
5
+
6
+ module PrintMap
7
+ class WorkerTest < Test::Unit::TestCase
8
+ should "create a pdf from a job request that responds to :render" do
9
+ job = Job.new
10
+ worker = Worker.new
11
+ pdf = worker.do_job(job)
12
+ assert_respond_to(pdf, :render)
13
+ end
14
+
15
+ context "when verifying expected behavior" do
16
+ setup do
17
+ @mock_map_exporter = mock do
18
+ expects(:export).times(2).returns(Magick::Image.new(1,1), Magick::Image.new(1,1))
19
+ end
20
+
21
+ @mock_pdf_generator = mock do
22
+ expects(:generate).once().returns(Prawn::Document.new)
23
+ end
24
+
25
+ services = [
26
+ 'http://sampleserver1.arcgisonline.com/ArcGIS/services/Demographics/ESRI_Census_USA/MapServer',
27
+ 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Petroleum/KGS_OilGasFields_Kansas/MapServer'
28
+ ]
29
+
30
+ @mock_legend_images_collector = mock do
31
+ expects(:collect).times(2).returns([Magick::Image.new(1,1), Magick::Image.new(1,1)])
32
+ end
33
+
34
+ @job = Job.new('services' => services.join(','))
35
+ end
36
+
37
+ should "delegate third-party api work to adapters" do
38
+ worker = Worker.new(
39
+ :map_exporter => @mock_map_exporter,
40
+ :pdf_generator => @mock_pdf_generator,
41
+ :legend_images_collector => @mock_legend_images_collector
42
+ )
43
+ worker.do_job(@job)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,68 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ require 'print_map_service'
3
+
4
+ class PrintMapServiceTest < Test::Unit::TestCase
5
+ include Rack::Test::Methods
6
+
7
+ def app
8
+ @app ||= PrintMapService.new
9
+ end
10
+
11
+ context "when requested job is valid" do
12
+ setup do
13
+ mock_job = mock do
14
+ expects(:valid?).returns(true)
15
+ expects(:title).returns('Test Title')
16
+ end
17
+
18
+ mock_pdf = mock(:render => 'pdf content')
19
+
20
+ mock_worker = mock do
21
+ expects(:do_job).with(mock_job).returns(mock_pdf)
22
+ end
23
+
24
+ @app = PrintMapService.new(:worker => mock_worker, :create_job => proc { |opts| mock_job })
25
+ end
26
+
27
+ should "return a pdf document for download" do
28
+ get "/"
29
+ assert_equal 200, last_response.status
30
+ assert_equal "application/pdf", last_response.headers["Content-Type"]
31
+ assert_equal 'attachment; filename="Test Title.pdf"', last_response["Content-Disposition"]
32
+ assert_equal 'pdf content', last_response.body
33
+ end
34
+ end
35
+
36
+ context "when requested job is invalid" do
37
+ setup do
38
+ mock_job = mock do
39
+ expects(:valid?).returns(false)
40
+ expects(:errors).returns([])
41
+ end
42
+ @app = PrintMapService.new(:create_job => proc { |opts| mock_job })
43
+ end
44
+
45
+ should "return an error report" do
46
+ get "/"
47
+ assert_equal 500, last_response.status
48
+ assert_equal "text/plain", last_response.headers['Content-Type']
49
+ assert_match(/^Print map failed because of the following errors:/, last_response.body)
50
+ end
51
+ end
52
+
53
+ context "when an error occurs during execution" do
54
+ setup do
55
+ mock_job = mock do
56
+ expects(:valid?).raises('Test Error')
57
+ end
58
+ @app = PrintMapService.new(:create_job => proc { |opts| mock_job })
59
+ end
60
+
61
+ should "trap the error and return a friendly response detailing what happened" do
62
+ get "/"
63
+ assert_equal 500, last_response.status
64
+ assert_equal "text/plain", last_response.headers['Content-Type']
65
+ assert_match /^An internal error occurred:\sTest Error/, last_response.body
66
+ end
67
+ end
68
+ end