grassgis 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,577 +1,578 @@
1
- # GrassGis
2
-
3
- [![Gem Version](https://badge.fury.io/rb/grassgis.svg)](http://badge.fury.io/rb/grassgis)
4
- [![Build Status](https://travis-ci.org/jgoizueta/grassgis.svg)](https://travis-ci.org/jgoizueta/grassgis)
5
-
6
- Support for scripting GRASS with Ruby.
7
-
8
- <!-- START doctoc generated TOC please keep comment here to allow auto update -->
9
- <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
10
- **Table of Contents**
11
-
12
- - [Installation](#installation)
13
- - [Usage](#usage)
14
- - [Configuration](#configuration)
15
- - [Running a GRASS Session](#running-a-grass-session)
16
- - [Creating new locations and mapsets](#creating-new-locations-and-mapsets)
17
- - [History](#history)
18
- - [Options](#options)
19
- - [Echo](#echo)
20
- - [Errors](#errors)
21
- - [Logging](#logging)
22
- - [Technicalities](#technicalities)
23
- - [Session scopes](#session-scopes)
24
- - [Invalid commands](#invalid-commands)
25
- - [Helper methods](#helper-methods)
26
- - [Examples](#examples)
27
- - [1. Map existence](#1-map-existence)
28
- - [2. Information as Hashes](#2-information-as-hashes)
29
- - [3. Average angle](#3-average-angle)
30
- - [Roadmap](#roadmap)
31
- - [GRASS cooking DSL](#grass-cooking-dsl)
32
- - [Contributing](#contributing)
33
-
34
- <!-- END doctoc generated TOC please keep comment here to allow auto update -->
35
-
36
- ## Installation
37
-
38
- Add this line to your application's Gemfile:
39
-
40
- gem 'grassgis'
41
-
42
- And then execute:
43
-
44
- $ bundle
45
-
46
- Or install it yourself as:
47
-
48
- $ gem install grassgis
49
-
50
- ## Usage
51
-
52
- This library can prepare environments to execute GRASS commands
53
- from Ruby scripts.
54
-
55
- First we require the library:
56
-
57
- ```ruby
58
- require 'grassgis'
59
- ```
60
-
61
- ### Configuration
62
-
63
- A GRASS session operates on a given location and mapset.
64
-
65
- Before starting a GRASS session we need some configuration
66
- parameters to specify the location of the GRASS Installation
67
- to be used and the location/mapset. We do this by using
68
- a Ruby Hash containing configuration parameters:
69
-
70
- ```ruby
71
- configuration = {
72
- gisbase: '/usr/local/grass-7.0.0',
73
- gisdbase: File.join(ENV['HOME'], 'grassdata'),
74
- location: 'nc_spm',
75
- mapset: 'user1'
76
- }
77
- ```
78
-
79
- So, you first need to know where is GRASS installed on your system
80
- to define the `:gisbase` option to point to the base directory of the GRASS
81
- installation.
82
-
83
- For *Ubuntu* it will typically be `/usr/lib/grass70` for version 7 of GRASS.
84
- In *Windows*, if installed with
85
- [OSGeo4W](http://trac.osgeo.org/osgeo4w/) it is typically of
86
- the form `C:\OGeo4W\app\grass\grass-7.0.0` (the last directory will vary
87
- depending on the GRASS version).
88
-
89
- Under *Mac OS X*, if using [Homebrew](http://brew.sh/)
90
- (with the [osgeo/osgeo4mac](https://github.com/OSGeo/homebrew-osgeo4mac) tap)
91
- it should bd something like `/usr/local/Cellar/grass-70/7.0.0/grass-7.0.0`.
92
-
93
- You can find the `:gisbase` directory by executing the
94
- `grass` command of your system (which may be `grass70`, `grass64`, etc.)
95
- with the `--config path` option:
96
-
97
- grass --config path
98
-
99
- You must also specify the `GISDBASE`, `LOCATION` and `MAPSET`, to work with,
100
- just like when starting GRASS, through the `:gisdbase`, `:location` and
101
- `:mapset` configuration options.
102
- You can omit `:gisdbase` which will default to a directory named `grassdata` in the
103
- user's home directory and `:mapset` which defaults to `PERMANENT`.
104
-
105
-
106
- ### Running a GRASS Session
107
-
108
- With the proper configuration in place, we can use it to
109
- create a GRASS Session and execute GRASS command from it:
110
-
111
- ```ruby
112
- GrassGis.session configuration do
113
- g.list 'vect'
114
- puts output # will print list of vector maps
115
- end
116
- ```
117
-
118
- Inside a `GrassGis` session we can execute GRASS commands
119
- just by using the command name as a Ruby method.
120
-
121
- Command flags and options must be passed first as regular method arguments,
122
- then named parameters must be passed as a Ruby Hash:
123
-
124
- ```ruby
125
- g.region '-p', rast: 'elevation'
126
- d.rast 'elevation'
127
- d.vect 'streams', col: 'blue'
128
- ```
129
-
130
- If you try to execute an invalid module name an `ENOENT`
131
- error will be raised:
132
-
133
- ```ruby
134
- g.this.module.does.not.exist '???'
135
- ```
136
-
137
- If the command to be executed has no arguments you need
138
- to invoke `.run` on it to execute it:
139
-
140
- ```ruby
141
- d.erase.run
142
- g.list.run
143
- ```
144
-
145
- ### Creating new locations and mapsets
146
-
147
- To create a new location and/or mapset, open a session to it
148
- and use a `:create` parameter like this:
149
-
150
- ```ruby
151
- options = configuration.merge(
152
- location: 'new_location',
153
- mapset: 'new_mapset',
154
- create: {
155
- epsg: 4326 # coordinate system for the new location
156
- limits: [-5, 30, 5, 50], # optional E, S, W, N limits
157
- res: 2 # optional resolution
158
- }
159
- )
160
- GrassGis.session options do
161
- g.region '-p'
162
- puts output
163
- end
164
- ```
165
-
166
- Use `nil` or `PERMANENT` for the mapset to avoid creating a new mapset.
167
-
168
- Existing locations or mapsets are not changed.
169
-
170
- ### History
171
-
172
- The return value of a GRASS command invocation inside a session is
173
- a `SysCmd::Command`
174
- (see the [sys_cmd gem](https://github.com/jgoizueta/sys_cmd)).
175
-
176
- ```ruby
177
- GrassGis.session configuration do+
178
- cmd = g.region '-p'
179
- puts cmd.output # command output is kept in the Command object
180
- puts cmd.status_value # 0 for success
181
- end
182
- ```
183
-
184
- You don't need to assign commands to variables as in the example
185
- to access them, because they're all kept in an array accesible
186
- through the `history` method of the session:
187
-
188
- ```ruby
189
- GrassGis.session configuration do+
190
- r.info 'slope'
191
- g.region '-p'
192
- puts history.size # 2
193
- puts history[-1].output # output of g.region
194
- puts history[-2].output # output of r.info
195
- end
196
- ```
197
-
198
- The last executed command (`history[-1]`) is also accessible through
199
- the `last` method and its output through `output`:
200
-
201
- ```ruby
202
- GrassGis.session configuration do+
203
- r.info 'slope'
204
- g.region '-p'
205
- puts output # output of g.region (same as last.output)
206
- puts last.status_value # result status of g.region
207
- end
208
- ```
209
-
210
- ### Options
211
-
212
- By default the commands executed in a session are echoed to standard output
213
- (just the command, not its output) and error return status causes
214
- an exception to be raised.
215
-
216
- This behaviour can be changed with some options:
217
-
218
- #### Echo
219
-
220
- Pass `false` as the `:echo` option it you don't want to output
221
- command names and `:output` if you want to output both
222
- the command name and its output.
223
-
224
- ```ruby
225
- GrassGis.session configuration.merge(echo: false) do
226
- # Command names not echoed now ...
227
- end
228
-
229
- GrassGis.session configuration.merge(echo: :output) do
230
- # Command names and its output echoed ...
231
- end
232
- ```
233
-
234
- #### Errors
235
-
236
- To avoid raising exceptions when commands return an error status you can pass
237
- `:quiet` to the `:errors` option. In that case the `error?` method of the
238
- session can be used to check if the previous messatge returned an error status;
239
- `error_info` to get its error message and the status of the command
240
- of the command can be obtained through the `last` command method.
241
-
242
- ```ruby
243
- GrassGis.session configuration.merge(errors: :quiet) do
244
- r.surf.rst 'randpts', elev: 'rstdef', zcol: 'value'
245
- if error?
246
- puts "Last command didn't go well..."
247
- puts "It returned the code: #{last.status_value}"
248
- puts "Here's what it said about the problem:"
249
- puts error_info
250
- end
251
- end
252
- ```
253
-
254
- With the `:quiet` option errors during command execution are not raised,
255
- but if a problem prevents the command from being executed (e.g. the
256
- module does not exist) an exception is still generated. This exception
257
- can be avoided too, with the `:silent` option, intended for tests and
258
- debugging.
259
-
260
- Passing the `:console` value to the `:errors` option is like `:quiet`,
261
- with the additional effect of relaying the command standard error output
262
- to the error output of the script.
263
-
264
- #### Logging
265
-
266
- With the `:log` option you can specify the name of a file
267
- where to record the commands executed and its output.
268
-
269
- ### Technicalities
270
-
271
- #### Session scopes
272
-
273
- In a session block, the Ruby `self` object is altered to
274
- refer to a `GrassGis::Context` object. That means that in addition
275
- to the enclosing `self`, any instance variables of the enclosing
276
- scope are not directly available. This may cause some surprises
277
- but is easy to overcome.
278
-
279
- ```ruby
280
- @value = 10
281
- GrassGis.session configuration do
282
- puts @value # nil!
283
- end
284
- ```
285
-
286
- A possible workaround is to assign instance variables that we need
287
- in the session to local variables:
288
-
289
- ```ruby
290
- @value = 10
291
- value = @value
292
- GrassGis.session configuration do
293
- puts value # 10
294
- end
295
- ```
296
-
297
- To avoid defining these variables you can pass a `:locals` Hash
298
- in the configuration to define values that you need to access
299
- in the session (but you won't be able to assign to them, because
300
- they're not local variables!)
301
-
302
- ```ruby
303
- @value = 10
304
-
305
- GrassGis.session configuration.merge(locals: { value: @value }) do
306
- puts value # 10
307
- value = 11 # don't do this: you're creating a local in the session
308
- end
309
- ```
310
-
311
- A different approach is prevent the session block from using a special
312
- `self` by defining a parameter to the block. This parameter will have
313
- the value of a `GrassGis::Context` which you'll need to explicitly use
314
- to execute any commands:
315
-
316
- ```ruby
317
- @value = 10
318
- GrassGis.session configuration do |grass|
319
- puts @value # 10
320
- grass.g.region res: 10 # now you need to use the object to issue commans
321
- end
322
- ```
323
-
324
- #### Invalid commands
325
-
326
- Currently the generation of GRASS commands inside a session is
327
- implemented in a versy simple way which allows to generate any command
328
- name even if it is invalid or does not exist. This has the advantage
329
- of supporting any version of GRASS, but doesn't allow for early
330
- detection of invalid commands (e.g. due to typos) or invalid command
331
- parameters.
332
-
333
- ```ruby
334
- GrassGis.session configuration do |grass|
335
- g.regoin res: 10 # Oops (runtime error)
336
- g.anything.goes.run # another runtime error
337
- end
338
- ```
339
-
340
- If the command generated does not exist a runtime `ENOENT` exception will
341
- occur.
342
-
343
- If the command exists, then if parameters are not valid, the command
344
- will execute but will return an error status. This will be handled
345
- as explaned above.
346
-
347
- ## Helper methods
348
-
349
- When writing a non-trivial program you'll probably
350
- find you want to define methods to avoid unnecessary repetition.
351
-
352
- Let's see how you can call methdos from your session and be
353
- able to execute GRASS commands from the method in the context of the session.
354
-
355
- Inside a session, `self` refers to an object of class
356
- `GrassGis::Context` which represents the current GRASS session.
357
-
358
- You can invoke grass commands directly on this object, so, if you pass
359
- this object around you can use it to execute GRASS commands:
360
-
361
- ```ruby
362
- def helper_method(grass)
363
- # ...
364
- end
365
-
366
- GrassGis.session configuration do
367
- helper_method self
368
- end
369
- ```
370
-
371
- In the helper method you can use the grass object like this;
372
-
373
- ```ruby
374
- def helper_method(grass)
375
- # change the current region resolution
376
- grass.g.region res: 10
377
- end
378
- ```
379
-
380
- To avoid having to prepend each command with `grapss.` you can
381
- use the `session` method like this:
382
-
383
- ```ruby
384
- def helper_method(grass)
385
- grass.session do
386
- g.region res: 10
387
- g.region '-p'
388
- puts output
389
- end
390
- end
391
- ```
392
-
393
- ### Examples
394
-
395
-
396
- #### 1. Map existence
397
-
398
- Helper methods to check for the existence of maps.
399
-
400
- Often we may want to know if a map exists. Next methods can be used to
401
- check for it.
402
-
403
- ```ruby
404
- def map_exists?(grass, type, map)
405
- grass.g.list type
406
- maps = grass.output.split
407
- maps.include?(map)
408
- end
409
-
410
- def raster_exists?(grass, map)
411
- map_exists? grass, 'rast', map
412
- end
413
-
414
- def vector_exists?(grass, map)
415
- map_exists? grass, 'vect', map
416
- end
417
- ```
418
-
419
- We can use these methods like this:
420
-
421
- ```ruby
422
- GrassGis.session configuration do
423
- unless raster_exists?(self, 'product')
424
- r.mapcalc "product = factor1*factor2"
425
- end
426
- end
427
- ```
428
-
429
- #### 2. Information as Hashes
430
-
431
- Following methods show how to obtain information about a raster map
432
- and the current region as a Hash:
433
-
434
- ```ruby
435
- def raster_info(grass, map)
436
- grass.r.info '-g', map
437
- shell_to_hash grass
438
- end
439
-
440
- def region_info(grass)
441
- grass.g.region '-m'
442
- shell_to_hash grass
443
- end
444
-
445
- def shell_to_hash(grass)
446
- Hash[grass.output.lines.map{|l| l.strip.split('=')}]
447
- end
448
-
449
- # Now, for example, we can easily obtain the resolution of a raster:
450
-
451
- def raster_res(grass, map)
452
- info = raster_info(grass, map)
453
- info.values_at('ewres', 'nsres').map(&:to_i)
454
- end
455
-
456
- def region_res(grass)
457
- info = region_info(grass)
458
- info.values_at('ewres', 'nsres').map(&:to_i)
459
- end
460
- ```
461
-
462
- #### 3. Average angle
463
-
464
- Let's assume we have a raster map `aspect` which is
465
- a direction angle (i.e. a cyclic value from 0 to 360).
466
-
467
- Now imagine that we need to compute a coarser raster grid with
468
- average values per cell. We can't just resample the angle
469
- (we want the average of 359 and 1 be 0, not 180);
470
- we would need an unitary vector or complex number to take averages.
471
-
472
- The next method will perform the average correctly using
473
- auxiliary raster maps for two cartesian components (that represent
474
- the angle as a vector).
475
-
476
- ```ruby
477
- def resample_average_angle(grass, options = {})
478
- input_raster = options[:input]
479
- raise "Raster #{input_raster} not found" unless raster_exists?(grass, input_raster)
480
- input_res = raster_res(grass, input_raster)
481
-
482
- if options[:output_res]
483
- output_res = options[:output_res]
484
- unless output_res.is_a?(Array)
485
- output_res = [output_res, output_res]
486
- end
487
- else
488
- output_res = region_res(grass)
489
- end
490
-
491
- output_raster = options[:output]
492
-
493
- grass.session do
494
- unless raster_exists?(self, "#{input_raster}_sin")
495
- g.region ewres: input_res[0], nsres: input_res[1]
496
- r.mapcalc "#{input_raster}_sin = sin(#{input_raster})"
497
- end
498
- unless raster_exists?(self, "#{input_raster}_cos")
499
- g.region ewres: input_res[0], nsres: input_res[1]
500
- r.mapcalc "#{input_raster}_cos = cos(#{input_raster})"
501
- end
502
- g.region ewres: output_res[0], nsres: output_res[1]
503
- r.resamp.stats input: "#{input_raster}_cos", output: "#{output_raster}_cos"
504
- r.resamp.stats input: "#{input_raster}_sin", output: "#{output_raster}_sin"
505
- r.mapcalc "#{output_raster} = atan(#{output_raster}_cos,#{output_raster}_sin)"
506
- r.colors map: ouput_raster, raster: input_raster
507
- g.remove '-f', type: 'raster', name: ["#{output_raster}_cos", "#{output_raster}_sin"]
508
- g.remove -f type=raster name=aspect_sin@landscape,aspect_cos@landscape
509
- end
510
- end
511
- ```
512
-
513
- Now, to resample a (cyclic angular) map `aspect_hires` to a lower resolution 10:
514
-
515
- ```ruby
516
- GrassGis.session configuration do
517
- resamp_average self,
518
- input: 'aspect_hires',
519
- output: 'aspect_lowres', output_res: 10
520
- end
521
- end
522
- ```
523
-
524
- ## Roadmap
525
-
526
- * Change Module to define explicitly available GRASS commands instead of
527
- accepting anything with `method_missing`. Declare commands with permitted
528
- arguments and options, etc.
529
- * Add some session helpers:
530
- - Method to clean GRASS temporaries ($GISBASE/etc/clean_temp), or do
531
- it automatically when disposing the session.
532
- - Methods to check if maps exist
533
- - Methods that return information as objects (arrays, hashes), e.g.
534
- values returned by r.what, the current region, etc.
535
- - Methods that execute operations in a GRASS-version independent
536
- manner (higher level, version independent interface to modules).
537
-
538
- ### GRASS cooking DSL
539
-
540
- Create a DSL to define GRASS processing recipes.
541
-
542
- It should handle errors gracefully during recipe execution,
543
- take care of logging, parsing command output, temporary files, etc.
544
-
545
- A recipe could have requirements such as which other recipes it depends
546
- upon (so must be executed first), which map layers must exist, etc.
547
-
548
- It could also define what map layers or other files are generated by the
549
- recipe.
550
-
551
- Example of intended recipe syntax:
552
-
553
- ```ruby
554
- recipe :resamp_average do |options = {}|
555
- # ...
556
- end
557
-
558
- recipe :generate_working_dem do |...|
559
- define :working_dem, 'dem'
560
- define :working_slope, 'slope'
561
- define :working_aspect, 'aspect'
562
- g.region res: working_res
563
- apply :resamp_average, input: base_dem, output: working_dem, input_res: base_dem_res, output_res: working_res
564
- apply :resamp_average, input: base_slope, output: working_slope, input_res: base_dem_res, output_res: working_res
565
- apply :resamp_average, input: base_aspect, output: working_aspect, input_res: base_dem_res, output_res: working_res, direction: true
566
- describe working_slope, "Pendiente en grados a #{working_res} #{region_units}"
567
- # ...
568
- end
569
- ```
570
-
571
- ## Contributing
572
-
573
- 1. Fork it ( https://github.com/[my-github-username]/grassgis/fork )
574
- 2. Create your feature branch (`git checkout -b my-new-feature`)
575
- 3. Commit your changes (`git commit -am 'Add some feature'`)
576
- 4. Push to the branch (`git push origin my-new-feature`)
577
- 5. Create a new Pull Request
1
+ # GrassGis
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/grassgis.svg)](http://badge.fury.io/rb/grassgis)
4
+ [![Build Status](https://travis-ci.org/jgoizueta/grassgis.svg)](https://travis-ci.org/jgoizueta/grassgis)
5
+
6
+ Support for scripting GRASS with Ruby.
7
+
8
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
9
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
10
+ **Table of Contents**
11
+
12
+ - [Installation](#installation)
13
+ - [Usage](#usage)
14
+ - [Configuration](#configuration)
15
+ - [Running a GRASS Session](#running-a-grass-session)
16
+ - [Creating new locations and mapsets](#creating-new-locations-and-mapsets)
17
+ - [History](#history)
18
+ - [Options](#options)
19
+ - [Echo](#echo)
20
+ - [Errors](#errors)
21
+ - [Logging](#logging)
22
+ - [Technicalities](#technicalities)
23
+ - [Session scopes](#session-scopes)
24
+ - [Invalid commands](#invalid-commands)
25
+ - [Helper methods](#helper-methods)
26
+ - [Examples](#examples)
27
+ - [1. Map existence](#1-map-existence)
28
+ - [2. Information as Hashes](#2-information-as-hashes)
29
+ - [3. Average angle](#3-average-angle)
30
+ - [Roadmap](#roadmap)
31
+ - [GRASS cooking DSL](#grass-cooking-dsl)
32
+ - [Contributing](#contributing)
33
+
34
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
35
+
36
+ ## Installation
37
+
38
+ Add this line to your application's Gemfile:
39
+
40
+ gem 'grassgis'
41
+
42
+ And then execute:
43
+
44
+ $ bundle
45
+
46
+ Or install it yourself as:
47
+
48
+ $ gem install grassgis
49
+
50
+ ## Usage
51
+
52
+ This library can prepare environments to execute GRASS commands
53
+ from Ruby scripts.
54
+
55
+ First we require the library:
56
+
57
+ ```ruby
58
+ require 'grassgis'
59
+ ```
60
+
61
+ ### Configuration
62
+
63
+ A GRASS session operates on a given location and mapset.
64
+
65
+ Before starting a GRASS session we need some configuration
66
+ parameters to specify the location of the GRASS Installation
67
+ to be used and the location/mapset. We do this by using
68
+ a Ruby Hash containing configuration parameters:
69
+
70
+ ```ruby
71
+ configuration = {
72
+ gisbase: '/usr/local/grass-7.0.0',
73
+ gisdbase: File.join(ENV['HOME'], 'grassdata'),
74
+ location: 'nc_spm',
75
+ mapset: 'user1'
76
+ }
77
+ ```
78
+
79
+ So, you first need to know where is GRASS installed on your system
80
+ to define the `:gisbase` option to point to the base directory of the GRASS
81
+ installation.
82
+
83
+ For *Ubuntu* it will typically be `/usr/lib/grass70` for version 7 of GRASS.
84
+
85
+ In *Windows*, if installed with
86
+ [OSGeo4W](http://trac.osgeo.org/osgeo4w/) it is typically of
87
+ the form `C:\OGeo4W\app\grass\grass-7.0.0` (the last directory will vary
88
+ depending on the GRASS version).
89
+
90
+ Under *Mac OS X*, if using [Homebrew](http://brew.sh/)
91
+ (with the [osgeo/osgeo4mac](https://github.com/OSGeo/homebrew-osgeo4mac) tap)
92
+ it should bd something like `/usr/local/Cellar/grass-70/7.0.0/grass-7.0.0`.
93
+
94
+ You can find the `:gisbase` directory by executing the
95
+ `grass` command of your system (which may be `grass70`, `grass64`, etc.)
96
+ with the `--config path` option:
97
+
98
+ grass --config path
99
+
100
+ You must also specify the `GISDBASE`, `LOCATION` and `MAPSET`, to work with,
101
+ just like when starting GRASS, through the `:gisdbase`, `:location` and
102
+ `:mapset` configuration options.
103
+ You can omit `:gisdbase` which will default to a directory named `grassdata` in the
104
+ user's home directory and `:mapset` which defaults to `PERMANENT`.
105
+
106
+
107
+ ### Running a GRASS Session
108
+
109
+ With the proper configuration in place, we can use it to
110
+ create a GRASS Session and execute GRASS command from it:
111
+
112
+ ```ruby
113
+ GrassGis.session configuration do
114
+ g.list 'vect'
115
+ puts output # will print list of vector maps
116
+ end
117
+ ```
118
+
119
+ Inside a `GrassGis` session we can execute GRASS commands
120
+ just by using the command name as a Ruby method.
121
+
122
+ Command flags and options must be passed first as regular method arguments,
123
+ then named parameters must be passed as a Ruby Hash:
124
+
125
+ ```ruby
126
+ g.region '-p', rast: 'elevation'
127
+ d.rast 'elevation'
128
+ d.vect 'streams', col: 'blue'
129
+ ```
130
+
131
+ If you try to execute an invalid module name an `ENOENT`
132
+ error will be raised:
133
+
134
+ ```ruby
135
+ g.this.module.does.not.exist '???'
136
+ ```
137
+
138
+ If the command to be executed has no arguments you need
139
+ to invoke `.run` on it to execute it:
140
+
141
+ ```ruby
142
+ d.erase.run
143
+ g.list.run
144
+ ```
145
+
146
+ ### Creating new locations and mapsets
147
+
148
+ To create a new location and/or mapset, open a session to it
149
+ and use a `:create` parameter like this:
150
+
151
+ ```ruby
152
+ options = configuration.merge(
153
+ location: 'new_location',
154
+ mapset: 'new_mapset',
155
+ create: {
156
+ epsg: 4326 # coordinate system for the new location
157
+ limits: [-5, 30, 5, 50], # optional E, S, W, N limits
158
+ res: 2 # optional resolution
159
+ }
160
+ )
161
+ GrassGis.session options do
162
+ g.region '-p'
163
+ puts output
164
+ end
165
+ ```
166
+
167
+ Use `nil` or `PERMANENT` for the mapset to avoid creating a new mapset.
168
+
169
+ Existing locations or mapsets are not changed.
170
+
171
+ ### History
172
+
173
+ The return value of a GRASS command invocation inside a session is
174
+ a `SysCmd::Command`
175
+ (see the [sys_cmd gem](https://github.com/jgoizueta/sys_cmd)).
176
+
177
+ ```ruby
178
+ GrassGis.session configuration do+
179
+ cmd = g.region '-p'
180
+ puts cmd.output # command output is kept in the Command object
181
+ puts cmd.status_value # 0 for success
182
+ end
183
+ ```
184
+
185
+ You don't need to assign commands to variables as in the example
186
+ to access them, because they're all kept in an array accesible
187
+ through the `history` method of the session:
188
+
189
+ ```ruby
190
+ GrassGis.session configuration do+
191
+ r.info 'slope'
192
+ g.region '-p'
193
+ puts history.size # 2
194
+ puts history[-1].output # output of g.region
195
+ puts history[-2].output # output of r.info
196
+ end
197
+ ```
198
+
199
+ The last executed command (`history[-1]`) is also accessible through
200
+ the `last` method and its output through `output`:
201
+
202
+ ```ruby
203
+ GrassGis.session configuration do+
204
+ r.info 'slope'
205
+ g.region '-p'
206
+ puts output # output of g.region (same as last.output)
207
+ puts last.status_value # result status of g.region
208
+ end
209
+ ```
210
+
211
+ ### Options
212
+
213
+ By default the commands executed in a session are echoed to standard output
214
+ (just the command, not its output) and error return status causes
215
+ an exception to be raised.
216
+
217
+ This behaviour can be changed with some options:
218
+
219
+ #### Echo
220
+
221
+ Pass `false` as the `:echo` option it you don't want to output
222
+ command names and `:output` if you want to output both
223
+ the command name and its output.
224
+
225
+ ```ruby
226
+ GrassGis.session configuration.merge(echo: false) do
227
+ # Command names not echoed now ...
228
+ end
229
+
230
+ GrassGis.session configuration.merge(echo: :output) do
231
+ # Command names and its output echoed ...
232
+ end
233
+ ```
234
+
235
+ #### Errors
236
+
237
+ To avoid raising exceptions when commands return an error status you can pass
238
+ `:quiet` to the `:errors` option. In that case the `error?` method of the
239
+ session can be used to check if the previous messatge returned an error status;
240
+ `error_info` to get its error message and the status of the command
241
+ of the command can be obtained through the `last` command method.
242
+
243
+ ```ruby
244
+ GrassGis.session configuration.merge(errors: :quiet) do
245
+ r.surf.rst 'randpts', elev: 'rstdef', zcol: 'value'
246
+ if error?
247
+ puts "Last command didn't go well..."
248
+ puts "It returned the code: #{last.status_value}"
249
+ puts "Here's what it said about the problem:"
250
+ puts error_info
251
+ end
252
+ end
253
+ ```
254
+
255
+ With the `:quiet` option errors during command execution are not raised,
256
+ but if a problem prevents the command from being executed (e.g. the
257
+ module does not exist) an exception is still generated. This exception
258
+ can be avoided too, with the `:silent` option, intended for tests and
259
+ debugging.
260
+
261
+ Passing the `:console` value to the `:errors` option is like `:quiet`,
262
+ with the additional effect of relaying the command standard error output
263
+ to the error output of the script.
264
+
265
+ #### Logging
266
+
267
+ With the `:log` option you can specify the name of a file
268
+ where to record the commands executed and its output.
269
+
270
+ ### Technicalities
271
+
272
+ #### Session scopes
273
+
274
+ In a session block, the Ruby `self` object is altered to
275
+ refer to a `GrassGis::Context` object. That means that in addition
276
+ to the enclosing `self`, any instance variables of the enclosing
277
+ scope are not directly available. This may cause some surprises
278
+ but is easy to overcome.
279
+
280
+ ```ruby
281
+ @value = 10
282
+ GrassGis.session configuration do
283
+ puts @value # nil!
284
+ end
285
+ ```
286
+
287
+ A possible workaround is to assign instance variables that we need
288
+ in the session to local variables:
289
+
290
+ ```ruby
291
+ @value = 10
292
+ value = @value
293
+ GrassGis.session configuration do
294
+ puts value # 10
295
+ end
296
+ ```
297
+
298
+ To avoid defining these variables you can pass a `:locals` Hash
299
+ in the configuration to define values that you need to access
300
+ in the session (but you won't be able to assign to them, because
301
+ they're not local variables!)
302
+
303
+ ```ruby
304
+ @value = 10
305
+
306
+ GrassGis.session configuration.merge(locals: { value: @value }) do
307
+ puts value # 10
308
+ value = 11 # don't do this: you're creating a local in the session
309
+ end
310
+ ```
311
+
312
+ A different approach is prevent the session block from using a special
313
+ `self` by defining a parameter to the block. This parameter will have
314
+ the value of a `GrassGis::Context` which you'll need to explicitly use
315
+ to execute any commands:
316
+
317
+ ```ruby
318
+ @value = 10
319
+ GrassGis.session configuration do |grass|
320
+ puts @value # 10
321
+ grass.g.region res: 10 # now you need to use the object to issue commans
322
+ end
323
+ ```
324
+
325
+ #### Invalid commands
326
+
327
+ Currently the generation of GRASS commands inside a session is
328
+ implemented in a versy simple way which allows to generate any command
329
+ name even if it is invalid or does not exist. This has the advantage
330
+ of supporting any version of GRASS, but doesn't allow for early
331
+ detection of invalid commands (e.g. due to typos) or invalid command
332
+ parameters.
333
+
334
+ ```ruby
335
+ GrassGis.session configuration do |grass|
336
+ g.regoin res: 10 # Oops (runtime error)
337
+ g.anything.goes.run # another runtime error
338
+ end
339
+ ```
340
+
341
+ If the command generated does not exist a runtime `ENOENT` exception will
342
+ occur.
343
+
344
+ If the command exists, then if parameters are not valid, the command
345
+ will execute but will return an error status. This will be handled
346
+ as explaned above.
347
+
348
+ ## Helper methods
349
+
350
+ When writing a non-trivial program you'll probably
351
+ find you want to define methods to avoid unnecessary repetition.
352
+
353
+ Let's see how you can call methdos from your session and be
354
+ able to execute GRASS commands from the method in the context of the session.
355
+
356
+ Inside a session, `self` refers to an object of class
357
+ `GrassGis::Context` which represents the current GRASS session.
358
+
359
+ You can invoke grass commands directly on this object, so, if you pass
360
+ this object around you can use it to execute GRASS commands:
361
+
362
+ ```ruby
363
+ def helper_method(grass)
364
+ # ...
365
+ end
366
+
367
+ GrassGis.session configuration do
368
+ helper_method self
369
+ end
370
+ ```
371
+
372
+ In the helper method you can use the grass object like this;
373
+
374
+ ```ruby
375
+ def helper_method(grass)
376
+ # change the current region resolution
377
+ grass.g.region res: 10
378
+ end
379
+ ```
380
+
381
+ To avoid having to prepend each command with `grapss.` you can
382
+ use the `session` method like this:
383
+
384
+ ```ruby
385
+ def helper_method(grass)
386
+ grass.session do
387
+ g.region res: 10
388
+ g.region '-p'
389
+ puts output
390
+ end
391
+ end
392
+ ```
393
+
394
+ ### Examples
395
+
396
+
397
+ #### 1. Map existence
398
+
399
+ Helper methods to check for the existence of maps.
400
+
401
+ Often we may want to know if a map exists. Next methods can be used to
402
+ check for it.
403
+
404
+ ```ruby
405
+ def map_exists?(grass, type, map)
406
+ grass.g.list type
407
+ maps = grass.output.split
408
+ maps.include?(map)
409
+ end
410
+
411
+ def raster_exists?(grass, map)
412
+ map_exists? grass, 'rast', map
413
+ end
414
+
415
+ def vector_exists?(grass, map)
416
+ map_exists? grass, 'vect', map
417
+ end
418
+ ```
419
+
420
+ We can use these methods like this:
421
+
422
+ ```ruby
423
+ GrassGis.session configuration do
424
+ unless raster_exists?(self, 'product')
425
+ r.mapcalc "product = factor1*factor2"
426
+ end
427
+ end
428
+ ```
429
+
430
+ #### 2. Information as Hashes
431
+
432
+ Following methods show how to obtain information about a raster map
433
+ and the current region as a Hash:
434
+
435
+ ```ruby
436
+ def raster_info(grass, map)
437
+ grass.r.info '-g', map
438
+ shell_to_hash grass
439
+ end
440
+
441
+ def region_info(grass)
442
+ grass.g.region '-m'
443
+ shell_to_hash grass
444
+ end
445
+
446
+ def shell_to_hash(grass)
447
+ Hash[grass.output.lines.map{|l| l.strip.split('=')}]
448
+ end
449
+
450
+ # Now, for example, we can easily obtain the resolution of a raster:
451
+
452
+ def raster_res(grass, map)
453
+ info = raster_info(grass, map)
454
+ info.values_at('ewres', 'nsres').map(&:to_i)
455
+ end
456
+
457
+ def region_res(grass)
458
+ info = region_info(grass)
459
+ info.values_at('ewres', 'nsres').map(&:to_i)
460
+ end
461
+ ```
462
+
463
+ #### 3. Average angle
464
+
465
+ Let's assume we have a raster map `aspect` which is
466
+ a direction angle (i.e. a cyclic value from 0 to 360).
467
+
468
+ Now imagine that we need to compute a coarser raster grid with
469
+ average values per cell. We can't just resample the angle
470
+ (we want the average of 359 and 1 be 0, not 180);
471
+ we would need an unitary vector or complex number to take averages.
472
+
473
+ The next method will perform the average correctly using
474
+ auxiliary raster maps for two cartesian components (that represent
475
+ the angle as a vector).
476
+
477
+ ```ruby
478
+ def resample_average_angle(grass, options = {})
479
+ input_raster = options[:input]
480
+ raise "Raster #{input_raster} not found" unless raster_exists?(grass, input_raster)
481
+ input_res = raster_res(grass, input_raster)
482
+
483
+ if options[:output_res]
484
+ output_res = options[:output_res]
485
+ unless output_res.is_a?(Array)
486
+ output_res = [output_res, output_res]
487
+ end
488
+ else
489
+ output_res = region_res(grass)
490
+ end
491
+
492
+ output_raster = options[:output]
493
+
494
+ grass.session do
495
+ unless raster_exists?(self, "#{input_raster}_sin")
496
+ g.region ewres: input_res[0], nsres: input_res[1]
497
+ r.mapcalc "#{input_raster}_sin = sin(#{input_raster})"
498
+ end
499
+ unless raster_exists?(self, "#{input_raster}_cos")
500
+ g.region ewres: input_res[0], nsres: input_res[1]
501
+ r.mapcalc "#{input_raster}_cos = cos(#{input_raster})"
502
+ end
503
+ g.region ewres: output_res[0], nsres: output_res[1]
504
+ r.resamp.stats input: "#{input_raster}_cos", output: "#{output_raster}_cos"
505
+ r.resamp.stats input: "#{input_raster}_sin", output: "#{output_raster}_sin"
506
+ r.mapcalc "#{output_raster} = atan(#{output_raster}_cos,#{output_raster}_sin)"
507
+ r.colors map: ouput_raster, raster: input_raster
508
+ g.remove '-f', type: 'raster', name: ["#{output_raster}_cos", "#{output_raster}_sin"]
509
+ g.remove -f type=raster name=aspect_sin@landscape,aspect_cos@landscape
510
+ end
511
+ end
512
+ ```
513
+
514
+ Now, to resample a (cyclic angular) map `aspect_hires` to a lower resolution 10:
515
+
516
+ ```ruby
517
+ GrassGis.session configuration do
518
+ resamp_average self,
519
+ input: 'aspect_hires',
520
+ output: 'aspect_lowres', output_res: 10
521
+ end
522
+ end
523
+ ```
524
+
525
+ ## Roadmap
526
+
527
+ * Change Module to define explicitly available GRASS commands instead of
528
+ accepting anything with `method_missing`. Declare commands with permitted
529
+ arguments and options, etc.
530
+ * Add some session helpers:
531
+ - Method to clean GRASS temporaries ($GISBASE/etc/clean_temp), or do
532
+ it automatically when disposing the session.
533
+ - Methods to check if maps exist
534
+ - Methods that return information as objects (arrays, hashes), e.g.
535
+ values returned by r.what, the current region, etc.
536
+ - Methods that execute operations in a GRASS-version independent
537
+ manner (higher level, version independent interface to modules).
538
+
539
+ ### GRASS cooking DSL
540
+
541
+ Create a DSL to define GRASS processing recipes.
542
+
543
+ It should handle errors gracefully during recipe execution,
544
+ take care of logging, parsing command output, temporary files, etc.
545
+
546
+ A recipe could have requirements such as which other recipes it depends
547
+ upon (so must be executed first), which map layers must exist, etc.
548
+
549
+ It could also define what map layers or other files are generated by the
550
+ recipe.
551
+
552
+ Example of intended recipe syntax:
553
+
554
+ ```ruby
555
+ recipe :resamp_average do |options = {}|
556
+ # ...
557
+ end
558
+
559
+ recipe :generate_working_dem do |...|
560
+ define :working_dem, 'dem'
561
+ define :working_slope, 'slope'
562
+ define :working_aspect, 'aspect'
563
+ g.region res: working_res
564
+ apply :resamp_average, input: base_dem, output: working_dem, input_res: base_dem_res, output_res: working_res
565
+ apply :resamp_average, input: base_slope, output: working_slope, input_res: base_dem_res, output_res: working_res
566
+ apply :resamp_average, input: base_aspect, output: working_aspect, input_res: base_dem_res, output_res: working_res, direction: true
567
+ describe working_slope, "Pendiente en grados a #{working_res} #{region_units}"
568
+ # ...
569
+ end
570
+ ```
571
+
572
+ ## Contributing
573
+
574
+ 1. Fork it ( https://github.com/[my-github-username]/grassgis/fork )
575
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
576
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
577
+ 4. Push to the branch (`git push origin my-new-feature`)
578
+ 5. Create a new Pull Request