grassgis 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 299ce1dcdebe07da42c2207549ab9fdeacc9cb2b
4
- data.tar.gz: 206df84d976c27567c4a5fe7fc3dd1949d5f92d6
3
+ metadata.gz: c0fd89fb91b60e769c7f1a29f2a683525a162480
4
+ data.tar.gz: ab57d26849db6fdbada05be7803b3c67df94f0ff
5
5
  SHA512:
6
- metadata.gz: be5e141c19ebc4880733387277d61a47fca4993ba1b74e57de8461c52b93965d83d3380864acc3aa37ac9886c53674d463b098ee0b193f2b7742c843c49a845d
7
- data.tar.gz: f9d883babf14d38607cc7430c964cfd10d017a246256b135ef6a26a6ba65db142481c94700744d975c081047506305aca059050870bb164355e8fc744ece2b29
6
+ metadata.gz: b35d3856bc5b77a5fd8eb104db5cddb113140841e59b840bfa99dc74625fa4cc3e97efbce586536b8005756b5ef4d894121d35f5b092258620c702d4e78c12fd
7
+ data.tar.gz: 4cd8093c0acb3c25556e1cefc01f0642c9220f76a7e17c3fe7c2b60a034904519d821b090bfc9ecb9f80196721bb8a87a6a5707dda20f3533115eb3cf65c064a
data/README.md CHANGED
@@ -24,26 +24,361 @@ Or install it yourself as:
24
24
  This library can prepare environments to execute GRASS commands
25
25
  from Ruby scripts.
26
26
 
27
- Example:
27
+ First we require the library:
28
28
 
29
29
  ```ruby
30
30
  require 'grassgis'
31
+ ```
32
+
33
+ ### Configuration
34
+
35
+ A GRASS session operates on a given location and mapset.
31
36
 
