grassgis 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +22 -22
- data/.travis.yml +7 -7
- data/Gemfile +4 -4
- data/LICENSE.txt +22 -22
- data/README.md +722 -578
- data/Rakefile +22 -22
- data/Vagrantfile +19 -18
- data/grassgis.gemspec +28 -28
- data/lib/grassgis.rb +10 -8
- data/lib/grassgis/context.rb +2 -0
- data/lib/grassgis/cookbook.rb +483 -0
- data/lib/grassgis/error.rb +4 -4
- data/lib/grassgis/location.rb +53 -53
- data/lib/grassgis/mapset.rb +31 -31
- data/lib/grassgis/module.rb +73 -73
- data/lib/grassgis/support.rb +26 -26
- data/lib/grassgis/tools.rb +215 -0
- data/lib/grassgis/version.rb +3 -3
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf52083bd1c17e88f35637c33190bb5118c315ae
|
4
|
+
data.tar.gz: d839e49ddad296eaf6571b7c84accc7afeb91222
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/.travis.yml
CHANGED
@@ -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
|
data/LICENSE.txt
CHANGED
@@ -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
|
-
[](http://badge.fury.io/rb/grassgis)
|
4
|
-
[](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
|
-
- [
|
23
|
-
|
24
|
-
- [
|
25
|
-
- [
|
26
|
-
|
27
|
-
|
28
|
-
- [
|
29
|
-
- [
|
30
|
-
- [
|
31
|
-
|
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
|
-
###
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
end
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
```
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
end
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
end
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
end
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
```
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
the
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
```
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
end
|
570
|
-
```
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
1
|
+
# GrassGis
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/grassgis)
|
4
|
+
[](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
|