rgeoserver 0.5.9 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/Gemfile +1 -7
- data/Gemfile.lock +147 -0
- data/README.rdoc +69 -78
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/bin/batch_load.rb +138 -0
- data/config/defaults.yml +17 -0
- data/examples/batch_demo.rb +170 -0
- data/examples/catalog_migration.rb +2 -2
- data/examples/cluster_demo.rb +1 -1
- data/examples/coverage_stores.rb +42 -0
- data/examples/demo_druid_workspace.rb +6 -0
- data/examples/deploy_dor_layer.rb +19 -0
- data/examples/sql_layer_demo.rb +1 -1
- data/lib/rgeoserver/catalog.rb +83 -86
- data/lib/rgeoserver/config.rb +11 -1
- data/lib/rgeoserver/coverage.rb +43 -15
- data/lib/rgeoserver/coveragestore.rb +46 -11
- data/lib/rgeoserver/datastore.rb +73 -62
- data/lib/rgeoserver/featuretype.rb +134 -71
- data/lib/rgeoserver/geoserver_url_helpers.rb +140 -10
- data/lib/rgeoserver/layer.rb +52 -16
- data/lib/rgeoserver/layergroup.rb +1 -2
- data/lib/rgeoserver/namespace.rb +1 -2
- data/lib/rgeoserver/resource.rb +37 -34
- data/lib/rgeoserver/rest_api_client.rb +33 -14
- data/lib/rgeoserver/style.rb +1 -2
- data/lib/rgeoserver/utils/boundingbox.rb +59 -16
- data/lib/rgeoserver/utils/metadata.rb +21 -0
- data/lib/rgeoserver/version.rb +1 -1
- data/lib/rgeoserver/wmsstore.rb +1 -6
- data/lib/rgeoserver/workspace.rb +23 -8
- data/lib/rgeoserver.rb +31 -38
- data/rgeoserver.gemspec +28 -20
- data/spec/fixtures/load_ex1.yml +45 -0
- data/spec/fixtures/load_ex2.yml +14 -0
- data/spec/functional/catalog_spec.rb +71 -0
- data/spec/functional/rest_api_client_spec.rb +215 -0
- data/spec/integration/geoserver_spec.rb +2 -2
- metadata +120 -66
- data/config/config_defaults.yml +0 -10
- data/examples/batch_example.rb +0 -105
data/lib/rgeoserver/coverage.rb
CHANGED
@@ -7,21 +7,33 @@ module RGeoServer
|
|
7
7
|
:catalog => "catalog",
|
8
8
|
:workspace => "workspace",
|
9
9
|
:coverage_store => "coverage_store",
|
10
|
+
:enabled => "enabled",
|
10
11
|
:name => "name",
|
11
12
|
:title => "title",
|
12
13
|
:abstract => "abstract",
|
14
|
+
:keywords => "keywords",
|
15
|
+
:metadata => "metadata",
|
13
16
|
:metadata_links => "metadataLinks"
|
14
17
|
}
|
15
18
|
OBJ_DEFAULT_ATTRIBUTES = {
|
16
19
|
:catalog => nil,
|
17
20
|
:workspace => nil,
|
18
21
|
:coverage_store => nil,
|
22
|
+
:enabled => "true",
|
19
23
|
:name => nil,
|
20
24
|
:title => nil,
|
21
25
|
:abstract => nil,
|
26
|
+
:keywords => [],
|
27
|
+
:metadata => {},
|
22
28
|
:metadata_links => []
|
23
29
|
}
|
24
30
|
|
31
|
+
# see http://inspire.ec.europa.eu/schemas/common/1.0/common.xsd
|
32
|
+
@@metadata_types = {
|
33
|
+
'ISO19139' => 'application/vnd.iso.19139+xml',
|
34
|
+
'TC211' => 'application/vnd.iso.19139+xml'
|
35
|
+
}
|
36
|
+
|
25
37
|
define_attribute_methods OBJ_ATTRIBUTES.keys
|
26
38
|
update_attribute_accessors OBJ_ATTRIBUTES
|
27
39
|
|
@@ -45,24 +57,36 @@ module RGeoServer
|
|
45
57
|
@@route % [@workspace.name , @coverage_store.name]
|
46
58
|
end
|
47
59
|
|
60
|
+
def to_mimetype(type, default = 'text/xml')
|
61
|
+
return @@metadata_types[type.upcase] if @@metadata_types.include?(type.upcase)
|
62
|
+
default
|
63
|
+
end
|
64
|
+
|
48
65
|
def message
|
49
66
|
builder = Nokogiri::XML::Builder.new do |xml|
|
50
67
|
xml.coverage {
|
51
68
|
xml.name @name
|
52
69
|
xml.title @title
|
53
|
-
|
70
|
+
xml.enabled @enabled if enabled_changed? or new?
|
71
|
+
if new?
|
54
72
|
xml.nativeName @name
|
55
73
|
xml.abstract @abtract if abstract_changed?
|
56
74
|
xml.metadataLinks {
|
57
|
-
@metadata_links.each
|
75
|
+
@metadata_links.each do |m|
|
58
76
|
xml.metadataLink {
|
59
|
-
xml.type_ m['
|
77
|
+
xml.type_ to_mimetype(m['metadataType'])
|
60
78
|
xml.metadataType m['metadataType']
|
61
79
|
xml.content m['content']
|
62
80
|
}
|
63
|
-
|
64
|
-
} if
|
81
|
+
end
|
82
|
+
} if @metadata_links
|
65
83
|
end
|
84
|
+
xml.keywords {
|
85
|
+
@keywords.each do |k|
|
86
|
+
xml.keyword RGeoServer::Metadata::to_keyword(k)
|
87
|
+
end
|
88
|
+
} if @keywords and new? or keywords_changed?
|
89
|
+
|
66
90
|
}
|
67
91
|
end
|
68
92
|
@message = builder.doc.to_xml
|
@@ -71,9 +95,8 @@ module RGeoServer
|
|
71
95
|
# @param [RGeoServer::Catalog] catalog
|
72
96
|
# @param [Hash] options
|
73
97
|
def initialize catalog, options
|
74
|
-
super(
|
98
|
+
super(catalog)
|
75
99
|
_run_initialize_callbacks do
|
76
|
-
@catalog = catalog
|
77
100
|
workspace = options[:workspace] || 'default'
|
78
101
|
if workspace.instance_of? String
|
79
102
|
@workspace = @catalog.get_workspace(workspace)
|
@@ -94,9 +117,10 @@ module RGeoServer
|
|
94
117
|
@name = options[:name]
|
95
118
|
@enabled = options[:enabled] || true
|
96
119
|
@route = route
|
97
|
-
end
|
120
|
+
end
|
98
121
|
end
|
99
122
|
|
123
|
+
# @return [Hash] extraction from GeoServer XML for this coverage
|
100
124
|
def profile_xml_to_hash profile_xml
|
101
125
|
doc = profile_xml_to_ng profile_xml
|
102
126
|
h = {
|
@@ -123,17 +147,21 @@ module RGeoServer
|
|
123
147
|
},
|
124
148
|
"abstract" => doc.at_xpath('//abstract/text()').to_s,
|
125
149
|
"supportedFormats" => doc.xpath('//supportedFormats/string').collect{ |t| t.to_s },
|
126
|
-
"
|
127
|
-
{
|
128
|
-
'
|
150
|
+
"keywords" => doc.at_xpath('//keywords').collect { |kl|
|
151
|
+
{
|
152
|
+
'keyword' => kl.at_xpath('//string/text()').to_s
|
153
|
+
}
|
154
|
+
},
|
155
|
+
"metadataLinks" => doc.xpath('//metadataLinks/metadataLink').collect{ |m|
|
156
|
+
{
|
157
|
+
'type' => m.at_xpath('//type/text()').to_s,
|
129
158
|
'metadataType' => m.at_xpath('//metadataType/text()').to_s,
|
130
|
-
'content' => m.at_xpath('//content').
|
131
|
-
}
|
132
|
-
}
|
159
|
+
'content' => m.at_xpath('//content/text()').to_s
|
160
|
+
}
|
161
|
+
},
|
133
162
|
}.freeze
|
134
163
|
h
|
135
164
|
end
|
136
165
|
|
137
|
-
|
138
166
|
end
|
139
167
|
end
|
@@ -3,8 +3,24 @@ module RGeoServer
|
|
3
3
|
# A coverage store is a source of spatial data that is raster based.
|
4
4
|
class CoverageStore < ResourceInfo
|
5
5
|
|
6
|
-
OBJ_ATTRIBUTES = {
|
7
|
-
|
6
|
+
OBJ_ATTRIBUTES = {
|
7
|
+
:catalog => 'catalog',
|
8
|
+
:workspace => 'workspace',
|
9
|
+
:url => 'url',
|
10
|
+
:data_type => 'type',
|
11
|
+
:name => 'name',
|
12
|
+
:enabled => 'enabled',
|
13
|
+
:description => 'description'
|
14
|
+
}
|
15
|
+
OBJ_DEFAULT_ATTRIBUTES = {
|
16
|
+
:catalog => nil,
|
17
|
+
:workspace => nil,
|
18
|
+
:url => '',
|
19
|
+
:data_type => 'GeoTIFF',
|
20
|
+
:name => nil,
|
21
|
+
:enabled => 'true',
|
22
|
+
:description=>nil
|
23
|
+
}
|
8
24
|
define_attribute_methods OBJ_ATTRIBUTES.keys
|
9
25
|
update_attribute_accessors OBJ_ATTRIBUTES
|
10
26
|
|
@@ -56,9 +72,8 @@ module RGeoServer
|
|
56
72
|
# @param [RGeoServer::Workspace|String] workspace
|
57
73
|
# @param [String] name
|
58
74
|
def initialize catalog, options
|
59
|
-
super(
|
75
|
+
super(catalog)
|
60
76
|
_run_initialize_callbacks do
|
61
|
-
@catalog = catalog
|
62
77
|
workspace = options[:workspace] || 'default'
|
63
78
|
if workspace.instance_of? String
|
64
79
|
@workspace = @catalog.get_workspace(workspace)
|
@@ -72,21 +87,41 @@ module RGeoServer
|
|
72
87
|
end
|
73
88
|
end
|
74
89
|
|
75
|
-
def coverages
|
76
|
-
self.class.list Coverage, @catalog, profile['coverages'], {:workspace => @workspace, :coverage_store => self},
|
90
|
+
def coverages
|
91
|
+
yield self.class.list Coverage, @catalog, profile['coverages'], {:workspace => @workspace, :coverage_store => self}, true
|
77
92
|
end
|
78
93
|
|
94
|
+
# <coverageStore>
|
95
|
+
# <name>antietam_1867</name>
|
96
|
+
# <description>
|
97
|
+
# Map shows the U.S. Civil War battle of Antietam. It indicates fortifications, roads, railroads, houses, names of residents, fences, drainage, vegetation, and relief by hachures.
|
98
|
+
# </description>
|
99
|
+
# <type>GeoTIFF</type>
|
100
|
+
# <enabled>true</enabled>
|
101
|
+
# <workspace>
|
102
|
+
# <name>druid</name>
|
103
|
+
# <atom:link xmlns:atom="http://www.w3.org/2005/Atom" rel="alternate" href="http://localhost:8080/geoserver/rest/workspaces/druid.xml" type="application/xml"/>
|
104
|
+
# </workspace>
|
105
|
+
# <__default>false</__default>
|
106
|
+
# <url>
|
107
|
+
# file:///var/geoserver/current/staging/rumsey/g3881015alpha.tif
|
108
|
+
# </url>
|
109
|
+
# <coverages>
|
110
|
+
# <atom:link xmlns:atom="http://www.w3.org/2005/Atom" rel="alternate" href="http://localhost:8080/geoserver/rest/workspaces/druid/coveragestores/antietam_1867/coverages.xml" type="application/xml"/>
|
111
|
+
# </coverages>
|
112
|
+
# </coverageStore>
|
79
113
|
def profile_xml_to_hash profile_xml
|
80
|
-
doc = profile_xml_to_ng profile_xml
|
114
|
+
doc = profile_xml_to_ng profile_xml
|
81
115
|
h = {
|
82
116
|
'name' => doc.at_xpath('//name').text.strip,
|
83
|
-
'
|
117
|
+
'description' => doc.at_xpath('//description/text()').to_s,
|
84
118
|
'type' => doc.at_xpath('//type/text()').to_s,
|
85
119
|
'enabled' => doc.at_xpath('//enabled/text()').to_s,
|
86
|
-
'
|
87
|
-
'
|
120
|
+
'url' => doc.at_xpath('//url/text()').to_s,
|
121
|
+
'workspace' => @workspace.name # Assume correct workspace
|
88
122
|
}
|
89
|
-
doc.xpath('//coverages/atom:link/@href',
|
123
|
+
doc.xpath('//coverages/atom:link[@rel="alternate"]/@href',
|
124
|
+
"xmlns:atom"=>"http://www.w3.org/2005/Atom" ).each{ |l|
|
90
125
|
h['coverages'] = begin
|
91
126
|
response = @catalog.do_url l.text
|
92
127
|
Nokogiri::XML(response).xpath('//name/text()').collect{ |a| a.text.strip }
|
data/lib/rgeoserver/datastore.rb
CHANGED
@@ -23,8 +23,23 @@ module RGeoServer
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
OBJ_ATTRIBUTES = {
|
27
|
-
|
26
|
+
OBJ_ATTRIBUTES = {
|
27
|
+
:workspace => 'workspace',
|
28
|
+
:connection_parameters => "connection_parameters",
|
29
|
+
:name => 'name',
|
30
|
+
:data_type => 'type',
|
31
|
+
:enabled => 'enabled',
|
32
|
+
:description => 'description'
|
33
|
+
}
|
34
|
+
OBJ_DEFAULT_ATTRIBUTES = {
|
35
|
+
:workspace => nil,
|
36
|
+
:connection_parameters => {},
|
37
|
+
:name => nil,
|
38
|
+
:data_type => :shapefile,
|
39
|
+
:enabled => true,
|
40
|
+
:description => nil
|
41
|
+
}
|
42
|
+
|
28
43
|
define_attribute_methods OBJ_ATTRIBUTES.keys
|
29
44
|
update_attribute_accessors OBJ_ATTRIBUTES
|
30
45
|
|
@@ -62,33 +77,36 @@ module RGeoServer
|
|
62
77
|
builder = Nokogiri::XML::Builder.new do |xml|
|
63
78
|
xml.dataStore {
|
64
79
|
xml.name @name
|
65
|
-
xml.enabled enabled
|
80
|
+
xml.enabled @enabled
|
81
|
+
xml.description @description
|
82
|
+
xml.type_ @data_type if (data_type_changed? || new?)
|
66
83
|
xml.connectionParameters { # this could be empty
|
67
|
-
connection_parameters.each_pair { |k,v|
|
84
|
+
@connection_parameters.each_pair { |k,v|
|
68
85
|
xml.entry(:key => k) {
|
69
86
|
xml.text v
|
70
87
|
}
|
71
|
-
} unless connection_parameters.nil? || connection_parameters.empty?
|
88
|
+
} unless @connection_parameters.nil? || @connection_parameters.empty?
|
72
89
|
}
|
73
90
|
}
|
74
91
|
end
|
92
|
+
# ap builder.doc
|
75
93
|
builder.doc.to_xml
|
76
94
|
end
|
77
95
|
|
78
96
|
# @param [RGeoServer::Catalog] catalog
|
79
|
-
# @param [RGeoServer::Workspace|String] workspace
|
80
|
-
# @param [String] name
|
97
|
+
# @param [RGeoServer::Workspace|String] options `:workspace`
|
98
|
+
# @param [String] options `:name`
|
81
99
|
def initialize catalog, options
|
82
100
|
super({})
|
83
101
|
_run_initialize_callbacks do
|
84
102
|
@catalog = catalog
|
85
103
|
workspace = options[:workspace] || 'default'
|
86
104
|
if workspace.instance_of? String
|
87
|
-
@workspace =
|
105
|
+
@workspace = catalog.get_workspace(workspace)
|
88
106
|
elsif workspace.instance_of? Workspace
|
89
107
|
@workspace = workspace
|
90
108
|
else
|
91
|
-
raise "Not a valid workspace"
|
109
|
+
raise ArgumentError, "Not a valid workspace: #{workspace}"
|
92
110
|
end
|
93
111
|
|
94
112
|
@name = options[:name].strip
|
@@ -97,60 +115,48 @@ module RGeoServer
|
|
97
115
|
end
|
98
116
|
|
99
117
|
def featuretypes &block
|
100
|
-
self.class.list FeatureType,
|
118
|
+
self.class.list FeatureType, catalog, profile['featureTypes'], {:workspace => @workspace, :data_store => self}, true, &block
|
101
119
|
end
|
102
120
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
121
|
+
def upload_file local_file, publish = {}
|
122
|
+
upload local_file, :file, data_type, publish
|
123
|
+
end
|
124
|
+
def upload_external remote_file, publish = {}
|
125
|
+
puts "Uploading external file #{remote_file} #{publish}"
|
126
|
+
upload remote_file, :external, data_type, publish
|
127
|
+
end
|
128
|
+
def upload_url url, publish = {}
|
129
|
+
upload url, :url, data_type, publish
|
130
|
+
end
|
131
|
+
|
132
|
+
# @param [String] path - location of upload data
|
133
|
+
# @param [Symbol] upload_method -- flag for :file, :url, or :external
|
134
|
+
# @param [Symbol] data_type -- currently only :shapefile
|
135
|
+
# @param [Boolean] publish -- only valid for :file
|
136
|
+
def upload path, upload_method = :file, data_type = :shapefile, publish = false
|
137
|
+
ap({ :path => path, :upload_method => upload_method, :data_type => data_type, :publish => publish, :self => self}) if $DEBUG
|
107
138
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
else
|
118
|
-
raise DataTypeNotExpected, data_type
|
119
|
-
end
|
120
|
-
|
121
|
-
@catalog.client[upload_url_suffix].put File.read(file_path), :content_type => 'application/zip'
|
122
|
-
|
123
|
-
clear
|
124
|
-
connection_parameters['url'] = connection_parameters['url'].gsub(/.*data/, '').insert(0, 'file:data') #correct to relative path
|
125
|
-
save
|
126
|
-
clear
|
127
|
-
|
128
|
-
if publish
|
129
|
-
ft = RGeoServer::FeatureType.new @catalog, :workspace => @workspace, :data_store => self, :name => @name
|
130
|
-
ft.title = ft.name.capitalize
|
131
|
-
ft.abstract = ft.name.capitalize
|
132
|
-
ft.enabled = true
|
133
|
-
|
134
|
-
bounds = case data_type
|
135
|
-
when :shapefile
|
136
|
-
shpInfo = ShapefileInfo.new file_path
|
137
|
-
shpInfo.bounds
|
138
|
-
else
|
139
|
-
raise DataTypeNotExpected, data_type
|
140
|
-
end
|
141
|
-
|
142
|
-
ft.native_bounds['minx'], ft.native_bounds['miny'], ft.native_bounds['maxx'], ft.native_bounds['maxy'] =
|
143
|
-
bounds.to_a
|
144
|
-
ft.projection_policy = :force
|
145
|
-
ft.save
|
146
|
-
|
147
|
-
layers = catalog.get_layers workspace: @workspace
|
148
|
-
layers.find_all{ |layer| layer.name == ft.name }.each do |layer|
|
149
|
-
layer.enabled = true
|
150
|
-
layer.save
|
139
|
+
raise DataStoreAlreadyExists, @name unless new?
|
140
|
+
raise DataTypeNotExpected, data_type unless [:shapefile].include? data_type
|
141
|
+
|
142
|
+
ext = 'shp'
|
143
|
+
case upload_method
|
144
|
+
when :file then # local file that we post
|
145
|
+
local_file = File.expand_path(path)
|
146
|
+
unless local_file =~ %r{\.zip$} and File.exist? local_file
|
147
|
+
raise ArgumentError, "Shapefile upload must be ZIP file: #{local_file}"
|
151
148
|
end
|
149
|
+
puts "Uploading #{File.size(local_file)} bytes from file #{local_file}..."
|
150
|
+
|
151
|
+
catalog.client["#{route}/#{name}/file.#{ext}"].put File.read(local_file), :content_type => 'application/zip'
|
152
|
+
refresh
|
153
|
+
when :external then # remote file that we reference
|
154
|
+
catalog.client["#{route}/#{name}/external.#{ext}"].put path, :content_type => 'text/plain'
|
155
|
+
when :url then
|
156
|
+
catalog.client["#{route}/#{name}/url.#{ext}"].put path, :content_type => 'text/plain'
|
157
|
+
else
|
158
|
+
raise NotImplementedError, "Unsupported upload method #{upload_method}"
|
152
159
|
end
|
153
|
-
|
154
160
|
self
|
155
161
|
end
|
156
162
|
|
@@ -158,17 +164,22 @@ module RGeoServer
|
|
158
164
|
doc = profile_xml_to_ng profile_xml
|
159
165
|
h = {
|
160
166
|
"name" => doc.at_xpath('//name').text.strip,
|
167
|
+
"description" => doc.at_xpath('//description/text()').to_s,
|
161
168
|
"enabled" => doc.at_xpath('//enabled/text()').to_s,
|
162
|
-
|
169
|
+
'type' => doc.at_xpath('//type/text()').to_s,
|
170
|
+
"connection_parameters" => doc.xpath('//connectionParameters/entry').inject({}){ |x, e| x.merge(e['key']=> e.text.to_s) }
|
163
171
|
}
|
164
|
-
|
172
|
+
# XXX: assume that we know the workspace for <workspace>...</workspace>
|
173
|
+
doc.xpath('//featureTypes/atom:link[@rel="alternate"]/@href',
|
174
|
+
"xmlns:atom"=>"http://www.w3.org/2005/Atom" ).each do |l|
|
165
175
|
h["featureTypes"] = begin
|
166
|
-
response =
|
176
|
+
response = catalog.do_url l.text
|
177
|
+
# lazy loading: only loads featuretype names
|
167
178
|
Nokogiri::XML(response).xpath('//name/text()').collect{ |a| a.text.strip }
|
168
179
|
rescue RestClient::ResourceNotFound
|
169
180
|
[]
|
170
181
|
end.freeze
|
171
|
-
|
182
|
+
end
|
172
183
|
h
|
173
184
|
end
|
174
185
|
end
|