grassgis 0.4.3 → 0.5.0

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: 31603b3573329e791fb150b29bdbc2431ac67b73
4
- data.tar.gz: 22cef2943a628277b2811565625be816400a5cfa
3
+ metadata.gz: cf52083bd1c17e88f35637c33190bb5118c315ae
4
+ data.tar.gz: d839e49ddad296eaf6571b7c84accc7afeb91222
5
5
  SHA512:
6
- metadata.gz: b5f0bc0d8e6f86f97e25ffb8d70ad2ba7dc34b699be10350e0f72ffd67162760ff08c0fa5477ecd63829386e3c3f4d82c2d248ab2aa9972880ca1b26346e54b6
7
- data.tar.gz: 9f652b3f652615c98ab606fee366546d6c4cac5ca5c47e69b87eed9479496c9f9bc9110ec94e435b7fd1295a20aa68fb9170f655bcc9e032f23bae571a73301b
6
+ metadata.gz: bd781b3d5cbb21cd66512b72aa8146beed1f7488c7683ecee4c3af8e0988de0939e660cfe9658051f6a01c71694deb521fa1c3247f89de6d8381e28ba5930a09
7
+ data.tar.gz: 54d0b9056cee6f5382bdbc3dfc690bd6b2aa79a4777c88ef00dc153cc6b20e2d0bb10676e1253361e200371829ca5850f76e23315f57676f4fea7465f8000698
data/.gitignore CHANGED
@@ -1,22 +1,22 @@
1
- *.gem
2
- *.rbc
3
- .bundle
4
- .config
5
- .yardoc
6
- Gemfile.lock
7
- InstalledFiles
8
- _yardoc
9
- coverage
10
- doc/
11
- lib/bundler/man
12
- pkg
13
- rdoc
14
- spec/reports
15
- test/tmp
16
- test/version_tmp
17
- tmp
18
- *.bundle
19
- *.so
20
- *.o
21
- *.a
22
- mkmf.log
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
@@ -1,7 +1,7 @@
1
- language: ruby
2
- rvm:
3
- - 2.2.2
4
- - 2.1.6
5
- - 1.9.3
6
- before_install:
7
- - gem update bundler
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - 2.1.6
5
+ - 1.9.3
6
+ before_install:
7
+ - gem update bundler
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in grassgis.gemspec
4
- gemspec
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in grassgis.gemspec
4
+ gemspec
@@ -1,22 +1,22 @@
1
- Copyright (c) 2015 Javier Goizueta
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2015 Javier Goizueta
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,578 +1,722 @@
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
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
+ - [Recipes](#recipes)
23
+ - [Technicalities](#technicalities)
24
+ - [Session scopes](#session-scopes)
25
+ - [Invalid commands](#invalid-commands)
26
+ - [Helper methods](#helper-methods)
27
+ - [Examples](#examples)
28
+ - [1. Map existence](#1-map-existence)
29
+ - [2. Information as Hashes](#2-information-as-hashes)
30
+ - [3. Average angle](#3-average-angle)
31
+ - [Roadmap](#roadmap)
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
+ ### Recipes
271
+
272
+ The `GrassCookbook` interface can be used to define geoprocessing
273
+ "recipes", each one specifying which data is required and
274
+ produced by the process.
275
+
276
+ A recipe is defined by calling `GrassCookbook.recipe` with a block
277
+ that provides the recipe definition in a declarative way, using
278
+ methods such as `description`, `required_parameters`, `required_files`,
279
+ `required_raster_maps`, `generated_rater_maps`, etc.
280
+
281
+ The `process` method defines, by using a block, the recipes's procedure.
282
+ The arguments to this block will be taken auto-magically from parameters
283
+ of the same name (parameters are provided to a recipe-executing environment
284
+ through a Hash).
285
+
286
+ The available `GrassCookbook` methods can be used to determine which recipes need
287
+ to be executed, in which order, and which products will be generated
288
+ based on available data.
289
+
290
+ For example, given this three recipes:
291
+
292
+ ```ruby
293
+ GrassCookbook.recipe :dem_base_from_mdt05 do
294
+ description %{
295
+ Generate a DEM for the location region at fixed 5m resolution
296
+ from CNIG's MDT05 data.
297
+ }
298
+
299
+ required_files 'data/MDT05'
300
+ generated_raster_maps 'dem_base'
301
+
302
+ process do |mdt05_sheets|
303
+ # Import MDT05 sheets and generate dem_base
304
+ mdt05_sheets = mdt05_sheets.map { |n| "MDT05-#{n}-H30-LIDAR.asc"}
305
+ sheet_maps = Hash[mdt05_sheets.map { |s| [s, File.basename(s, '.asc')]}]
306
+ mdt05_sheets.each do |sheet|
307
+ map = sheet_maps[sheet]
308
+ r.in.gdal '-o', '--overwrite',
309
+ input: File.join('data', 'MDT05', sheet),
310
+ output: map
311
+ r.colors map: map, color: 'elevation'
312
+ end
313
+ # Keep previous resolution
314
+ ewres, nsres = region_res
315
+ # Patch sheets and crop
316
+ g.region res: 5
317
+ r.patch input: sheet_maps.values, output: 'dem_base'
318
+ r.colors map: 'dem_base', color: 'elevation'
319
+ # Restore previous resolution
320
+ g.region nsres: nsres, ewres: ewres
321
+ g.remove '-f', type: 'raster', name: sheet_maps.values
322
+ end
323
+ end
324
+
325
+ GrassCookbook.recipe :dem_base_derived do
326
+ description %{
327
+ Generate DEM-derived maps from the 5m dem base:
328
+ slope, aspect and relief shading
329
+ }
330
+
331
+ required_raster_maps 'dem_base'
332
+ generated_raster_maps 'shade_base', 'slope_base', 'aspect_base'
333
+
334
+ process do
335
+ r.relief input: 'dem_base', output: 'shade_base'
336
+ r.slope.aspect elevation: 'dem_base',
337
+ slope: 'slope_base',
338
+ aspect: 'aspect_base'
339
+ end
340
+ end
341
+
342
+ GrassCookbook.recipe :working_dem do
343
+ description %{
344
+ Generate DEM data at working resolution
345
+ }
346
+
347
+ required_raster_maps 'dem_base', 'slope_base', 'aspect_base'
348
+ generated_raster_maps 'dem', 'slope', 'aspect'
349
+
350
+ process do |resolution|
351
+ # Keep previous resolution
352
+ ewres, nsres = region_res
353
+
354
+ resamp_average input: 'dem_base', output: 'dem', output_res: resolution
355
+ resamp_average input: 'slope_base', output: 'slope', output_res: resolution
356
+ resamp_average input: 'aspect_base', output: 'aspect', output_res: resolution, direction: true
357
+
358
+ # Restore previous resolution
359
+ g.region nsres: nsres, ewres: ewres
360
+ end
361
+ end
362
+ ```
363
+
364
+ We could now use those recipes to compute some permanent base maps and
365
+ then maps for alternative scenarios by varying some parameter.
366
+
367
+ In this example the fixed parameter defines the available data
368
+ we have to create a base DEM at a resolution of 5 meters,
369
+ which will be kept in the PERMANENT mapset.
370
+
371
+ Then we vary the `resolution` parameter to compute derived information
372
+ (topography information at the given resolution) for two values
373
+ of the parameter (10m and 25m) which will produce two mapsets with the
374
+ name assigned to the variant scenario ('10m' and '25m')
375
+ and all the maps that depend on the varying parameter in each of them.
376
+
377
+ ```ruby
378
+ GrassGis.session grass_config do
379
+ # First generate maps using fixed parameters and move them to PERMANENT
380
+ fixed_parameters = { mdt05_sheets: %w(0244 0282) }
381
+ fixed_data = primary = GrassCookbook::Data[
382
+ parameters: fixed_parameters.keys,
383
+ files: GrassCookbook.existing_external_input_files
384
+ ]
385
+ plan = GrassCookbook.plan(fixed_data)
386
+ permanent = plan.last
387
+ GrassCookbook.replace_existing_products self, plan
388
+ GrassCookbook.execute self, fixed_parameters, plan
389
+ permanent.maps.each do |map, type|
390
+ move_map(map, type: type, to: 'PERMANENT')
391
+ end
392
+
393
+ # Then define some variations of other parameters and create a mapset
394
+ # for each variation, where maps dependent on the varying parameters
395
+ # will be put
396
+ variants = {
397
+ '10m' => { resolution: 10 },
398
+ '25m' => { resolution: 25 }
399
+ }
400
+ for variant_name, variant_parameters in variants
401
+ data = GrassCookbook::Data[parameters: variant_parameters.keys] + permanent
402
+ plan = GrassCookbook.plan(data)
403
+ GrassCookbook.replace_existing_products self, plan
404
+ GrassCookbook.execute self, fixed_parameters.merge(variant_parameters), plan
405
+ variant_maps = (plan.last - data).maps
406
+ create_mapset variant_name
407
+ variant_maps.each do |map, type|
408
+ move_map(map, type: type, to: variant_name)
409
+ end
410
+ end
411
+ end
412
+ ```
413
+
414
+ ### Technicalities
415
+
416
+ #### Session scopes
417
+
418
+ In a session block, the Ruby `self` object is altered to
419
+ refer to a `GrassGis::Context` object. That means that in addition
420
+ to the enclosing `self`, any instance variables of the enclosing
421
+ scope are not directly available. This may cause some surprises
422
+ but is easy to overcome.
423
+
424
+ ```ruby
425
+ @value = 10
426
+ GrassGis.session configuration do
427
+ puts @value # nil!
428
+ end
429
+ ```
430
+
431
+ A possible workaround is to assign instance variables that we need
432
+ in the session to local variables:
433
+
434
+ ```ruby
435
+ @value = 10
436
+ value = @value
437
+ GrassGis.session configuration do
438
+ puts value # 10
439
+ end
440
+ ```
441
+
442
+ To avoid defining these variables you can pass a `:locals` Hash
443
+ in the configuration to define values that you need to access
444
+ in the session (but you won't be able to assign to them, because
445
+ they're not local variables!)
446
+
447
+ ```ruby
448
+ @value = 10
449
+
450
+ GrassGis.session configuration.merge(locals: { value: @value }) do
451
+ puts value # 10
452
+ value = 11 # don't do this: you're creating a local in the session
453
+ end
454
+ ```
455
+
456
+ A different approach is prevent the session block from using a special
457
+ `self` by defining a parameter to the block. This parameter will have
458
+ the value of a `GrassGis::Context` which you'll need to explicitly use
459
+ to execute any commands:
460
+
461
+ ```ruby
462
+ @value = 10
463
+ GrassGis.session configuration do |grass|
464
+ puts @value # 10
465
+ grass.g.region res: 10 # now you need to use the object to issue commands
466
+ end
467
+ ```
468
+
469
+ The GRASS command `g.mapset` should not be used to change
470
+ the current mapset, use the `change_mapset` method in a GrassGis
471
+ session instead:
472
+
473
+ ```ruby
474
+ GrassGis.session configuration do
475
+ # Get the name of the current mapset
476
+ g.mapsets '-p'
477
+ mapset = output.lines.last.split.first.inspect
478
+ # Copy 'some_map' raster map to PERMANENT
479
+ change_mapset 'PERMANENT'
480
+ g.copy rast: "some_map@#{original_mapset},some_map"
481
+ # Get back to our mapset
482
+ change_mapset mapset
483
+ end
484
+ ```
485
+
486
+ #### Invalid commands
487
+
488
+ Currently the generation of GRASS commands inside a session is
489
+ implemented in a very simple way which allows to generate any command
490
+ name even if it is invalid or does not exist. This has the advantage
491
+ of supporting any version of GRASS, but doesn't allow for early
492
+ detection of invalid commands (e.g. due to typos) or invalid command
493
+ parameters.
494
+
495
+ ```ruby
496
+ GrassGis.session configuration do |grass|
497
+ g.region res: 10 # Oops (runtime error)
498
+ g.anything.goes.run # another runtime error
499
+ end
500
+ ```
501
+
502
+ If the command generated does not exist a runtime `ENOENT` exception will
503
+ occur.
504
+
505
+ If the command exists, then if parameters are not valid, the command
506
+ will execute but will return an error status. This will be handled
507
+ as explained above.
508
+
509
+ ## Helper methods
510
+
511
+ When writing a non-trivial program you'll probably
512
+ find you want to define methods to avoid unnecessary repetition.
513
+
514
+ Let's see how you can call methods from your session and be
515
+ able to execute GRASS commands from the method in the context of the session.
516
+
517
+ Inside a session, `self` refers to an object of class
518
+ `GrassGis::Context` which represents the current GRASS session.
519
+
520
+ You can invoke grass commands directly on this object, so, if you pass
521
+ this object around you can use it to execute GRASS commands:
522
+
523
+ ```ruby
524
+ def helper_method(grass)
525
+ # ...
526
+ end
527
+
528
+ GrassGis.session configuration do
529
+ helper_method self
530
+ end
531
+ ```
532
+
533
+ In the helper method you can use the grass object like this;
534
+
535
+ ```ruby
536
+ def helper_method(grass)
537
+ # change the current region resolution
538
+ grass.g.region res: 10
539
+ end
540
+ ```
541
+
542
+ To avoid having to prepend each command with `grass.` you can
543
+ use the `session` method like this:
544
+
545
+ ```ruby
546
+ def helper_method(grass)
547
+ grass.session do
548
+ g.region res: 10
549
+ g.region '-p'
550
+ puts output
551
+ end
552
+ end
553
+ ```
554
+
555
+ An alternative is to use a Ruby module and extend the session with it:
556
+
557
+ ```ruby
558
+ module Helpers
559
+ def helper_method
560
+ g.region res: 10
561
+ g.region '-p'
562
+ puts output
563
+ end
564
+ end
565
+
566
+ GrassGis.session configuration do
567
+ extend Helpers
568
+ helper_method
569
+ end
570
+ ```
571
+
572
+ ### Examples
573
+
574
+ Note: the functionality of these examples is now provided by
575
+ a Tools module which is included by default in GrassGis sessions.
576
+
577
+ #### 1. Map existence
578
+
579
+ Helper methods to check for the existence of maps.
580
+
581
+ Often we may want to know if a map exists. Next methods can be used to
582
+ check for it.
583
+
584
+ ```ruby
585
+ def map_exists?(grass, type, map)
586
+ grass.g.list type
587
+ maps = grass.output.split
588
+ maps.include?(map)
589
+ end
590
+
591
+ def raster_exists?(grass, map)
592
+ map_exists? grass, 'rast', map
593
+ end
594
+
595
+ def vector_exists?(grass, map)
596
+ map_exists? grass, 'vect', map
597
+ end
598
+ ```
599
+
600
+ We can use these methods like this:
601
+
602
+ ```ruby
603
+ GrassGis.session configuration do
604
+ unless raster_exists?(self, 'product')
605
+ r.mapcalc "product = factor1*factor2"
606
+ end
607
+ end
608
+ ```
609
+
610
+ #### 2. Information as Hashes
611
+
612
+ Following methods show how to obtain information about a raster map
613
+ and the current region as a Hash:
614
+
615
+ ```ruby
616
+ def raster_info(grass, map)
617
+ grass.r.info '-g', map
618
+ shell_to_hash grass
619
+ end
620
+
621
+ def region_info(grass)
622
+ grass.g.region '-m'
623
+ shell_to_hash grass
624
+ end
625
+
626
+ def shell_to_hash(grass)
627
+ Hash[grass.output.lines.map{|l| l.strip.split('=')}]
628
+ end
629
+
630
+ # Now, for example, we can easily obtain the resolution of a raster:
631
+
632
+ def raster_res(grass, map)
633
+ info = raster_info(grass, map)
634
+ info.values_at('ewres', 'nsres').map(&:to_i)
635
+ end
636
+
637
+ def region_res(grass)
638
+ info = region_info(grass)
639
+ info.values_at('ewres', 'nsres').map(&:to_i)
640
+ end
641
+ ```
642
+
643
+ #### 3. Average angle
644
+
645
+ Let's assume we have a raster map `aspect` which is
646
+ a direction angle (i.e. a cyclic value from 0 to 360).
647
+
648
+ Now imagine that we need to compute a coarser raster grid with
649
+ average values per cell. We can't just resample the angle
650
+ (we want the average of 359 and 1 be 0, not 180);
651
+ we would need an unitary vector or complex number to take averages.
652
+
653
+ The next method will perform the average correctly using
654
+ auxiliary raster maps for two cartesian components (that represent
655
+ the angle as a vector).
656
+
657
+ ```ruby
658
+ def resample_average_angle(grass, options = {})
659
+ input_raster = options[:input]
660
+ raise "Raster #{input_raster} not found" unless raster_exists?(grass, input_raster)
661
+ input_res = raster_res(grass, input_raster)
662
+
663
+ if options[:output_res]
664
+ output_res = options[:output_res]
665
+ unless output_res.is_a?(Array)
666
+ output_res = [output_res, output_res]
667
+ end
668
+ else
669
+ output_res = region_res(grass)
670
+ end
671
+
672
+ output_raster = options[:output]
673
+
674
+ grass.session do
675
+ unless raster_exists?(self, "#{input_raster}_sin")
676
+ g.region ewres: input_res[0], nsres: input_res[1]
677
+ r.mapcalc "#{input_raster}_sin = sin(#{input_raster})"
678
+ end
679
+ unless raster_exists?(self, "#{input_raster}_cos")
680
+ g.region ewres: input_res[0], nsres: input_res[1]
681
+ r.mapcalc "#{input_raster}_cos = cos(#{input_raster})"
682
+ end
683
+ g.region ewres: output_res[0], nsres: output_res[1]
684
+ r.resamp.stats input: "#{input_raster}_cos", output: "#{output_raster}_cos"
685
+ r.resamp.stats input: "#{input_raster}_sin", output: "#{output_raster}_sin"
686
+ r.mapcalc "#{output_raster} = atan(#{output_raster}_cos,#{output_raster}_sin)"
687
+ r.colors map: ouput_raster, raster: input_raster
688
+ g.remove '-f', type: 'raster', name: ["#{output_raster}_cos", "#{output_raster}_sin"]
689
+ g.remove -f type=raster name=aspect_sin@landscape,aspect_cos@landscape
690
+ end
691
+ end
692
+ ```
693
+
694
+ Now, to resample a (cyclic angular) map `aspect_hires` to a lower resolution 10:
695
+
696
+ ```ruby
697
+ GrassGis.session configuration do
698
+ resamp_average self,
699
+ input: 'aspect_hires',
700
+ output: 'aspect_lowres', output_res: 10
701
+ end
702
+ end
703
+ ```
704
+
705
+ ## Roadmap
706
+
707
+ * Change Module to define explicitly available GRASS commands instead of
708
+ accepting anything with `method_missing`. Declare commands with permitted
709
+ arguments and options, etc.
710
+ * Add some session helpers:
711
+ - Method to clean GRASS temporaries ($GISBASE/etc/clean_temp), or do
712
+ it automatically when disposing the session.
713
+ - Methods that execute operations in a GRASS-version independent
714
+ manner (higher level, version independent interface to modules).
715
+
716
+ ## Contributing
717
+
718
+ 1. Fork it ( https://github.com/[my-github-username]/grassgis/fork )
719
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
720
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
721
+ 4. Push to the branch (`git push origin my-new-feature`)
722
+ 5. Create a new Pull Request