rgeoserver 0.5.9 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.gitignore +1 -1
  2. data/Gemfile +1 -7
  3. data/Gemfile.lock +147 -0
  4. data/README.rdoc +69 -78
  5. data/Rakefile +2 -1
  6. data/VERSION +1 -1
  7. data/bin/batch_load.rb +138 -0
  8. data/config/defaults.yml +17 -0
  9. data/examples/batch_demo.rb +170 -0
  10. data/examples/catalog_migration.rb +2 -2
  11. data/examples/cluster_demo.rb +1 -1
  12. data/examples/coverage_stores.rb +42 -0
  13. data/examples/demo_druid_workspace.rb +6 -0
  14. data/examples/deploy_dor_layer.rb +19 -0
  15. data/examples/sql_layer_demo.rb +1 -1
  16. data/lib/rgeoserver/catalog.rb +83 -86
  17. data/lib/rgeoserver/config.rb +11 -1
  18. data/lib/rgeoserver/coverage.rb +43 -15
  19. data/lib/rgeoserver/coveragestore.rb +46 -11
  20. data/lib/rgeoserver/datastore.rb +73 -62
  21. data/lib/rgeoserver/featuretype.rb +134 -71
  22. data/lib/rgeoserver/geoserver_url_helpers.rb +140 -10
  23. data/lib/rgeoserver/layer.rb +52 -16
  24. data/lib/rgeoserver/layergroup.rb +1 -2
  25. data/lib/rgeoserver/namespace.rb +1 -2
  26. data/lib/rgeoserver/resource.rb +37 -34
  27. data/lib/rgeoserver/rest_api_client.rb +33 -14
  28. data/lib/rgeoserver/style.rb +1 -2
  29. data/lib/rgeoserver/utils/boundingbox.rb +59 -16
  30. data/lib/rgeoserver/utils/metadata.rb +21 -0
  31. data/lib/rgeoserver/version.rb +1 -1
  32. data/lib/rgeoserver/wmsstore.rb +1 -6
  33. data/lib/rgeoserver/workspace.rb +23 -8
  34. data/lib/rgeoserver.rb +31 -38
  35. data/rgeoserver.gemspec +28 -20
  36. data/spec/fixtures/load_ex1.yml +45 -0
  37. data/spec/fixtures/load_ex2.yml +14 -0
  38. data/spec/functional/catalog_spec.rb +71 -0
  39. data/spec/functional/rest_api_client_spec.rb +215 -0
  40. data/spec/integration/geoserver_spec.rb +2 -2
  41. metadata +120 -66
  42. data/config/config_defaults.yml +0 -10
  43. data/examples/batch_example.rb +0 -105
@@ -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
- unless new?
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{ |m|
75
+ @metadata_links.each do |m|
58
76
  xml.metadataLink {
59
- xml.type_ m['type']
77
+ xml.type_ to_mimetype(m['metadataType'])
60
78
  xml.metadataType m['metadataType']
61
79
  xml.content m['content']
62
80
  }
63
- }
64
- } if metadata_links_changed?
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
- "metadataLinks" => doc.xpath('//metadataLinks/metadataLink').collect{ |m|
127
- {
128
- 'type' => m.at_xpath('//type/text()').to_s,
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').text.strip
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 = {:catalog => 'catalog', :workspace => 'workspace', :url => 'url', :data_type => 'type', :name => 'name', :enabled => 'enabled', :description => 'description'}
7
- OBJ_DEFAULT_ATTRIBUTES = {:catalog => nil, :workspace => nil, :url => '', :data_type => 'GeoTIFF', :name => nil, :enabled => 'true', :description=>nil}
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 &block
76
- self.class.list Coverage, @catalog, profile['coverages'], {:workspace => @workspace, :coverage_store => self}, check_remote = true, &block
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
- 'workspace' => @workspace.name,
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
- 'description' => doc.at_xpath('//description/text()').to_s,
87
- 'url' => doc.at_xpath('//url/text()').to_s
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', "xmlns:atom"=>"http://www.w3.org/2005/Atom" ).each{ |l|
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 }
@@ -23,8 +23,23 @@ module RGeoServer
23
23
  end
24
24
  end
25
25
 
26
- OBJ_ATTRIBUTES = {:enabled => "enabled", :catalog => "catalog", :workspace => "workspace", :name => "name", :connection_parameters => "connection_parameters"}
27
- OBJ_DEFAULT_ATTRIBUTES = {:enabled => 'true', :catalog => nil, :workspace => nil, :name => nil, :connection_parameters => {}}
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 = @catalog.get_workspace(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, @catalog, profile['featureTypes'], {:workspace => @workspace, :data_store => self}, check_remote = true, &block
118
+ self.class.list FeatureType, catalog, profile['featureTypes'], {:workspace => @workspace, :data_store => self}, true, &block
101
119
  end
102
120
 
103
- # @param [String] file_path
104
- # @param [Hash] options { data_type: [:shapefile] }. optional
105
- def upload_file file_path, options = {}
106
- raise DataStoreAlreadyExists, @name unless new?
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
- options = options.dup
109
-
110
- data_type = options.delete(:data_type) || :shapefile
111
- data_type = data_type.to_sym
112
-
113
- publish = options.delete(:publish) || false
114
-
115
- upload_url_suffix = case data_type
116
- when :shapefile then "#{update_route}/file.shp"
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
- "connection_parameters" => doc.xpath('//connectionParameters/entry').inject({}){ |h, e| h.merge(e['key']=> e.text.to_s) }
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
- doc.xpath('//featureTypes/atom:link/@href', "xmlns:atom"=>"http://www.w3.org/2005/Atom" ).each{ |l|
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 = @catalog.do_url l.text
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