atlas_middleware 0.0.1 → 0.0.2

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/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