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