37
+ Before starting a GRASS session we need some configuration
38
+ parameters to specify the location of the GRASS Installation
39
+ to be used and the location/mapset. We do this by using
40
+ a Ruby Hash containing configuration parameters:
41
+
42
+ ```ruby
32
43
  configuration = {
33
- gisbase: '/usr/local/Cellar/grass-70/7.0.0/grass-7.0.0',
34
- location: 'world'
44
+ gisbase: '/usr/local/grass-7.0.0',
45
+ gisdbase: File.join(ENV['HOME'], 'grassdata'),
46
+ location: 'nc_spm',
47
+ mapset: 'user1'
35
48
  }
49
+ ```
50
+
51
+ So, you first need to know where is GRASS installed on your system
52
+ to define the `:gisbase` option to point to the base directory of the GRASS
53
+ installation.
54
+
55
+ In *Windows*, if installed with
56
+ [OSGeo4W](http://trac.osgeo.org/osgeo4w/) it is typically of
57
+ the form `C:\OGeo4W\app\grass\grass-7.0.0` (the last directory will vary
58
+ depending on the GRASS version).
59
+
60
+ Under *Mac OS X*, if using [Homebrew](http://brew.sh/)
61
+ (with the [osgeo/osgeo4mac](https://github.com/OSGeo/homebrew-osgeo4mac) tap)
62
+ it should bd something like `/usr/local/Cellar/grass-70/7.0.0/grass-7.0.0`.
63
+
64
+ You can find the `:gisbase` directory by executing the
65
+ `grass` command of your system (which may be `grass70`, `grass64`, etc.)
66
+ with the `--config path` option:
67
+
68
+ grass --config path
36
69
 
70
+ You must also specify the `GISDBASE`, `LOCATION` and `MAPSET`, to work with,
71
+ just like when starting GRASS, through the `:gisdbase`, `:location` and
72
+ `:mapset` configuration options.
73
+ You can omit `:gisdbase` which will default to a directory named `grassdata` in the
74
+ user's home directory and `:mapset` which defaults to `PERMANENT`.
75
+
76
+
77
+ ### Running a GRASS Session
78
+
79
+ With the proper configuration in place, we can use it to
80
+ create a GRASS Session and execute GRASS command from it:
81
+
82
+ ```ruby
37
83
  GrassGis.session configuration do
38
- r.resamp.stats '-n', input: "map1@mapset1", output: "map2"
39
84
  g.list 'vect'
40
- puts output # output of last command
85
+ puts output # will print list of vector maps
86
+ end
87
+ ```
88
+
89
+ Inside a `GrassGis` session we can execute GRASS commands
90
+ just by using the command name as a Ruby method.
91
+
92
+ Command flags and options must be passed first as regular method arguments,
93
+ then named parameters must be passed as a Ruby Hash:
94
+
95
+ ```ruby
96
+ g.region '-p', rast: 'elevation'
97
+ d.rast 'elevation'
98
+ d.vect 'streams', col: 'blue'
99
+ ```
100
+
101
+ If you try to execute an invalid module name an `ENOENT`
102
+ error will be raised:
103
+
104
+ ```ruby
105
+ g.this.module.does.not.exist '???'
106
+ ```
107
+
108
+ If the command to be executed has no arguments you need
109
+ to invoke `.run` on it to execute it:
110
+
111
+ ```ruby
112
+ d.erase.run
113
+ g.list.run
114
+ ```
115
+
116
+ ## Helper methods
117
+
118
+ When writing a non-trivial program you'll probably
119
+ find you want to define methods to avoid innecesary repetition.
120
+
121
+ Let's see how you can call methdos from your session and be
122
+ able to execute GRASS commands from the method in the context of the session.
123
+
124
+ Inside a session, `self` refers to an object of class
125
+ `GrassGis::Context` which represents the current GRASS session.
126
+
127
+ You can invoke grass commands directly on this object, so, if you pass
128
+ this object aroung you can use it to execute GRASS commands:
129
+
130
+ ```ruby
131
+ def helper_method(grass)
132
+ # ...
133
+ end
134
+
135
+ GrassGis.session configuration do
136
+ helper_method self
41
137
  end
42
138
  ```
43
139
 
140
+ In the helper method you can use the grass object like this;
141
+
142
+ ```ruby
143
+ def helper_method(grass)
144
+ # change the current region resolution
145
+ grass.g.region res: 10
146
+ end
147
+ ```
148
+
149
+ To avoid having to prepend each command with `grapss.` you can
150
+ use the `session` method like this:
151
+
152
+ ```ruby
153
+ def helper_method(grass)
154
+ grass.session do
155
+ g.region res: 10
156
+ g.region '-p'
157
+ puts output
158
+ end
159
+ end
160
+ ```
161
+
162
+ ### Examples
163
+
164
+
165
+ #### 1. Map existence
166
+
167
+ Helper methods to check for the existence of maps.
168
+
169
+ Often we may want to know if a map exists. Next methods can be used to
170
+ check for it.
171
+
172
+ ```ruby
173
+ def map_exists?(grass, type, map)
174
+ grass.g.list type
175
+ maps = grass.output.split
176
+ maps.include?(map)
177
+ end
178
+
179
+ def raster_exists?(grass, map)
180
+ map_exists? grass, 'rast', map
181
+ end
182
+
183
+ def vector_exists?(grass, map)
184
+ map_exists? grass, 'vect', map
185
+ end
186
+ ```
187
+
188
+ We can use these methods like this:
189
+
190
+ ```ruby
191
+ GrassGis.session configuration do
192
+ unless raster_exists?(self, 'product')
193
+ r.mapcalc "product = factor1*factor2"
194
+ end
195
+ end
196
+ ```
197
+
198
+
199
+ ### 2. Information as Hashes
200
+
201
+ Following methods show how to obtain information about a raster map
202
+ and the current region as a Hash:
203
+
204
+ ```ruby
205
+ def raster_info(grass, map)
206
+ grass.r.info '-g', map
207
+ shell_to_hash grass
208
+ end
209
+
210
+ def region_info(grass)
211
+ grass.g.region '-m'
212
+ shell_to_hash grass
213
+ end
214
+
215
+ def shell_to_hash(grass)
216
+ Hash[grass.output.lines.map{|l| l.strip.split('=')}]
217
+ end
218
+
219
+ # Now, for example, we can easily obtain the resolution of a raster:
220
+
221
+ def raster_res(grass, map)
222
+ info = raster_info(grass, map)
223
+ info.values_at('ewres', 'nsres').map(&:to_i)
224
+ end
225
+
226
+ def region_res(grass)
227
+ info = region_info(grass)
228
+ info.values_at('ewres', 'nsres').map(&:to_i)
229
+ end
230
+ ```
231
+
232
+ ### 3. Average angle
233
+
234
+ Let's assume we have a raster map `aspect` which is
235
+ a direction angle (i.e. a cyclic value from 0 to 360).
236
+
237
+ Now imagine that we need to compute a coarser raster grid with
238
+ average values per cell. We can't just resample the angle
239
+ (we want the average of 359 and 1 be 0, not 180);
240
+ we would need an unitary vector or complex number to take averages.
241
+
242
+ The next method will perform the average correctly using
243
+ auxiliary raster maps for two cartesian components (that represent
244
+ the angle as a vector).
245
+
246
+ ```ruby
247
+ def resample_average_angle(grass, options = {})
248
+ input_raster = options[:input]
249
+ raise "Raster #{input_raster} not found" unless raster_exists?(grass, input_raster)
250
+ input_res = raster_res(grass, input_raster)
251
+
252
+ if options[:output_res]
253
+ output_res = options[:output_res]
254
+ unless output_res.is_a?(Array)
255
+ output_res = [output_res, output_res]
256
+ end
257
+ else
258
+ output_res = region_res(grass)
259
+ end
260
+
261
+ output_raster = options[:output]
262
+
263
+ grass.session do
264
+ unless raster_exists?(self, "#{input_raster}_sin")
265
+ g.region ewres: input_res[0], nsres: input_res[1]
266
+ r.mapcalc "#{input_raster}_sin = sin(#{input_raster})"
267
+ end
268
+ unless raster_exists?(self, "#{input_raster}_cos")
269
+ g.region ewres: input_res[0], nsres: input_res[1]
270
+ r.mapcalc "#{input_raster}_cos = cos(#{input_raster})"
271
+ end
272
+ g.region ewres: output_res[0], nsres: output_res[1]
273
+ r.resamp.stats input: "#{input_raster}_cos", output: "#{output_raster}_cos"
274
+ r.resamp.stats input: "#{input_raster}_sin", output: "#{output_raster}_sin"
275
+ r.mapcalc "#{output_raster} = atan(#{output_raster}_cos,#{output_raster}_sin)"
276
+ r.colors map: ouput_raster, raster: input_raster
277
+ g.remove '-f', type: 'raster', name: ["#{output_raster}_cos", "#{output_raster}_sin"]
278
+ g.remove -f type=raster name=aspect_sin@landscape,aspect_cos@landscape
279
+ end
280
+ end
281
+ ```
282
+
283
+ Now, to resample a (cyclic angular) map `aspect_hires` to a lower resolution 10:
284
+
285
+ ```ruby
286
+ GrassGis.session configuration do
287
+ resamp_average self,
288
+ input: 'aspect_hires',
289
+ output: 'aspect_lowres', output_res: 10
290
+ end
291
+ end
292
+ ```
293
+
294
+ ### Options
295
+
296
+ TODO: Explain options for error handling, echoing, logging, ...
297
+
298
+ ### Technicalities
299
+
300
+ #### Session scopes
301
+
302
+ In a session block, the Ruby `self` object is altered to
303
+ refer to a `GrassGis::Context` object. That means that in addition
304
+ to the enclosing `self`, any instance variables of the enclosing
305
+ scope are not directly available. This may cause some surprises
306
+ but is easy to overcome.
307
+
308
+ ```ruby
309
+ @value = 10
310
+ GrassGis.session configuration do
311
+ puts @value # nil!
312
+ end
313
+ ```
314
+
315
+ A possible workaround is to assign instance variables that we need
316
+ in the session to local variables:
317
+
318
+ ```ruby
319
+ @value = 10
320
+ value = @value
321
+ GrassGis.session configuration do
322
+ puts value # 10
323
+ end
324
+ ```
325
+
326
+ To avoid defining these variables you can pass a `:locals` Hash
327
+ in the configuration to define values that you need to access
328
+ in the session (but you won't be able to assign to them, because
329
+ they're not local variables!)
330
+
331
+ ```ruby
332
+ @value = 10
333
+
334
+ GrassGis.session configuration.merge(locals: { value: @value }) do
335
+ puts value # 10
336
+ value = 11 # don't do this: you're creating a local in the session
337
+ end
338
+ ```
339
+
340
+ A different approach is prevent the session block from using a special
341
+ `self` by defining a parameter to the block. This parameter will have
342
+ the value of a `GrassGis::Context` which you'll need to explicitly use
343
+ to execute any commands:
344
+
345
+ ```ruby
346
+ @value = 10
347
+ GrassGis.session configuration do |grass|
348
+ puts @value # 10
349
+ grass.g.region res: 10 # now you need to use the object to issue commans
350
+ end
351
+ ```
352
+
353
+ #### Invalid commands
354
+
355
+ Currently the generation of GRASS commands inside a session is
356
+ implemented in a versy simple way which allows to generate any command
357
+ name even if it is invalid or does not exist. This has the advantage
358
+ of supporting any version of GRASS, but doesn't allow for early
359
+ detection of invalid commands (e.g. due to typos) or invalid command
360
+ parameters.
361
+
362
+ ```ruby
363
+ GrassGis.session configuration do |grass|
364
+ g.regoin res: 10 # Oops (runtime error)
365
+ g.anything.goes.run # another runtime error
366
+ end
367
+ ```
368
+
369
+ If the command generated does not exist a runtime `ENOENT` exception will
370
+ occur.
371
+
372
+ If the command exists, then if parameters are not valid, the command
373
+ will execute but will return an error status. This will be handled
374
+ as explaned above.
375
+
44
376
  ## Roadmap
45
377
 
46
- * Write more documentation with examples.
378
+ * Methods to create new locations and mapsets.
379
+ * Change Module to define explicitly available GRASS commands instead of
380
+ accepting anything with `method_missing`. Declare commands with permitted
381
+ arguments and options, etc.
47
382
  * Add some session helpers:
48
383
  - Method to clean GRASS temporaries ($GISBASE/etc/clean_temp), or do
49
384
  it automatically when disposing the session.
@@ -68,28 +403,7 @@ Example of intended recipe syntax:
68
403
 
69
404
  ```ruby
70
405
  recipe :resamp_average do |options = {}|
71
- input_raster = options[:input]
72
- input_res = options[:input_res] # TODO: extract from input_rater info
73
- output_raster = options[:output]
74
- output_res = options[:output_res] # TODO: extract from output_raster info
75
- if options[:direction]
76
- unless raster_exists?("#{input_raster}_sin")
77
- g.region res: input_res
78
- r.mapcalc "#{input_raster}_sin = sin(#{input_raster})"
79
- end
80
- unless raster_exists?("#{input_raster}_cos")
81
- g.region res: input_res
82
- r.mapcalc "#{input_raster}_cos = cos(#{input_raster})"
83
- end
84
- g.region res: output_res
85
- r.resamp.stats input: "#{input_raster}_cos", output: "#{output_raster}_cos"
86
- r.resamp.stats input: "#{input_raster}_sin", output: "#{output_raster}_sin"
87
- r.mapcalc "#{output_raster} = atan(#{output_raster}_cos,#{output_raster}_sin)"
88
- g.remove rast: ["#{output_raster}_cos", "#{output_raster}_sin"]
89
- else
90
- g.region res: output_res
91
- r.resamp.stats input: input_raster, output: output_raster
92
- end
406
+ # ...
93
407
  end
94
408
 
95
409
  recipe :generate_working_dem do |...|
@@ -161,7 +161,11 @@ module GrassGis
161
161
  # end
162
162
  #
163
163
  def session(&blk)
164
- instance_eval(&blk)
164
+ if blk.arity == 1
165
+ blk.call self
166
+ else
167
+ instance_eval &blk
168
+ end
165
169
  end
166
170
 
167
171
  def dry?
@@ -179,7 +183,7 @@ module GrassGis
179
183
  log log_file, cmd.to_s(with_input: true)
180
184
  end
181
185
  unless dry?
182
- cmd.run error_output: :reparate
186
+ cmd.run error_output: :separate
183
187
  end
184
188
  if cmd.output
185
189
  puts cmd.output if @config[:echo] == :output
@@ -278,7 +282,9 @@ module GrassGis
278
282
  # :errors to define the behaviour when a GRASS command fails:
279
283
  # * :raise is the default and raises on errors
280
284
  # * :console shows standar error output of commands
281
- # * :quiet error output is retained but not shown
285
+ # * :quiet error output is retained but not shown; no exceptions are
286
+ # raise except when the command cannot be executed (e.g. when
287
+ # the command name is ill-formed)
282
288
  #
283
289
  # If :errors is anything other than :raise, it is up to the user
284
290
  # to check each command for errors. With the :console option
@@ -297,6 +303,12 @@ module GrassGis
297
303
  # * :output show the output of commands too
298
304
  # * false don't echo anything
299
305
  #
306
+ # Testing/debugging options:
307
+ #
308
+ # * :dry prevents actual execution of any command
309
+ # * errors: :silent omits raising exceptions (as :quiet) even when
310
+ # a command cannot be executed (usually because of an invalid command name)
311
+ #
300
312
  def self.session(config, &blk)
301
313
  context = Context.new(config)
302
314
  context.allocate
@@ -326,10 +338,13 @@ module GrassGis
326
338
 
327
339
  def self.error(command, error_mode = :raise)
328
340
  if command
329
- if error_mode == :raise
330
- if command.error
331
- raise command.error
332
- elsif (command.status_value && command.status_value != 0)
341
+ if command.error # :silent mode for testing/debugging?
342
+ # Errors that prevent command execution
343
+ # (usually ENOENT because the command does not exist)
344
+ # are always raised
345
+ raise command.error unless error_mode == :silent
346
+ elsif error_mode == :raise
347
+ if (command.status_value && command.status_value != 0)
333
348
  raise Error.new, error_info(command)
334
349
  end
335
350
  end
@@ -1,3 +1,3 @@
1
1
  module GrassGis
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grassgis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Goizueta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-23 00:00:00.000000000 Z
11
+ date: 2015-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sys_cmd
@@ -105,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
105
  version: '0'
106
106
  requirements: []
107
107
  rubyforge_project:
108
- rubygems_version: 2.4.6
108
+ rubygems_version: 2.4.8
109
109
  signing_key:
110
110
  specification_version: 4
111
111
  summary: Support for scripting GRASS GIS in Ruby