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