module GrassCookbook

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

Public Class Methods

[](recipe) click to toggle source
# 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) click to toggle source
# File lib/grassgis/cookbook.rb, line 419
def achievable_results(grass, parameters)
  inputs = available_data(grass, parameters)
  inputs + plan(inputs).last
end
all_files_possible() click to toggle source
# File lib/grassgis/cookbook.rb, line 366
def all_files_possible
  @recipes.values.map(&:generated_files).flatten.uniq
end
all_files_used() click to toggle source
# File lib/grassgis/cookbook.rb, line 358
def all_files_used
  @recipes.values.map(&:required_files).flatten.uniq
end
all_maps_possible() click to toggle source
# File lib/grassgis/cookbook.rb, line 370
def all_maps_possible
  @recipes.values.map(&:generated_maps).flatten(1).uniq
end
all_maps_used() click to toggle source
# File lib/grassgis/cookbook.rb, line 362
def all_maps_used
  @recipes.values.map(&:required_maps).flatten(1).uniq
end
available_data(grass, parameters) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_files() click to toggle source

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

# File lib/grassgis/cookbook.rb, line 426
def existing_external_input_files
  existing_input_files - all_files_possible
end
existing_input_files() click to toggle source
# File lib/grassgis/cookbook.rb, line 374
def existing_input_files
  all_files_used.select { |f| File.exists?(f) }
end
existing_input_maps(grass) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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) click to toggle source

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

# 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) click to toggle source
# 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) click to toggle source
# 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