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 +23 -2
- data/lib/{rest_proxy.rb → arcgis_server_rest_proxy.rb} +1 -1
- data/lib/cgns_search.rb +16 -6
- data/lib/export_map_services.rb +56 -0
- data/lib/map_server_legend_info.rb +26 -6
- data/lib/postal_code_lookup.rb +19 -12
- data/test/print_map/job_test.rb +139 -0
- data/test/print_map/worker_test.rb +47 -0
- data/test/print_map_service_test.rb +68 -0
- data/test/test_helper.rb +7 -0
- metadata +49 -19
- data/lib/arc_server_soap.rb +0 -47
- data/lib/esri/soap/map_server/classes.rb +0 -4328
- data/lib/esri/soap/map_server/client.rb +0 -326
- data/lib/esri/soap/map_server/map_server.rb +0 -39
- data/lib/esri/soap/map_server/mapping_registry.rb +0 -4300
- data/lib/pdf_template.rb +0 -206
data/README
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
-
==
|
1
|
+
== Atlas Middleware
|
2
2
|
|
3
|
-
|
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
|
+
|
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 =
|
16
|
+
response = Rack::Response.new(find(request.params), 200, headers)
|
17
|
+
response = maybe_cache_response(response)
|
19
18
|
rescue Exception => e
|
20
|
-
response =
|
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
|
-
|
9
|
-
|
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
|
-
|
13
|
+
response = Rack::Response.new({ :error => e.message }.to_json, 500, headers)
|
13
14
|
end
|
14
15
|
|
15
|
-
|
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
|
data/lib/postal_code_lookup.rb
CHANGED
@@ -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" => "
|
20
|
-
r = Rack::Response # shorthand
|
18
|
+
headers = { "Content-Type" => "application/json" }
|
21
19
|
begin
|
22
|
-
response =
|
20
|
+
response = Rack::Response.new(find(request.params['code']), 200, headers)
|
21
|
+
response = maybe_cache_response(response)
|
23
22
|
rescue Exception => e
|
24
|
-
response =
|
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
|