rgeoserver 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/Gemfile +8 -0
- data/README.rdoc +80 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/config/config_defaults.yml +9 -0
- data/lib/rgeoserver.rb +45 -0
- data/lib/rgeoserver/catalog.rb +169 -0
- data/lib/rgeoserver/config.rb +7 -0
- data/lib/rgeoserver/coverage.rb +84 -0
- data/lib/rgeoserver/coverages.rb +114 -0
- data/lib/rgeoserver/coveragestore.rb +111 -0
- data/lib/rgeoserver/datastore.rb +110 -0
- data/lib/rgeoserver/datastores.rb +97 -0
- data/lib/rgeoserver/featuretype.rb +136 -0
- data/lib/rgeoserver/geoserver_url_helpers.rb +17 -0
- data/lib/rgeoserver/resource.rb +140 -0
- data/lib/rgeoserver/rest_api_client.rb +103 -0
- data/lib/rgeoserver/version.rb +9 -0
- data/lib/rgeoserver/wmsstore.rb +100 -0
- data/lib/rgeoserver/workspace.rb +74 -0
- data/rgeoserver.gemspec +31 -0
- data/spec/lib/integration_test_spec.rb +122 -0
- data/spec/spec_helper.rb +6 -0
- metadata +210 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
== RGeoServer
|
2
|
+
|
3
|
+
RGeoServer is a Ruby client for the GeoServer RESTful administrative interface.
|
4
|
+
|
5
|
+
It provides primitive Ruby model abstraction.
|
6
|
+
|
7
|
+
== Documentation
|
8
|
+
The GeoServer REST Configuration API Reference can be found here:
|
9
|
+
|
10
|
+
http://docs.geoserver.org/stable/en/user/restconfig/rest-config-api.html
|
11
|
+
|
12
|
+
|
13
|
+
== Installation
|
14
|
+
|
15
|
+
$ gem install rgeoserver
|
16
|
+
|
17
|
+
|
18
|
+
== Example:
|
19
|
+
|
20
|
+
> catalog = RGeoServer::Catalog.new :user=>"admin", :url=>"http://10.0.0.2/geoserver/rest", :password=>"osgeo!"
|
21
|
+
=> Catalog: http://10.0.0.2/geoserver/rest
|
22
|
+
> w = catalog.get_workspace('topp')
|
23
|
+
=> RGeoServer::Workspace: topp
|
24
|
+
> ds = w.data_stores.first
|
25
|
+
=> RGeoServer::DataStore: states_shapefile
|
26
|
+
> ds.profile
|
27
|
+
=> {"name"=>"states_shapefile", "enabled"=>"true", "connectionParameters"=>{"url"=>"file:data/shapefiles/states.shp", "namespace"=>"http://www.openplans.org/topp"}, "featureTypes"=>["states"]}
|
28
|
+
> ft = ds.featuretypes.first
|
29
|
+
=> RGeoServer::FeatureType: states
|
30
|
+
> ft.profile
|
31
|
+
=> {:name=>"states", :workspace=>"topp", :nativeName=>"states"}
|
32
|
+
|
33
|
+
== Testing
|
34
|
+
We use {jettywrapper}[https://github.com/projecthydra/jettywrapper] to wrap a test instance of GeoServer. In theory, you should be able to point to any other local installation. Suppose that you download the binary stable version 2.1.3 binary from {here}[http://sourceforge.net/project/downloading.php?groupname=geoserver&filename=geoserver-2.1.3-bin.zip&use_mirror=softlayer], then unzip it under say, /tmp/geoserver-2.1.3. The integration tests are executed as follows:
|
35
|
+
> rake integration['/tmp/geoserver-2.1.3','8080','-DGEOSERVER_DATA_DIR=data_dir']
|
36
|
+
|
37
|
+
To generate the documentation run:
|
38
|
+
> rake yard
|
39
|
+
|
40
|
+
To enter into an irb console with all classess loaded:
|
41
|
+
> rake console
|
42
|
+
|
43
|
+
== Related Resources
|
44
|
+
* {OSGeo The Open Source Geospatial Foundation}[http://www.osgeo.org]
|
45
|
+
* {GeoServer Project page}[http://docs.geoserver.org/stable/en/user/index.html]
|
46
|
+
* {GeoServer Catalog design}[http://bit.ly/JrX1J8]
|
47
|
+
* {GeoTools}[http://geotools.org/]
|
48
|
+
* Implementation in other languages:
|
49
|
+
* Python: {gsconfig.py}[https://github.com/dwins/gsconfig.py]
|
50
|
+
|
51
|
+
== Release History
|
52
|
+
|
53
|
+
- <b>v0.5.0</b> - Initial alpha release
|
54
|
+
|
55
|
+
== TODO
|
56
|
+
- Complete stores and coverages functionality, handle Layers, styles and data upload.
|
57
|
+
- Complete documentaion and test coverage. Add more flexibility for integration tests with embedded Jetty and other containers.
|
58
|
+
- Migrate base HTTP client to {Weary}[https://github.com/mwunsch/weary]?
|
59
|
+
|
60
|
+
== Contributing with Patches and Pull requests checklist
|
61
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
62
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
63
|
+
* Fork the project
|
64
|
+
* Start a feature/bugfix branch
|
65
|
+
* Commit and push until you are happy with your contribution
|
66
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
67
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
68
|
+
|
69
|
+
|
70
|
+
== Acknowledgements
|
71
|
+
Inspired on the Rubydora and RSolr gems. Followed somewhat closely to {gsconfig.py}[https://github.com/dwins/gsconfig.py]
|
72
|
+
|
73
|
+
== Contributors
|
74
|
+
|
75
|
+
|
76
|
+
== License
|
77
|
+
|
78
|
+
Copyright (c) 2011 Renzo Sanchez-Silva <renzo@stanford.edu>.
|
79
|
+
|
80
|
+
Licensed under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get your spec rake tasks working in RSpec 2.0
|
14
|
+
|
15
|
+
require 'rspec/core/rake_task'
|
16
|
+
|
17
|
+
desc 'Default: run specs.'
|
18
|
+
task :default => :spec
|
19
|
+
|
20
|
+
desc "Run specs"
|
21
|
+
RSpec::Core::RakeTask.new do |t|
|
22
|
+
# Put spec opts in a file named .rspec in root
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'yard'
|
26
|
+
YARD::Rake::YardocTask.new do |t|
|
27
|
+
t.options = ["--readme", "README.rdoc"]
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Open an irb session preloaded with this library"
|
31
|
+
task :console do
|
32
|
+
sh "irb -rubygems -I lib -r rgeoserver.rb"
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Execute integration tests"
|
36
|
+
task :integration, :jetty_home, :jetty_port, :java_opts do |t, args|
|
37
|
+
|
38
|
+
require 'jettywrapper'
|
39
|
+
jetty_params = {
|
40
|
+
:jetty_home => args.jetty_home,
|
41
|
+
:java_opts => [args.java_opts],
|
42
|
+
:jetty_port => args.jetty_port,
|
43
|
+
:quiet => true,
|
44
|
+
:startup_wait => 20
|
45
|
+
}
|
46
|
+
|
47
|
+
error = Jettywrapper.wrap(jetty_params) do
|
48
|
+
Rake::Task['spec'].invoke
|
49
|
+
end
|
50
|
+
raise "test failures: #{error}" if error
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.1
|
data/lib/rgeoserver.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module RGeoServer
|
5
|
+
autoload :Config, "rgeoserver/config"
|
6
|
+
autoload :Catalog, "rgeoserver/catalog"
|
7
|
+
autoload :RestApiClient, "rgeoserver/rest_api_client"
|
8
|
+
autoload :GeoServerUrlHelpers, "rgeoserver/geoserver_url_helpers"
|
9
|
+
autoload :ResourceInfo, "rgeoserver/resource"
|
10
|
+
autoload :Workspace, "rgeoserver/workspace"
|
11
|
+
autoload :FeatureType, "rgeoserver/featuretype"
|
12
|
+
autoload :Coverage, "rgeoserver/coverage"
|
13
|
+
autoload :DataStore, "rgeoserver/datastore"
|
14
|
+
autoload :CoverageStore, "rgeoserver/coveragestore"
|
15
|
+
autoload :WmsStore, "rgeoserver/wmsstore"
|
16
|
+
|
17
|
+
require 'restclient'
|
18
|
+
require 'nokogiri'
|
19
|
+
require 'time'
|
20
|
+
require 'rgeoserver/version'
|
21
|
+
|
22
|
+
def self.connect *args
|
23
|
+
Catalog.new *args
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.catalog
|
27
|
+
@catalog ||= self.connect(self.default_config.geoserver)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.catalog= catalog
|
31
|
+
@catalog = catalog
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.default_config *args, &block
|
35
|
+
Config.configure *args, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
class RGeoServerError < StandardError
|
40
|
+
end
|
41
|
+
|
42
|
+
class GeoServerInvalidRequest < RGeoServerError
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module RGeoServer
|
2
|
+
# This class represents the main class of the data model.
|
3
|
+
# Refer to
|
4
|
+
# - http://geoserver.org/display/GEOS/Catalog+Design
|
5
|
+
# - http://docs.geoserver.org/stable/en/user/restconfig/rest-config-api.html#workspaces
|
6
|
+
|
7
|
+
class Catalog
|
8
|
+
include RGeoServer::RestApiClient
|
9
|
+
include ActiveSupport::Benchmarkable
|
10
|
+
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
# @param [OrderedHash] options
|
14
|
+
# @option options [String] :url
|
15
|
+
# @option options [String] :user
|
16
|
+
# @option options [String] :password
|
17
|
+
def initialize options = {}
|
18
|
+
@config = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"Catalog: #{@config[:url]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def client config = {}
|
26
|
+
c = self.config.merge(config)
|
27
|
+
@client ||= RestClient::Resource.new(c[:url], :user => c[:user], :password => c[:password], :headers => c[:headers])
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers format
|
31
|
+
sym = :xml || format.to_sym
|
32
|
+
{:accept => sym, :content_type=> sym}
|
33
|
+
end
|
34
|
+
|
35
|
+
# List of workspaces available
|
36
|
+
# @return [Array<RGeoServer::Workspace>]
|
37
|
+
def get_workspaces
|
38
|
+
response = self.search :workspaces => nil
|
39
|
+
doc = Nokogiri::XML(response)
|
40
|
+
doc.xpath(Workspace.root_xpath).collect { |w| Workspace.new self, :name => w.text }
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [String] workspace
|
44
|
+
# @return [<RGeoServer::Workspace]
|
45
|
+
def get_workspace workspace
|
46
|
+
response = self.search :workspaces => workspace
|
47
|
+
doc = Nokogiri::XML(response)
|
48
|
+
name = doc.at_xpath(Workspace.member_xpath)
|
49
|
+
return Workspace.new self, :name => name.text if name
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [<RGeoServer::Workspace]
|
53
|
+
def get_default_workspace
|
54
|
+
return Workspace.new self, :name => 'default'
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_default_workspace
|
58
|
+
raise NotImplementedError
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [String] store
|
62
|
+
# @param [String] workspace
|
63
|
+
def reassign_workspace store, workspace
|
64
|
+
pass
|
65
|
+
end
|
66
|
+
|
67
|
+
# List of feature types
|
68
|
+
# @return [Array<RGeoServer::Namespace>]
|
69
|
+
# TODO: Implement when the stable release includes it
|
70
|
+
def get_namespaces
|
71
|
+
raise NotImplementedError
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_default_namespace
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
|
78
|
+
def set_default_namespace id, prefix, uri
|
79
|
+
raise NotImplementedError
|
80
|
+
end
|
81
|
+
|
82
|
+
#= Data Stores (Vector datasets)
|
83
|
+
|
84
|
+
# List of vector based spatial data
|
85
|
+
# @param [String] workspace
|
86
|
+
# @return [Array<RGeoServer::DataStore>]
|
87
|
+
def get_data_stores workspace = nil
|
88
|
+
ws = workspace.nil?? get_workspaces : [get_workspace(workspace)]
|
89
|
+
ds = []
|
90
|
+
ws.each{ |w| ds += w.data_stores if w.data_stores }
|
91
|
+
ds
|
92
|
+
end
|
93
|
+
|
94
|
+
# @param [String] workspace
|
95
|
+
# @param [String] datastore
|
96
|
+
# @return [RGeoServer::DataStore]
|
97
|
+
def get_data_store workspace, datastore
|
98
|
+
response = self.search({:workspaces => workspace, :name => datastore})
|
99
|
+
doc = Nokogiri::XML(response)
|
100
|
+
name = doc.at_xpath(DataStore.member_xpath)
|
101
|
+
return DataStore.new self, workspace, name.text if name
|
102
|
+
end
|
103
|
+
|
104
|
+
# List of feature types
|
105
|
+
# @param [String] workspace
|
106
|
+
# @param [String] datastore
|
107
|
+
# @return [Array<RGeoServer::FeatureType>]
|
108
|
+
def get_feature_types workspace, datastore
|
109
|
+
pass
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param [String] workspace
|
114
|
+
# @param [String] datastore
|
115
|
+
# @param [String] featuretype_id
|
116
|
+
# @return [RGeoServer::FeatureType]
|
117
|
+
def get_feature_type workspace, datastore, featuretype_id
|
118
|
+
pass
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
#= Coverages (Raster datasets)
|
124
|
+
|
125
|
+
# List of coverage stores
|
126
|
+
# @param [String] workspace
|
127
|
+
# @return [Array<RGeoServer::CoverageStore>]
|
128
|
+
def get_coverage_stores workspace = nil
|
129
|
+
ws = workspace.nil?? get_workspaces : [get_workspace(workspace)]
|
130
|
+
cs = []
|
131
|
+
ws.each{ |w| cs += w.coverage_stores if w.coverage_stores }
|
132
|
+
cs
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param [String] workspace
|
136
|
+
# @param [String] coveragestore
|
137
|
+
# @return [RGeoServer::CoverageStore]
|
138
|
+
def get_coverage_store workspace, coveragestore
|
139
|
+
response = self.search({:workspaces => workspace, :name => coveragestore})
|
140
|
+
doc = Nokogiri::XML(response)
|
141
|
+
name = doc.at_xpath(CoverageStore.member_xpath)
|
142
|
+
return CoverageStore.new self, workspace, name.text if name
|
143
|
+
end
|
144
|
+
|
145
|
+
#= WMS Stores (Web Map Services)
|
146
|
+
# TODO: Implement when the stable release includes it
|
147
|
+
# List of WMS stores.
|
148
|
+
# @param [String] workspace
|
149
|
+
# @return [Array<RGeoServer::WmsStore>]
|
150
|
+
def get_wms_stores workspace = nil
|
151
|
+
ws = workspace.nil?? get_workspaces : [get_workspace(workspace)]
|
152
|
+
cs = []
|
153
|
+
ws.each{ |w| cs += w.wms_stores if w.wms_stores }
|
154
|
+
cs
|
155
|
+
end
|
156
|
+
|
157
|
+
# @param [String] workspace
|
158
|
+
# @param [String] wmsstore
|
159
|
+
# @return [RGeoServer::WmsStore]
|
160
|
+
def get_wms_store workspace, wmsstore
|
161
|
+
response = self.search({:workspaces => workspace, :name => wmsstore})
|
162
|
+
doc = Nokogiri::XML(response)
|
163
|
+
name = doc.at_xpath(WmsStore.member_xpath)
|
164
|
+
return WmsStore.new self, workspace, name.text if name
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
|
2
|
+
module RGeoServer
|
3
|
+
|
4
|
+
class Coverage < ResourceInfo
|
5
|
+
|
6
|
+
OBJ_ATTRIBUTES = {:catalog => "catalog", :name => "name", :workspace => "workspace", :enabled => "enabled" }
|
7
|
+
OBJ_DEFAULT_ATTRIBUTES = {:catalog => nil, :workspace => nil, :coverage_store => nil, :name => nil, :enabled => false }
|
8
|
+
|
9
|
+
define_attribute_methods OBJ_ATTRIBUTES.keys
|
10
|
+
update_attribute_accessors OBJ_ATTRIBUTES
|
11
|
+
|
12
|
+
@@r = Confstruct::Configuration.new(
|
13
|
+
:route => "workspaces/%s/coveragestores/%s/coverages",
|
14
|
+
:root => "coverages",
|
15
|
+
:resource_name => "coverage"
|
16
|
+
)
|
17
|
+
|
18
|
+
def self.root
|
19
|
+
@@r.root
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.method
|
23
|
+
:put
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.member_xpath
|
27
|
+
"//#{resource_name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.resource_name
|
31
|
+
@@r.resource_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def route
|
35
|
+
@@r.route % [@workspace.name , @coverage_store.name]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# @param [RGeoServer::Catalog] catalog
|
40
|
+
# @param [Hash] options
|
41
|
+
def initialize catalog, options
|
42
|
+
super({})
|
43
|
+
_run_initialize_callbacks do
|
44
|
+
@catalog = catalog
|
45
|
+
workspace = options[:workspace] || 'default'
|
46
|
+
if workspace.instance_of? String
|
47
|
+
@workspace = @catalog.get_workspace(workspace)
|
48
|
+
elsif workspace.instance_of? Workspace
|
49
|
+
@workspace = workspace
|
50
|
+
else
|
51
|
+
raise "Not a valid workspace"
|
52
|
+
end
|
53
|
+
coverage_store = options[:coverage_store]
|
54
|
+
if coverage_store.instance_of? String
|
55
|
+
@coverage_store = CoverageStore.new @catalog, :workspace => @workspace, :name => coverage_store
|
56
|
+
elsif coverage_store.instance_of? CoverageStore
|
57
|
+
@coverage_store = coverage_store
|
58
|
+
else
|
59
|
+
raise "Not a valid coverage store"
|
60
|
+
end
|
61
|
+
|
62
|
+
@name = options[:name]
|
63
|
+
@type = options[:type]
|
64
|
+
@enabled = options[:enabled] || true
|
65
|
+
@route = route
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def profile_xml_to_hash profile_xml
|
70
|
+
doc = profile_xml_to_ng profile_xml
|
71
|
+
h = {
|
72
|
+
"coverage_store" => @coverage_store.name,
|
73
|
+
"workspace" => @workspace.name,
|
74
|
+
"name" => doc.at_xpath('//name/text()').text.strip,
|
75
|
+
"nativeName" => doc.at_xpath('//nativeName/text()').to_s,
|
76
|
+
"title" => doc.at_xpath('//title/text()').to_s,
|
77
|
+
"supportedFormats" => doc.xpath('//supportedFormats/string/text()').collect{ |t| t.to_s }
|
78
|
+
}.freeze
|
79
|
+
h
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|