Module: GrassCookbook

Defined in:
lib/grassgis/cookbook.rb

Overview

Recipes can only be defined when input parameters, files and maps are specific (although an input file can be a directory with an indetermninate number of files inside) and there’s no dependency between input parameters/files/maps and which products are generated. (so the generated products are also specific, except, again, for the contents of directories). Also, recipes shouldn’t generate any non-declared maps, i.e. temporary or auxiliary maps should be removed before the recipe ends.

Temporary maps & files support, e.g.: temporaries are declared, then recipe is executed in a block with a ensure which removes temporary maps

Example:

 GrassCookbook.recipe :dem_base_from_mdt05 do
   description %{
     Generate a DEM for the interest area at fixed 5m resolution
     from CNIG's MDT05 data.
   }

   required_files 'data/MDT05'
   generated_raster_maps 'dem_base'

   process do |mdt05_sheets|
     ...
   end
 end

 GrassCookbook.recipe :dem_base_derived do
  description %{
    Generate DEM-derived maps from the 5m dem base:
    slope, aspect and relief shading
  }

  required_raster_maps 'dem_base'
  generated_raster_maps 'shade_base', 'slope_base', 'aspect_base'

  process do
    r.relief input: 'dem_base', output: 'shade_base'
    r.slope.aspect elevation: 'dem_base',
                   slope:     'slope_base',
                   aspect:    'aspect_base'
  end
end

GrassCookbook.recipe :working_dem do
  description %{
    Generate DEM data at working resolution
  }

  required_raster_maps 'dem_base', 'slope_base', 'aspect_base'
  generated_raster_maps 'dem', 'slope', 'aspect'

  process do |resolution|
    ...
  end
end

# Now use our recipes to compute some permanent base maps and
# alternative scenario mapsheets varying some parameter.

GrassGis.session grass_config do
  # First generate maps using fixed parameters and move them to PERMANENT
  fixed_parameters = { mdt05_sheets: %w(0244 0282) }
  fixed_data = primary = GrassCookbook::Data[
    parameters: fixed_parameters.keys,
    files: GrassCookbook.existing_external_input_files
  ]
  plan = GrassCookbook.plan(fixed_data)
  permanent = plan.last
  GrassCookbook.replace_existing_products self, plan
  GrassCookbook.execute self, fixed_parameters, plan
  permanent.maps.each do |map, type|
    move_map(map, type: type, to: 'PERMANENT')
  end

  # Then define some variations of other parameters and create a mapset
  # for each variation, where maps dependent on the varying parameters
  # will be put
  variants = {
    '10m' => { resolucion: 10 },
    '25m' => { resolucion: 25 }
  }
  for variant_name, variant_parameters in variants
     data = GrassCookbook::Data[parameters: variant_parameters.keys] + permanent
     plan = GrassCookbook.plan(data)
     GrassCookbook.replace_existing_products self, plan
     GrassCookbook.execute self, fixed_parameters.merge(variant_parameters), plan
     variant_maps = (plan.last - data).maps
     create_mapset variant_name
     variant_maps.each do |map, type|
       move_map(map, type: type, to: variant_name)
     end
   end
 end

Defined Under Namespace

Classes: Data, Recipe, RecipeDsl

Class Method Summary collapse

Class Method Details

.[](recipe) ⇒ Object



345
346
347
348
349
350
351
# File 'lib/grassgis/cookbook.rb', line 345

def [](recipe)
  if recipe.is_a? Recipe
    recipe
  else
    @recipes[recipe.to_sym]
  end
end

.achievable_results(grass, parameters) ⇒ Object



419
420
421
422
# File 'lib/grassgis/cookbook.rb', line 419

def achievable_results(grass, parameters)
  inputs = available_data(grass, parameters)
  inputs + plan(inputs).last
end

.all_files_possibleObject



366
367
368
# File 'lib/grassgis/cookbook.rb', line 366

def all_files_possible
  @recipes.values.map(&:generated_files).flatten.uniq
end

.all_files_usedObject



358
359
360
# File 'lib/grassgis/cookbook.rb', line 358

def all_files_used
  @recipes.values.map(&:required_files).flatten.uniq
end

.all_maps_possibleObject



370
371
372
# File 'lib/grassgis/cookbook.rb', line 370

def all_maps_possible
  @recipes.values.map(&:generated_maps).flatten(1).uniq
end

.all_maps_usedObject



362
363
364
# File 'lib/grassgis/cookbook.rb', line 362

