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
# File lib/grassgis/cookbook.rb, line 345 def [](recipe) if recipe.is_a? Recipe recipe else @recipes[recipe.to_sym] end end
# File lib/grassgis/cookbook.rb, line 419 def achievable_results(grass, parameters) inputs = available_data(grass, parameters) inputs + plan(inputs).last end
# File lib/grassgis/cookbook.rb, line 366 def all_files_possible @recipes.values.map(&:generated_files).flatten.uniq end
# File lib/grassgis/cookbook.rb, line 358 def all_files_used @recipes.values.map(&:required_files).flatten.uniq end
# File lib/grassgis/cookbook.rb, line 370 def all_maps_possible @recipes.values.map(&:generated_maps).flatten(1).uniq end
# File lib/grassgis/cookbook.rb, line 362 def all_maps_used @recipes.values.map(&:required_maps).flatten(1).uniq end
# 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
# File lib/grassgis/cookbook.rb, line 353 def cook(grass, recipe, parameters) grass.log "Recipe: #{recipe}" self[recipe].cook grass, parameters end
# 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
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
# File lib/grassgis/cookbook.rb, line 374 def existing_input_files all_files_used.select { |f| File.exists?(f) } end
# 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
# 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
# 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
# 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
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
# 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
# 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