def all_maps_used
  @recipes.values.map(&:required_maps).flatten(1).uniq
end

.available_data(grass, parameters) ⇒ Object



411
412
413
414
415
416
417
# File 'lib/grassgis/cookbook.rb', line 411

def available_data(grass, parameters)
  Data[
    parameters: parameters,
    files: existing_input_files,
    maps: existing_input_maps(grass)
  ]
end

.cook(grass, recipe, parameters) ⇒ Object



353
354
355
356
# File 'lib/grassgis/cookbook.rb', line 353

def cook(grass, recipe, parameters)
  grass.log "Recipe: #{recipe}"
  self[recipe].cook grass, parameters
end

.execute(grass, parameters, plan) ⇒ Object



450
451
452
453
454
455
456
# File 'lib/grassgis/cookbook.rb', line 450

def execute(grass, parameters, plan)
  recipes, result = plan
  recipes.each do |recipe|
    cook grass, recipe, parameters
  end
  result
end

.existing_external_input_filesObject

primary input files input files that exist (and are not generated, so they are externally provided input)



426
427
428
# File 'lib/grassgis/cookbook.rb', line 426

def existing_external_input_files
  existing_input_files - all_files_possible
end

.existing_input_filesObject



374
375
376
# File 'lib/grassgis/cookbook.rb', line 374

def existing_input_files
  all_files_used.select { |f| File.exists?(f) }
end

.existing_input_maps(grass) ⇒ Object



378
379
380
381
382
# File 'lib/grassgis/cookbook.rb', line 378

def existing_input_maps(grass)
  # TODO: use mapset PATH here (add as another parameters)
  path = []
  all_maps_used.select { |m, t| grass.map_exists?(m, type: t, mapset: path) }
end

.impossible_results(grass, parameters) ⇒ Object



442
443
444
445
446
447
448
# File 'lib/grassgis/cookbook.rb', line 442

def impossible_results(grass, parameters)
  possibilities = Data[
    files: all_files_possible,
    maps:  all_maps_possible
  ]
  possibilities - achievable_results(grass, parameters)
end

.missing_input(grass) ⇒ Object



435
436
437
438
439
440
# File 'lib/grassgis/cookbook.rb', line 435

def missing_input(grass)
  Data[
    files: all_files_used - existing_input_files,
    maps: all_maps_used - existing_input_maps(grass)
  ]
end

.permantent_results(grass, fixed_parameters) ⇒ Object



430
431
432
433
# File 'lib/grassgis/cookbook.rb', line 430

def permantent_results(grass, fixed_parameters)
  primary = Data[parameters: fixed_parameters, files: existing_external_input_files]
  plan(primary).last
end

.plan(input_data) ⇒ Object

Generate ordered recipes and generated products (output) than can be obtained with available inputdata.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/grassgis/cookbook.rb', line 386

def plan(input_data)
  input = Data[input_data]
  existing = input.dup

  applied_recipes = []

  remaining_recipes = @recipes.values - applied_recipes

  while remaining_recipes.size > 0
    progress = false
    remaining_recipes.each do |recipe|
      unless recipe.done?(existing)
        if recipe.doable?(existing)
          progress = true
          applied_recipes << recipe
          existing.merge! recipe.products
        end
      end
    end
    break unless progress
    remaining_recipes -= applied_recipes
  end
  [applied_recipes, existing - input]
end

.recipe(id, &blk) ⇒ Object



339
340
341
342
343
# File 'lib/grassgis/cookbook.rb', line 339

def recipe(id, &blk)
  dsl = RecipeDsl.new(id)
  dsl.instance_eval &blk
  @recipes[id.to_sym] = dsl.recipe
end

.replace_existing_products(grass, plan, parameters = nil) ⇒ Object



458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/grassgis/cookbook.rb', line 458

def replace_existing_products(grass, plan, parameters = nil)
  results = plan.last
  if parameters
    results.parameters.each do |parameter|
      parameters.delete parameter
    end
  end
  results.maps.each do |map, type|
    mapset = grass.explicit_map_mapset(map) || grass.current_mapset
    if grass.map_exists?(map, type: type, mapset: mapset)
      grass.remove_map(map, type: type, mapset: mapset)
    end
  end
  results.files.each do |file|
    if File.exists?(file)
      if File.directory?(file)
        FileUtils.rm_rf file
      else
        FileUtils.rm file
      end
    end
  end
end