[GRASS-SVN] r74143 - in grass-addons/grass7/raster/r.estimap.recreation: . estimap_recreation images r.estimap.recreation

svn_grass at osgeo.org svn_grass at osgeo.org
Fri Mar 1 03:47:49 PST 2019


Author: nikosa
Date: 2019-03-01 03:47:49 -0800 (Fri, 01 Mar 2019)
New Revision: 74143

Added:
   grass-addons/grass7/raster/r.estimap.recreation/corine_accounting_to_maes_land_classes.rules
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/Makefile
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/__init__.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/accessibility.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/colors.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/components.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/constants.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/distance.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/grassy_utilities.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/labels.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/main.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/mobility.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/normalisation.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/panos.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/spectrum.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/supply_and_use.py
   grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/utilities.py
   grass-addons/grass7/raster/r.estimap.recreation/images/
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_area_of_interest.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_bathing_water_quality.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006_suitability.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_legend.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_demand.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_distance_to_infrastructure.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_land_suitability.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_local_administrative_units.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_mobility.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_opportunity.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_population_2015.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_1.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_2.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_3.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_4.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_based_on_corine_land_cover_2006.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_corine.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_masked.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_protected_areas.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum_high_provision_near.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_unmet_demand.png
   grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_water_resources.png
   grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/
   grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/Makefile
   grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.html
   grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.py
   grass-addons/grass7/raster/r.estimap.recreation/requirements-dev.txt
   grass-addons/grass7/raster/r.estimap.recreation/setup.py
   grass-addons/grass7/raster/r.estimap.recreation/test_integration.sh
Removed:
   grass-addons/grass7/raster/r.estimap.recreation/area_of_interest.png
   grass-addons/grass7/raster/r.estimap.recreation/bathing_water_quality.png
   grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006.png
   grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006_suitability.png
   grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_legend.png
   grass-addons/grass7/raster/r.estimap.recreation/demand.png
   grass-addons/grass7/raster/r.estimap.recreation/distance_to_infrastructure.png
   grass-addons/grass7/raster/r.estimap.recreation/land_suitability.png
   grass-addons/grass7/raster/r.estimap.recreation/local_administrative_units.png
   grass-addons/grass7/raster/r.estimap.recreation/mobility.png
   grass-addons/grass7/raster/r.estimap.recreation/opportunity.png
   grass-addons/grass7/raster/r.estimap.recreation/population_2015.png
   grass-addons/grass7/raster/r.estimap.recreation/potential.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_1.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_2.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_3.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_4.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_based_on_corine_land_cover_2006.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_corine.png
   grass-addons/grass7/raster/r.estimap.recreation/potential_masked.png
   grass-addons/grass7/raster/r.estimap.recreation/protected_areas.png
   grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.py
   grass-addons/grass7/raster/r.estimap.recreation/spectrum.png
   grass-addons/grass7/raster/r.estimap.recreation/spectrum_high_provision_near.png
   grass-addons/grass7/raster/r.estimap.recreation/test.r.estimap.recreation.py
   grass-addons/grass7/raster/r.estimap.recreation/unmet_demand.png
   grass-addons/grass7/raster/r.estimap.recreation/water_resources.png
Modified:
   grass-addons/grass7/raster/r.estimap.recreation/Makefile
   grass-addons/grass7/raster/r.estimap.recreation/README.md
   grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.html
Log:
r.estimap.recreation: Major refactoring, see also https://gitlab.com/natcapes/r.estimap.recreation/tags/v3.0

Modified: grass-addons/grass7/raster/r.estimap.recreation/Makefile
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/Makefile	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/Makefile	2019-03-01 11:47:49 UTC (rev 74143)
@@ -2,7 +2,12 @@
 
 PGM = r.estimap.recreation
 
-include $(MODULE_TOPDIR)/include/Make/Script.make
-include $(MODULE_TOPDIR)/include/Make/Python.make
+SUBDIRS = estimap_recreation \
+          r.estimap.recreation
 
-default: script
+include $(MODULE_TOPDIR)/include/Make/Dir.make
+
+default: parsubdirs htmldir
+
+install: installsubdirs
+	$(INSTALL_DATA) $(PGM).html $(INST_DIR)/docs/html/

Modified: grass-addons/grass7/raster/r.estimap.recreation/README.md
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/README.md	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/README.md	2019-03-01 11:47:49 UTC (rev 74143)
@@ -1,7 +1,7 @@
 Description
 -----------
 
-*r.estimap* is an implementation of the ESTIMAP recreation algorithm to
+*r.estimap.recreation* is an implementation of the ESTIMAP recreation algorithm to
 support mapping and modelling of ecosystem services (Zulian, 2014).
 
 Examples
@@ -19,10 +19,10 @@
 
 <div>
 
-![Example of a land suitability input map](area_of_interest.png)
-![Example of a land suitability input map](land_suitability.png)
-![Example of a water resources input map](water_resources.png) ![Example
-of a protected areas input map](protected_areas.png)
+![Example of a land suitability input map](images/r_estimap_recreation_area_of_interest.png)
+![Example of a land suitability input map](images/r_estimap_recreation_land_suitability.png)
+![Example of a water resources input map](images/r_estimap_recreation_water_resources.png) ![Example
+of a protected areas input map](images/r_estimap_recreation_protected_areas.png)
 
 </div>
 
@@ -51,11 +51,11 @@
 
 <div class="code">
 
-    r.estimap  land=land_suitability  potential=potential
+    r.estimap.recreation  land=land_suitability  potential=potential
 
 </div>
 
-![Example of a recreation potential output map](potential.png)
+![Example of a recreation potential output map](images/r_estimap_recreation_potential.png)
 
 Note, this will process the input map `land_suitability` over the extent
 defined previously via `g.region`, which is the standard behaviour in
@@ -66,12 +66,12 @@
 
 <div class="code">
 
-    r.estimap  land=land_suitability  mask=area_of_interest  potential=potential_1
+    r.estimap.recreation  land=land_suitability  mask=area_of_interest  potential=potential_1
 
 </div>
 
 ![Example of a recreation potential output map while using a
-MASK](potential_1.png)
+MASK](images/r_estimap_recreation_potential_1.png)
 
 The use of a mask (in GRASS GIS' terminology known as **MASK**) will
 ignore areas of **No Data** (pixels in the `area_of_interest` map
@@ -93,12 +93,12 @@
 
 <div class="code">
 
-    r.estimap  land=land_suitability  water=water_resources  potential=potential_2
+    r.estimap.recreation  land=land_suitability  water=water_resources  potential=potential_2
 
 </div>
 
 ![Example of a recreation potential output map while using a MASK, a
-land suitability map and a water resources map](potential_2.png)
+land suitability map and a water resources map](images/r_estimap_recreation_potential_2.png)
 
 At this point it becomes clear that all `NULL` cells present in the
 "water" map, are proagated in the output map `potential_2`.
@@ -109,13 +109,13 @@
 
 <div class="code">
 
-    r.estimap  land=land_suitability  water=water_resources  natural=protected_areas  potential=potential_3
+    r.estimap.recreation  land=land_suitability  water=water_resources  natural=protected_areas  potential=potential_3
 
 </div>
 
 ![Example of a recreation potential output map while using a MASK, a
 land suitability map, a water resources map and a natural resources
-map](potential_3.png)
+map](images/r_estimap_recreation_potential_3.png)
 
 While the `land` option accepts only one map as an input, both the
 `water` and the `natural` options accept multiple maps as inputs. In
@@ -124,7 +124,7 @@
 
 <div class="code">
 
-    r.estimap  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  potential=potential_4
+    r.estimap.recreation  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  potential=potential_4
 
 </div>
 
@@ -133,7 +133,7 @@
 
 ![Example of a recreation potential output map while using a MASK, a
 land suitability map, two water resources maps and a natural resources
-map](potential_4.png)
+map](images/r_estimap_recreation_potential_4.png)
 
 This example, features also a title and a legend, so as to make sense of
 the map.
@@ -164,13 +164,13 @@
 `distance_to_infrastructure` is defined as an input:
 
 ![Example of an input map showing distances to
-infrastructure](distance_to_infrastructure.png)
+infrastructure](images/r_estimap_recreation_distance_to_infrastructure.png)
 
 Naturally, we need to define the output map option `spectrum` too:
 
 <div class="code">
 
-    r.estimap  \
+    r.estimap.recreation  \
       land=land_suitability \
       water=water_resources,bathing_water_quality \
       natural=protected_areas \
@@ -183,13 +183,13 @@
 
 <div class="code">
 
-    r.estimap  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  spectrum=spectrum
+    r.estimap.recreation  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  spectrum=spectrum
 
 </div>
 
 ![Example of a recreation spectrum output map while using a MASK, a land
 suitability map, a water resources map and a natural resources
-map](spectrum.png)
+map](images/r_estimap_recreation_spectrum.png)
 
 Missing to define the `infrastructure` map, the command will abort and
 inform about.
@@ -215,7 +215,7 @@
 
 <div class="code">
 
-    r.estimap  \
+    r.estimap.recreation  \
       mask=area_of_interest \
       land=land_suitability \
       water=water_resources,bathing_water_quality \
@@ -230,13 +230,13 @@
 
 <div class="code">
 
-    r.estimap  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  spectrum=spectrum  infrastructure=distance_to_infrastructure  opportunity=opportunity
+    r.estimap.recreation  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  spectrum=spectrum  infrastructure=distance_to_infrastructure  opportunity=opportunity
 
 </div>
 
 ![Example of a recreation spectrum output map while using a MASK, a land
 suitability map, a water resources map and a natural resources
-map](opportunity.png)
+map](images/r_estimap_recreation_opportunity.png)
 
 #### More input maps
 
@@ -252,7 +252,7 @@
 
 Population
 
-![Fragment of a population map (GHSL, 2015)](population_2015.png)
+![Fragment of a population map (GHSL, 2015)](images/r_estimap_recreation_population_2015.png)
 
 In this example, the population map named `population_2015` is of
 1000m\^2.
@@ -260,7 +260,7 @@
 Local administrative units
 
 ![Fragment of a local administrative units input
-map](local_administrative_units.png)
+map](images/r_estimap_recreation_local_administrative_units.png)
 
 The map named `local_administrative_units` serves in the following
 example as the base map for the zonal statistics to obtain the demand
@@ -270,7 +270,7 @@
 
 <div class="code">
 
-    r.estimap --o \
+    r.estimap.recreation --o \
       mask=area_of_interest \
       land=land_suitability \
       water=water_resources,bathing_water_quality \
@@ -284,13 +284,13 @@
 
 ![Example of a demand distribution output map while using a MASK and
 inputs for land suitability, water resources, natural resources,
-infrastructure, population and base](demand.png)
+infrastructure, population and base](images/r_estimap_recreation_demand.png)
 
 #### Unmet Demand
 
 <div class="code">
 
-    r.estimap --o \
+    r.estimap.recreation --o \
       mask=area_of_interest \
       land=land_suitability \
       water=water_resources,bathing_water_quality \
@@ -305,7 +305,7 @@
 
 ![Example of an 'unmet demand' output map while using a MASK and inputs
 for land suitability, water resources, natural resources,
-infrastructure, population and base](unmet_demand.png)
+infrastructure, population and base](images/r_estimap_recreation_unmet_demand.png)
 
 #### Mobility
 
@@ -336,7 +336,7 @@
 
 <div class="code">
 
-    r.estimap --o \
+    r.estimap.recreation --o \
       mask=area_of_interest \
       land=land_suitability \
       water=water_resources,bathing_water_quality \
@@ -350,7 +350,7 @@
 
 ![Example of a mobility output map while using a MASK and inputs for
 land suitability, water resources, natural resources, infrastructure,
-population and base](mobility.png)
+population and base](images/r_estimap_recreation_mobility.png)
 
 #### All in one call
 
@@ -358,7 +358,7 @@
 
 <div class="code">
 
-    r.estimap --o  \
+    r.estimap.recreation --o  \
       mask=area_of_interest  \
       land=land_suitability  \
       water=water_resources,bathing_water_quality  \
@@ -441,8 +441,8 @@
 
 <div>
 
-![Fragment from the CORINE land data base ](corine_land_cover_2006.png)
-![Legend for the CORINE land data base](corine_land_cover_legend.png)
+![Fragment from the CORINE land data base ](images/r_estimap_recreation_corine_land_cover_2006.png)
+![Legend for the CORINE land data base](images/r_estimap_recreation_corine_land_cover_legend.png)
 
 </div>
 
@@ -487,18 +487,18 @@
 
 <div class="code">
 
-    r.estimap  landuse=corine_land_cover_2006 suitability_scores=corine_suitability.scores  potential=potential_corine --o
+    r.estimap.recreation  landuse=corine_land_cover_2006 suitability_scores=corine_suitability.scores  potential=potential_corine --o
 
 </div>
 
 ![Example of a recreation spectrum output map while using a MASK, based
-on a fragment from the CORINE land data base](potential_corine.png)
+on a fragment from the CORINE land data base](images/r_estimap_recreation_potential_corine.png)
 
 The same can be achieved with a long one-line string too:
 
 <div class="code">
 
-    r.estimap \
+    r.estimap.recreation \
       landuse=corine_land_cover_2006 \
       suitability_scores="1:1:0:0,2:2:0.1:0.1,3:9:0:0,10:10:1:1,11:11:0.1:0.1,12:13:0.3:0.3,14:14:0.4:0.4,15:17:0.5:0.5,18:18:0.6:0.6,19:20:0.3:0.3,21:22:0.6:0.6,23:23:1:1,24:24:0.8:0.8,25:25:1:1,26:29:0.8:0.8,30:30:1:1,31:31:0.8:0.8,32:32:0.7:0.7,33:33:0:0,34:34:0.8:0.8,35:35:1:1,36:36:0.8:0.8,37:37:1:1,38:38:0.8:0.8,39:39:1:1,40:42:1:1,43:43:0.8:0.8,44:44:1:1,45:45:0.3:0.3"  potential=potential_1 --o
 
@@ -510,7 +510,7 @@
 
 <div class="code">
 
-    r.estimap  landuse=corine_land_cover_2006  suitability_scores=corine_suitability.scores  potential=potential_1 --o
+    r.estimap.recreation  landuse=corine_land_cover_2006  suitability_scores=corine_suitability.scores  potential=potential_1 --o
 
 </div>
 

Deleted: grass-addons/grass7/raster/r.estimap.recreation/area_of_interest.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/bathing_water_quality.png
===================================================================
(Binary files differ)

Added: grass-addons/grass7/raster/r.estimap.recreation/corine_accounting_to_maes_land_classes.rules
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/corine_accounting_to_maes_land_classes.rules	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/corine_accounting_to_maes_land_classes.rules	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,40 @@
+1 = 1 Urban
+2 = 1 Urban
+3 = 1 Urban
+4 = 1 Urban
+5 = 1 Urban
+6 = 1 Urban
+7 = 1 Urban
+8 = 1 Urban
+9 = 1 Urban
+10 = 1 Urban
+11 = 1 Urban
+12 = 2 Cropland
+13 = 2 Cropland
+14 = 2 Cropland
+15 = 2 Cropland
+16 = 2 Cropland
+17 = 2 Cropland
+18 = 4 Grassland
+19 = 2 Cropland
+20 = 2 Cropland
+21 = 2 Cropland
+22 = 2 Cropland
+23 = 3 Woodland and forest
+24 = 3 Woodland and forest
+25 = 3 Woodland and forest
+26 = 4 Grassland
+27 = 5 Heathland and shrub
+28 = 5 Heathland and shrub
+29 = 3 Woodland and forest
+30 = 6 Sparsely vegetated land
+31 = 6 Sparsely vegetated land
+32 = 6 Sparsely vegetated land
+33 = 6 Sparsely vegetated land
+34 = 6 Sparsely vegetated land
+35 = 7 Wetland
+36 = 7 Wetland
+37 = 8 Marine
+38 = 8 Marine
+39 = 8 Marine
+ 

Deleted: grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006_suitability.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_legend.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/demand.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/distance_to_infrastructure.png
===================================================================
(Binary files differ)

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/Makefile
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/Makefile	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/Makefile	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,25 @@
+MODULE_TOPDIR = ../../..
+
+include $(MODULE_TOPDIR)/include/Make/Other.make
+include $(MODULE_TOPDIR)/include/Make/Python.make
+
+MODULES = __init__ accessibility colors components constants distance grassy_utilities labels main mobility normalisation panos spectrum supply_and_use utilities
+
+PGM = r.estimap.recreation
+LIBDIR = estimap_recreation
+ETCDIR = $(ETC)/$(PGM)/$(LIBDIR)
+
+PYFILES := $(patsubst %,$(ETCDIR)/%.py,$(MODULES))
+PYCFILES := $(patsubst %,$(ETCDIR)/%.pyc,$(MODULES))
+
+default: $(PYFILES) $(PYCFILES)
+
+$(ETCDIR):
+	$(MKDIR) $@
+
+$(ETCDIR)/%: % | $(ETCDIR)
+	$(INSTALL_DATA) $< $@
+
+install:
+	$(MKDIR) $(INST_DIR)/etc/$(PGM)
+	cp -r $(ETCDIR) $(INST_DIR)/etc/$(PGM)/$(LIBDIR)

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/__init__.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/__init__.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/__init__.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+# __all__ = ['colors', 'constants', 'labels', 'utilities']
+
+version = "0.3"

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/accessibility.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/accessibility.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/accessibility.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+from grass.exceptions import CalledModuleError
+from grass.pygrass.modules.shortcuts import general as g
+from grass.pygrass.modules.shortcuts import raster as r
+from grass.pygrass.modules.shortcuts import vector as v
+
+
+def artificial_accessibility_expression(artificial_proximity, roads_proximity):
+    """
+    Build an r.mapcalc compatible expression to compute accessibility to
+    artificial surfaces based on the following accessibility classification
+    rules for artificial surfaces:
+
+|-------------------+-------+------------+-------------+--------------+---------|
+| Anthropic \ Roads | < 500 | 500 - 1000 | 1000 - 5000 | 5000 - 10000 | > 10000 |
+|-------------------+-------+------------+-------------+--------------+---------|
+| < 500             | 1     | 1          | 2           | 3            | 4       |
+|-------------------+-------+------------+-------------+--------------+---------|
+| 500 - 1000        | 1     | 1          | 2           | 3            | 4       |
+|-------------------+-------+------------+-------------+--------------+---------|
+| 1000 - 5000       | 2     | 2          | 2           | 4            | 5       |
+|-------------------+-------+------------+-------------+--------------+---------|
+| 5000 - 10000      | 3     | 3          | 4           | 5            | 5       |
+|-------------------+-------+------------+-------------+--------------+---------|
+| > 10000           | 3     | 4          | 4           | 5            | 5       |
+|-------------------+-------+------------+-------------+--------------+---------|
+
+    Parameters
+    ----------
+    artificial :
+        Proximity to artificial surfaces
+
+    roads :
+        Proximity to roads
+
+    Returns
+    -------
+    expression
+        Valid r.mapcalc expression
+
+
+    Examples
+    --------
+    ...
+    """
+    expression = (
+        "if( {artificial} <= 2 && {roads} <= 2, 1,"
+        " \ \n if( {artificial} == 1 && {roads} == 3, 2,"
+        " \ \n if( {artificial} == 2 && {roads} == 3, 2,"
+        " \ \n if( {artificial} == 3 && {roads} <= 3, 2,"
+        " \ \n if( {artificial} <= 2 && {roads} == 4, 3,"
+        " \ \n if( {artificial} == 4 && {roads} == 2, 3,"
+        " \ \n if( {artificial} >= 4 && {roads} == 1, 3,"
+        " \ \n if( {artificial} <= 2 && {roads} == 5, 4,"
+        " \ \n if( {artificial} == 3 && {roads} == 4, 4,"
+        " \ \n if( {artificial} >= 4 && {roads} == 3, 4,"
+        " \ \n if( {artificial} == 5 && {roads} == 2, 4,"
+        " \ \n if( {artificial} >= 3 && {roads} == 5, 5,"
+        " \ \n if( {artificial} >= 4 && {roads} == 4, 5)))))))))))))"
+    )
+
+    expression = expression.format(
+        artificial=artificial_proximity, roads=roads_proximity
+    )
+    return expression
+
+
+def compute_artificial_accessibility(
+    artificial_proximity, roads_proximity, output_name=None
+):
+    """Compute artificial proximity
+
+    Parameters
+    ----------
+    artificial_proximity :
+        Artificial surfaces...
+
+    roads_proximity :
+        Road infrastructure
+
+    output_name :
+        Name to pass to temporary_filename() to create a temporary map name
+
+    Returns
+    -------
+    output :
+        ...
+
+    Examples
+    --------
+    ...
+    """
+    artificial = grass.find_file(name=artificial_proximity, element="cell")
+    if not artificial["file"]:
+        grass.fatal("Raster map {name} not found".format(name=artificial_proximity))
+
+    roads = grass.find_file(name=roads_proximity, element="cell")
+    if not roads["file"]:
+        grass.fatal("Raster map {name} not found".format(name=roads_proximity))
+
+    accessibility_expression = artificial_accessibility_expression(
+        artificial_proximity, roads_proximity
+    )
+    # temporary maps will be removed!
+    if output_name:
+        tmp_output = temporary_filename(filename=output_name)
+    else:
+        basename = "artificial_accessibility"
+        tmp_output = temporary_filename(filename=basename)
+
+    accessibility_equation = EQUATION.format(
+        result=tmp_output, expression=accessibility_expression
+    )
+
+    msg = "Equation for proximity to artificial areas: \n"
+    msg += accessibility_equation
+    grass.verbose(msg)
+
+    grass.verbose(_("Computing accessibility to artificial surfaces"))
+    grass.mapcalc(accessibility_equation, overwrite=True)
+
+    return tmp_output

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/colors.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/colors.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/colors.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,69 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+MOBILITY_COLORS = "wave"
+LANDCOVER_FRACTIONS_COLOR = "wave"
+
+SCORE_COLORS = """
+# http://colorbrewer2.org/?type=diverging&scheme=RdYlGn&n=11
+0.0% 165:0:38
+10.0% 215:48:39
+20.0% 244:109:67
+30.0% 253:174:97
+40.0% 254:224:139
+50.0% 255:255:191
+60.0% 217:239:139
+70.0% 166:217:106
+80.0% 102:189:99
+90.0% 26:152:80
+100.0% 0:104:55
+""".strip()
+
+POTENTIAL_COLORS = """
+# Cubehelix color table generated using:
+#   r.colors.cubehelix -dn ncolors=3 map=recreation_potential nrotations=0.33 gamma=1.5 hue=0.9 dark=0.3 output=recreation_potential.colors
+0.000% 55:29:66
+33.333% 55:29:66
+33.333% 157:85:132
+66.667% 157:85:132
+66.667% 235:184:193
+100.000% 235:184:193
+""".strip()
+
+OPPORTUNITY_COLORS = """
+# Cubehelix color table generated using:
+#   r.colors.cubehelix -dn ncolors=3 map=recreation_potential nrotations=0.33 gamma=1.5 hue=0.9 dark=0.3 output=recreation_potential.colors
+0.000% 55:29:66
+33.333% 55:29:66
+33.333% 157:85:132
+66.667% 157:85:132
+66.667% 235:184:193
+100.000% 235:184:193
+""".strip()
+
+SPECTRUM_COLORS = """
+# Cubehelix color table generated using:
+#   r.colors.cubehelix -dn ncolors=9 map=recreation_spectrum nrotations=0.33 gamma=1.5 hue=0.9 dark=0.3 output=recreation_spectrum.colors
+0.000% 55:29:66
+11.111% 55:29:66
+11.111% 79:40:85
+22.222% 79:40:85
+22.222% 104:52:102
+33.333% 104:52:102
+33.333% 131:67:118
+44.444% 131:67:118
+44.444% 157:85:132
+55.556% 157:85:132
+55.556% 180:104:145
+66.667% 180:104:145
+66.667% 202:128:159
+77.778% 202:128:159
+77.778% 221:156:175
+88.889% 221:156:175
+88.889% 235:184:193
+100.000% 235:184:193
+""".strip()

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/components.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/components.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/components.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+import grass.script as grass
+from grass.pygrass.modules.shortcuts import raster as r
+
+
+def append_map_to_component(raster, component_name, component_list):
+    """Appends raster map to given list of components
+
+    Parameters
+    ----------
+    raster :
+        Input raster map name
+
+    component_name :
+        Name of the component to add the raster map to
+
+    component_list :
+        List of raster maps to add the input 'raster' map
+
+    Returns
+    -------
+
+    Examples
+    --------
+    ...
+    """
+    component_list.append(raster)
+    msg = "Map {name} included in the '{component}' component"
+    msg = msg.format(name=raster, component=component_name)
+    grass.verbose(_(msg))
+
+
+def smooth_component(component, method, size):
+    """
+    component:
+
+    method:
+
+    size:
+    """
+    try:
+        if len(component) > 1:
+            for item in component:
+                smooth_map(item, method=method, size=size)
+        else:
+            smooth_map(component[0], method=method, size=size)
+
+    except IndexError:
+        grass.verbose(_("Index Error"))  # FIXME: some useful message... ?
+
+
+def classify_recreation_component(component, rules, output_name):
+    """
+    Recode an input recreation component based on given rules
+
+    To Do:
+
+    - Potentially, test range of input recreation component, i.e. ranging in
+      [0,1]
+
+    Parameters
+    ----------
+    component :
+        Name of input raster map
+
+    rules :
+        Rules for r.recode
+
+    output_name :
+        Name for output raster map
+
+    Returns
+    -------
+        Does not return any value
+
+    Examples
+    --------
+    ...
+
+    """
+    r.recode(input=component, rules="-", stdin=rules, output=output_name)

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/constants.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/constants.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/constants.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+CITATION_RECREATION_POTENTIAL = "Zulian (2014)"
+SPACY_PLUS = " + "
+EQUATION = "{result} = {expression}"  # basic equation for mapcalc
+
+THRESHHOLD_ZERO = 0
+THRESHHOLD_0001 = 0.0001
+
+CSV_EXTENSION = ".csv"
+COMMA = "comma"
+METHODS = "sum"
+EUCLIDEAN = "euclidean"
+# units='k'
+NEIGHBORHOOD_SIZE = 11  # this and below, required for neighborhood_function
+NEIGHBORHOOD_METHOD = "mode"
+
+WATER_PROXIMITY_CONSTANT = 1
+WATER_PROXIMITY_KAPPA = 30
+WATER_PROXIMITY_ALPHA = 0.008
+WATER_PROXIMITY_SCORE = 1
+BATHING_WATER_PROXIMITY_CONSTANT = 1
+BATHING_WATER_PROXIMITY_KAPPA = 5
+BATHING_WATER_PROXIMITY_ALPHA = 0.1101
+
+SUITABILITY_SCORES = """
+1:1:0:0
+2:2:0.1:0.1
+3:9:0:0
+10:10:1:1
+11:11:0.1:0.1
+12:13:0.3:0.3
+14:14:0.4:0.4
+15:17:0.5:0.5
+18:18:0.6:0.6
+19:20:0.3:0.3
+21:22:0.6:0.6
+23:23:1:1
+24:24:0.8:0.8
+25:25:1:1
+26:29:0.8:0.8
+30:30:1:1
+31:31:0.8:0.8
+32:32:0.7:0.7
+33:33:0:0
+34:34:0.8:0.8
+35:35:1:1
+36:36:0.8:0.8
+37:37:1:1
+38:38:0.8:0.8
+39:39:1:1
+40:42:1:1
+43:43:0.8:0.8
+44:44:1:1
+45:45:0.3:0.3
+""".strip()
+
+SUITABILITY_SCORES_LABELS = """
+1 thru 1 = 1 0
+2 thru 2 = 2 0.1
+3 thru 9 = 9 0
+10 thru 10 = 10 1
+11 thru 11 = 11 0.1
+12 thru 13 = 13 0.3
+14 thru 14 = 14 0.4
+15 thru 17 = 17 0.5
+18 thru 18 = 18 0.6
+19 thru 20 = 20 0.3
+21 thru 22 = 22 0.6
+23 thru 23 = 23 1
+24 thru 24 = 24 0.8
+25 thru 25 = 25 1
+26 thru 29 = 29 0.8
+30 thru 30 = 30 1
+31 thru 31 = 31 0.8
+32 thru 32 = 32 0.7
+33 thru 33 = 33 0
+34 thru 34 = 34 0.8
+35 thru 35 = 35 1
+36 thru 36 = 36 0.8
+37 thru 37 = 37 1
+38 thru 38 = 38 0.8
+39 thru 39 = 39 1
+40 thru 42 = 42 1
+43 thru 43 = 43 0.8
+44 thru 44 = 44 1
+45 thru 45 = 45 0.3
+""".strip()
+
+URBAN_ATLAS_CATEGORIES = """
+11100
+11200
+12100
+12200
+12300
+12400
+13100
+13200
+13300
+14100
+14200
+21100
+21200
+21300
+22100
+22200
+22300
+23100
+24100
+24200
+24300
+24400
+31100
+31200
+31300
+32100
+32200
+32300
+32400
+33100
+33200
+33300
+33400
+33500
+41100
+41200
+42100
+42200
+42300
+""".strip()
+
+URBAN_ATLAS_TO_MAES_NOMENCLATURE = """
+11100 = 1 Urban
+11210 = 1 Urban
+11220 = 1 Urban
+11230 = 1 Urban
+11240 = 1 Urban
+11300 = 1 Urban
+12100 = 1 Urban
+12210 = 1 Urban
+12220 = 1 Urban
+12230 = 1 Urban
+12300 = 1 Urban
+12400 = 1 Urban
+13100 = 1 Urban
+13300 = 1 Urban
+13400 = 1 Urban
+14100 = 1 Urban
+14200 = 1 Urban
+21000 = 2 Cropland
+22000 = 2 Cropland
+23000 = 2 Cropland
+25400 = 2 Cropland
+31000 = 3 Woodland and forest
+32000 = 3 Woodland and forest
+33000 = 3 Woodland and forest
+40000 = 4 Grassland
+50000 = 5 Heathland and shrub
+""".strip()
+
+# Recreation
+
+RECREATION_POTENTIAL_CATEGORIES = """
+0.0:0.2:1
+0.2:0.4:2
+0.4:*:3
+""".strip()
+# artificial_distance_categories=
+#'0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5'
+RECREATION_OPPORTUNITY_CATEGORIES = RECREATION_POTENTIAL_CATEGORIES
+HIGHEST_RECREATION_CATEGORY = 9
+
+# Mobility
+
+MOBILITY_CONSTANT = 1
+MOBILITY_COEFFICIENTS = {
+    0: (0.02350, 0.00102),
+    1: (0.02651, 0.00109),
+    2: (0.05120, 0.00098),
+    3: (0.10700, 0.00067),
+    4: (0.06930, 0.00057),
+}
+MOBILITY_SCORE = 52

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/distance.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/distance.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/distance.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,324 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import grass.script as grass
+from grass.exceptions import CalledModuleError
+from grass.pygrass.modules.shortcuts import general as g
+from grass.pygrass.modules.shortcuts import raster as r
+from grass.pygrass.modules.shortcuts import vector as v
+
+
+def build_distance_function(
+    constant, kappa, alpha, variable, score=None, suitability=None
+):
+    """
+    Build a valid `r.mapcalc` expression based on the following "space-time"
+    function:
+
+        ( {constant} + {kappa} ) / ( {kappa} + exp({alpha} * {variable}) )
+
+    Parameters
+    ----------
+    constant :
+        1
+
+    kappa :
+        A constant named 'K'
+
+    alpha :
+        A constant named 'a'
+
+    variable :
+        The main input variable: for 'attractiveness' it is 'distance', for
+        'flow' it is 'population'
+
+    score :
+        If 'score' is given, it is used as a multiplication factor to the base
+        equation.
+
+    suitability :
+        [ NOTE: this argument is yet unused!  It is meant to be used only after
+        successful integration of land suitability scores in
+        build_distance_function(). ]
+        If 'suitability' is given, it is used as a multiplication
+        factor to the base equation.
+
+    Returns
+    -------
+    function:
+        A valid `r.mapcalc` expression
+
+    Examples
+    --------
+    ..
+    """
+    numerator = "{constant} + {kappa}"
+    numerator = numerator.format(constant=constant, kappa=kappa)
+
+    denominator = "{kappa} + exp({alpha} * {variable})"
+    denominator = denominator.format(
+        kappa=kappa, alpha=alpha, variable=variable
+    )  # variable "formatted" by a caller function
+
+    function = " ( {numerator} ) / ( {denominator} )"
+    function = function.format(numerator=numerator, denominator=denominator)
+    grass.debug("Function without score: {f}".format(f=function))
+
+    if score:
+        function += " * {score}"  # need for float()?
+        function = function.format(score=score)
+    grass.debug(_("Function after adding 'score': {f}".format(f=function)))
+
+    # -------------------------------------------------------------------------
+    # if suitability:
+    #     function += " * {suitability}"  # FIXME : Confirm Correctness
+    #     function = function.format(suitability=suitability)
+    # msg = "Function after adding 'suitability': {f}".format(f=function)
+    # grass.debug(_(msg))
+    # -------------------------------------------------------------------------
+
+    return function
+
+
+def compute_attractiveness(
+    raster, metric, constant, kappa, alpha, mask=None, output_name=None
+):
+    """
+    Compute a raster map whose values follow an (euclidean) distance function
+    ( {constant} + {kappa} ) / ( {kappa} + exp({alpha} * {distance}) ), where:
+
+    Source: http://publications.jrc.ec.europa.eu/repository/bitstream/JRC87585/lb-na-26474-en-n.pd
+
+    Parameters
+    ----------
+    constant : 1
+
+    kappa :
+        A constant named 'K'
+
+    alpha :
+        A constant named 'a'
+
+    distance :
+        A distance map based on the input raster
+
+    score :
+        A score term to multiply the distance function
+
+    mask :
+        Optional raster MASK which is inverted to selectively exclude non-NULL
+        cells from distance related computations.
+
+    output_name :
+        Name to pass to temporary_filename() to create a temporary map name
+
+    Returns
+    -------
+    tmp_output :
+        A temporary proximity to features raster map.
+
+    Examples
+    --------
+    ...
+
+    """
+    distance_terms = [
+        str(raster),
+        str(metric),
+        "distance",
+        str(constant),
+        str(kappa),
+        str(alpha),
+    ]
+
+    if score:
+        grass.debug(_("Score for attractiveness equation: {s}".format(s=score)))
+        distance_terms += str(score)
+
+    # tmp_distance = temporary_filename('_'.join(distance_terms))
+    tmp_distance = temporary_filename(filename="_".join([raster, metric]))
+    r.grow_distance(
+        input=raster, distance=tmp_distance, metric=metric, quiet=True, overwrite=True
+    )
+
+    if mask:
+        msg = "Inverted masking to exclude non-NULL cells "
+        msg += "from distance related computations based on '{mask}'"
+        msg = msg.format(mask=mask)
+        grass.verbose(_(msg))
+        r.mask(raster=mask, flags="i", overwrite=True, quiet=True)
+
+    # FIXME: use a parameters dictionary, avoid conditionals
+    if score:
+        distance_function = build_distance_function(
+            constant=constant,
+            kappa=kappa,
+            alpha=alpha,
+            variable=tmp_distance,
+            score=score,
+        )
+
+    # FIXME: use a parameters dictionary, avoid conditionals
+    if not score:
+        distance_function = build_distance_function(
+            constant=constant, kappa=kappa, alpha=alpha, variable=tmp_distance
+        )
+
+    # temporary maps will be removed
+    if output_name:
+        tmp_distance_map = temporary_filename(filename=output_name)
+    else:
+        basename = "_".join([raster, "attractiveness"])
+        tmp_distance_map = temporary_filename(filename=basename)
+
+    distance_function = EQUATION.format(
+        result=tmp_distance_map, expression=distance_function
+    )
+    msg = "Distance function: {f}".format(f=distance_function)
+    grass.verbose(_(msg))
+    grass.mapcalc(distance_function, overwrite=True)
+
+    r.null(map=tmp_distance_map, null=0)  # Set NULLs to 0
+
+    compress_status = grass.read_command("r.compress", flags="g", map=tmp_distance_map)
+    grass.verbose(_("Compress status: {s}".format(s=compress_status)))  # REMOVEME
+
+    return tmp_distance_map
+
+
+def neighborhood_function(raster, method, size, distance_map):
+    """
+    Parameters
+    ----------
+    raster :
+        Name of input raster map for which to apply r.neighbors
+
+    method :
+        Method for r.neighbors
+
+    size :
+        Size for r.neighbors
+
+    distance :
+        A distance map
+
+    Returns
+    -------
+    filtered_output :
+        A neighborhood filtered raster map
+
+    Examples
+    --------
+    ...
+    """
+    r.null(map=raster, null=0)  # Set NULLs to 0
+
+    neighborhood_output = distance_map + "_" + method
+    msg = "Neighborhood operator '{method}' and size '{size}' for map '{name}'"
+    msg = msg.format(method=method, size=size, name=neighborhood_output)
+    grass.verbose(_(msg))
+
+    r.neighbors(
+        input=raster,
+        output=neighborhood_output,
+        method=method,
+        size=size,
+        overwrite=True,
+    )
+
+    scoring_function = "{neighborhood} * {distance}"
+    scoring_function = scoring_function.format(
+        neighborhood=neighborhood_output, distance=distance_map
+    )
+
+    filtered_output = distance_map
+    filtered_output += "_" + method + "_" + str(size)
+
+    neighborhood_function = EQUATION.format(
+        result=filtered_output, expression=scoring_function
+    )
+    # ---------------------------------------------------------------
+    grass.debug(_("Expression: {e}".format(e=neighborhood_function)))
+    # ---------------------------------------------------------------
+    grass.mapcalc(neighborhood_function, overwrite=True)
+
+    # tmp_distance_map = filtered_output
+
+    # r.compress(distance_map, flags='g')
+
+    return filtered_output
+
+
+def compute_artificial_proximity(raster, distance_categories, output_name=None):
+    """
+    Compute proximity to artificial surfaces
+
+    1. Distance to features
+    2. Classify distances
+
+    Parameters
+    ----------
+    raster :
+        Name of input raster map
+
+    distance_categories :
+        Category rules to recode the input distance map
+
+    output_name :
+        Name to pass to temporary_filename() to create a temporary map name
+
+    Returns
+    -------
+    tmp_output :
+        Name of the temporary output map for internal, in-script, re-use
+
+    Examples
+    --------
+    ...
+    """
+    artificial_distances = temporary_filename(filename=raster)
+
+    grass.run_command(
+        "r.grow.distance",
+        input=raster,
+        distance=artificial_distances,
+        metric=EUCLIDEAN,
+        quiet=True,
+        overwrite=True,
+    )
+
+    # temporary maps will be removed
+    if output_name:
+        tmp_output = temporary_filename(filename=output_name)
+        grass.debug(_("Pre-defined output map name {name}".format(name=tmp_output)))
+
+    else:
+        tmp_output = temporary_filename(filename="artificial_proximity")
+        grass.debug(_("Hardcoded temporary map name {name}".format(name=tmp_output)))
+
+    msg = "Computing proximity to '{mapname}'"
+    msg = msg.format(mapname=raster)
+    grass.verbose(_(msg))
+    grass.run_command(
+        "r.recode",
+        input=artificial_distances,
+        output=tmp_output,
+        rules=distance_categories,
+        overwrite=True,
+    )
+
+    output = grass.find_file(name=tmp_output, element="cell")
+    if not output["file"]:
+        grass.fatal("Proximity map {name} not created!".format(name=raster))
+    #     else:
+    #         g.message(_("Output map {name}:".format(name=tmp_output)))
+
+    return tmp_output

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/grassy_utilities.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/grassy_utilities.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/grassy_utilities.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,532 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import atexit
+import os
+
+import grass.script as grass
+from grass.exceptions import CalledModuleError
+from grass.pygrass.modules.shortcuts import general as g
+from grass.pygrass.modules.shortcuts import raster as r
+from grass.pygrass.modules.shortcuts import vector as v
+
+from .colors import SCORE_COLORS
+from .constants import CITATION_RECREATION_POTENTIAL
+
+
+def run(cmd, **kwargs):
+    """Pass required arguments to grass commands (?)"""
+    grass.run_command(cmd, quiet=True, **kwargs)
+
+
+def remove_map(map_name):
+    """ Remove the provided map """
+    grass.verbose("Removing %s" % map_name)
+    g.remove(flags="f", type=("raster", "vector"), name=map_name, quiet=True)
+
+
+def remove_map_at_exit(map_name):
+    """ Remove the provided map when the program exits """
+    atexit.register(lambda: remove_map(map_name))
+
+
+def remove_files_at_exit(filename):
+    """ Remove the specified file when the program exits """
+    atexit.register(lambda: os.unlink(filename))
+
+
+def temporary_filename(filename=None):
+    """Returns a temporary filename using grass.script.tempfile() and
+    grass.script.basename()
+
+    Parameters
+    ----------
+    filename :
+        Name for a file
+
+    Returns
+    -------
+    temporary_filename :
+        A temporary file name
+
+    Examples
+    --------
+    >>> temporary_filename(potential)
+    tmp.SomeTemporaryString.potential
+    """
+    temporary_absolute_filename = grass.tempfile()
+    temporary_filename = "tmp." + grass.basename(temporary_absolute_filename)
+    if filename:
+        temporary_filename = temporary_filename + "." + str(filename)
+    return temporary_filename
+
+
+def remove_temporary_maps():
+    """Clean up temporary maps"""
+
+    # get list of temporary maps
+    # temporary_raster_maps = grass.list_strings(
+    #     type="raster", pattern="tmp.{pid}*".format(pid=os.getpid())
+    # )
+
+    # # remove temporary maps
+    # if temporary_raster_maps:
+    g.message("Removing temporary maps")
+    g.remove(
+        flags="f",
+        type="raster",
+        pattern="tmp.{pid}*".format(pid=os.getpid()),
+        quiet=True,
+    )
+
+    # # remove MASK ? FIXME
+    # if grass.find_file(name='MASK', element='cell')['file']:
+    #     r.mask(flags='r', verbose=True)
+
+
+def string_to_file(string, filename=None):
+    """Split series of strings separated by comma in lines and write as an
+    ASCII file
+
+    Parameters
+    ----------
+    string :
+        A string where commas will be replaced by a newline
+
+    name :
+        A string for temporary_filename() to create a temporary file name 'filename'
+
+    Returns
+    -------
+    filename :
+        Name of the ASCII file into where the transformed string is written
+
+    Examples
+    --------
+
+    """
+    string = string.split(",")
+    string = "\n".join(string)
+    # string = string.splitlines()
+    msg = "String split in lines: {s}".format(s=string)
+    grass.debug(_(msg))
+
+    # # Use a file-like object instead?
+    # import tempfile
+    # ascii_file = tempfile.TemporaryFile()
+
+    try:
+        ascii_file = open(filename, "w")
+        ascii_file.writelines(string)
+        # ascii_file.seek(0)  # in case of a file-like object
+
+    # if DEBUG, then do:
+    # for line in ascii_file:
+    #     grass.debug(_(line.rstrip()))
+
+    except IOError as error:
+        print("IOError :", error)
+        return
+
+    finally:
+        ascii_file.close()
+        return filename  # how would that work with a file-like object?
+        # Will be removed right after `.close()` -- How to possibly re-use it
+        # outside the function?
+        # Wrap complete main() in a `try` statement?
+
+
+def get_univariate_statistics(raster):
+    """
+    Return and print basic univariate statistics of the input raster map
+
+    Parameters
+    ----------
+    raster :
+        Name of input raster map
+
+    Returns
+    -------
+    univariate :
+        Univariate statistics min, mean, max and variance of the input raster
+        map
+
+    Example
+    -------
+    ...
+    """
+    univariate = grass.parse_command("r.univar", flags="g", map=raster)
+    minimum = univariate["min"]
+    mean = univariate["mean"]
+    maximum = univariate["max"]
+    variance = univariate["variance"]
+    msg = "min {mn} | mean {avg} | max {mx} | variance {v}"
+    msg = msg.format(mn=minimum, avg=mean, mx=maximum, v=variance)
+    grass.verbose(_(msg))
+    return univariate
+
+
+def recode_map(raster, rules, colors, output):
+    """Scores a raster map based on a set of category recoding rules.
+
+    This is a wrapper around r.recode
+
+    Parameters
+    ----------
+    raster :
+        Name of input raster map
+
+    rules :
+        Rules for r.recode
+
+    colors :
+        Color rules for r.colors
+
+    output :
+        Name of output raster map
+
+    Returns
+    -------
+        Does not return any value
+
+    Examples
+    --------
+    ...
+    """
+    msg = "Setting NULL cells in {name} map to 0"
+    msg = msg.format(name=raster)
+    grass.debug(_(msg))
+
+    # ------------------------------------------
+    r.null(map=raster, null=0)  # Set NULLs to 0
+    msg = "To Do: confirm if setting the '{raster}' map's NULL cells to 0 is right"
+    msg = msg.format(raster=raster)
+    grass.debug(_(msg))
+    # Is this right?
+    # ------------------------------------------
+
+    r.recode(input=raster, rules=rules, output=output)
+
+    r.colors(map=output, rules="-", stdin=SCORE_COLORS, quiet=True)
+
+    grass.verbose(_("Scored map {name}:".format(name=raster)))
+
+
+def float_to_integer(double):
+    """Converts an FCELL or DCELL raster map into a CELL raster map
+
+    Parameters
+    ----------
+    double :
+            An 'FCELL' or 'DCELL' type raster map
+
+    Returns
+    -------
+    This function does not return any value
+
+    Examples
+    --------
+    ..
+    """
+    expression = "int({double})"
+    expression = expression.format(double=double)
+    equation = EQUATION.format(result=double, expression=expression)
+    r.mapcalc(equation)
+
+
+def update_meta(raster, title, timestamp=None):
+    """
+    Update metadata of given raster map
+
+    Parameters
+    ----------
+    raster :
+        ...
+
+    title :
+        ...
+
+    Returns
+    -------
+        Does not return any value
+
+    Examples
+    --------
+    ...
+    """
+    history = "\n" + CITATION_RECREATION_POTENTIAL
+    description_string = "Recreation {raster} map"
+    description = description_string.format(raster=raster)
+
+    title = "{title}".format(title=title)
+    units = "Meters"
+
+    source1 = "Source 1"
+    source2 = "Source 2"
+
+    r.support(
+        map=raster,
+        title=title,
+        description=description,
+        units=units,
+        source1=source1,
+        source2=source2,
+        history=history,
+    )
+
+    if timestamp:
+        r.timestamp(map=raster, date=timestamp)
+
+
+def export_map(input_name, title, categories, colors, output_name, timestamp):
+    """
+    Export a raster map by renaming the (temporary) raster map name
+    'input_name' to the requested output raster map name 'output_name'.
+    This function is (mainly) used to export either of the intermediate
+    recreation 'potential' or 'opportunity' maps.
+
+    Parameters
+    ----------
+    raster :
+        Input raster map name
+
+    title :
+        Title for the output raster map
+
+    categories :
+        Categories and labels for the output raster map
+
+    colors :
+        Colors for the output raster map
+
+    output_name :
+        Output raster map name
+
+    Returns
+    -------
+    output_name :
+        This function will return the requested 'output_name'
+
+    Examples
+    --------
+    ..
+    """
+    finding = grass.find_file(name=input_name, element="cell")
+    if not finding["file"]:
+        grass.fatal("Raster map {name} not found".format(name=input_name))
+
+    # inform
+    msg = "\nOutputting '{raster}' map\n"
+    msg = msg.format(raster=input_name)
+    grass.verbose(_(msg))
+
+    # get categories and labels
+    temporary_raster_categories_map = temporary_filename("categories_of_" + input_name)
+    raster_category_labels = string_to_file(
+        string=categories, filename=temporary_raster_categories_map
+    )
+
+    # add ascii file to removal list
+    remove_files_at_exit(raster_category_labels)
+
+    # apply categories and description
+    r.category(map=input_name, rules=raster_category_labels, separator=":")
+
+    # update meta and colors
+    update_meta(input_name, title, timestamp)
+    r.colors(map=input_name, rules="-", stdin=colors, quiet=True)
+    # rename to requested output name
+    g.rename(raster=(input_name, output_name), quiet=True)
+
+    return output_name
+
+
+def get_raster_statistics(map_one, map_two, separator, flags):
+    """
+    Parameters
+    ----------
+    map_one :
+        First map as input to `r.stats`
+
+    map_two :
+        Second map as input to `r.stats`
+
+    separator :
+        Character to use as separator in `r.stats`
+
+    flags :
+        Flags for `r.stats`
+
+    Returns
+    -------
+    dictionary :
+        A nested dictionary that holds categorical statistics for both maps
+        'map_one' and 'map_two'.
+
+        - The 'outer_key' is the raster category _and_ label of 'map_one'.
+        - The 'inner_key' is the raster map category of 'map_two'.
+        - The 'inner_value' is the list of statistics for map two, as returned
+          for `r.stats`.
+
+        Example of a nested dictionary:
+
+        {(u'3',
+            u'Region 3'):
+            {u'1': [
+                u'355.747658',
+                u'6000000.000000',
+                u'6',
+                u'6.38%'],
+            u'3': [
+                u'216304.146140',
+                u'46000000.000000',
+                u'46',
+                u'48.94%'],
+            u'2': [
+                u'26627.415787',
+                u'46000000.000000',
+                u'46',
+                u'48.94%']}}
+    """
+
+    statistics = grass.read_command(
+        "r.stats",
+        input=(map_one, map_two),
+        output="-",
+        flags=flags,
+        separator=separator,
+        quiet=True,
+    )
+    statistics = statistics.split("\n")[:-1]
+
+    dictionary = dict()
+
+    # build a nested dictionary where:
+    for row in statistics:
+        row = row.split("|")
+        outer_key = (row[0], row[1])
+        inner_key = row[2]
+        inner_value = row[3:]
+        inner_dictionary = {inner_key: inner_value}
+        try:
+            dictionary[outer_key][inner_key] = inner_value
+        except KeyError:
+            dictionary[outer_key] = {inner_key: inner_value}
+
+    return dictionary
+
+
+def smooth_map(raster, method, size):
+    """
+    Parameters
+    ----------
+    raster :
+
+    method :
+
+    size :
+
+    Returns
+    -------
+
+    Examples
+    --------
+    """
+    r.neighbors(
+        input=raster,
+        output=raster,
+        method=method,
+        size=size,
+        overwrite=True,
+        quiet=True,
+    )
+
+
+# This function is not used.  Review and Fix or Remove!
+def update_vector(vector, raster, methods, column_prefix):
+    """
+
+    Parameters
+    ----------
+    vector :
+        Vector map whose attribute table to update with results of the
+        v.rast.stats call
+
+    raster :
+        Source raster map for statistics
+
+    methods :
+        Descriptive statistics for the `v.rast.stats` call
+
+    column_prefix :
+        Prefix for the names of the columns created by the `v.rast.stats` call
+
+    Returns
+    -------
+        This helper function executes `v.rast.stats`. It does not return any
+        value.
+
+    Examples
+    --------
+    ..
+    """
+    run(
+        "v.rast.stats",
+        map=vector,
+        flags="c",
+        raster=raster,
+        method=methods,
+        column_prefix=column_prefix,
+        overwrite=True,
+    )
+    # grass.verbose(_("Updating vector map '{v}'".format(v=vector)))
+
+
+def raster_to_vector(raster, vector, type):
+    """Converts a raster to a vector map
+
+    Parameters
+    ----------
+
+    raster :
+        Name of the input raster map
+
+    vector :
+        Name for the output vector map
+
+    type :
+        Type for the output vector map
+
+    Returns
+    -------
+
+    Examples
+    --------
+    ..
+    """
+    r.to_vect(input=flow_in_category, output=flow_in_category, type="area", quiet=True)
+
+    # Value is the ecosystem type
+    v.db_renamecolumn(map=flow_in_category, column=("value", "ecosystem"))
+
+    # New column for flow values
+    addcolumn_string = flow_column_name + " double"
+    v.db_addcolumn(map=flow_in_category, columns=addcolumn_string)
+
+    # The raster category 'label' is the 'flow'
+    v.db_update(map=flow_in_category, column="flow", query_column="label")
+    v.db_dropcolumn(map=flow_in_category, columns="label")
+
+    # Update the aggregation raster categories
+    v.db_addcolumn(map=flow_in_category, columns="aggregation_id int")
+    v.db_update(map=flow_in_category, column="aggregation_id", value=category)
+
+    v.colors(map=flow_in_category, raster=flow_in_category, quiet=True)

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/labels.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/labels.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/labels.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+#
+## FIXME -- No hardcodings please.
+#
+
+POTENTIAL_CATEGORY_LABELS = """
+1:Low
+2:Moderate
+3:High
+""".strip()
+
+OPPORTUNITY_CATEGORY_LABELS = """
+1:Far
+2:Midrange
+3:Near
+""".strip()
+
+SPECTRUM_CATEGORY_LABELS = """
+1:Low provision (far)
+2:Low provision (midrange)
+3:Low provision (near)
+4:Moderate provision (far)
+5:Moderate provision (midrange)
+6:Moderate provision (near)
+7:High provision (far)
+8:High provision (midrange)
+9:High provision (near)
+""".strip()
+
+SPECTRUM_DISTANCE_CATEGORY_LABELS = """
+1:0 to 1 km
+2:1 to 2 km
+3:2 to 3 km
+4:3 to 4 km
+5:>4 km
+""".strip()

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/main.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/main.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/main.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,976 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+import atexit
+
+# constants
+
+from estimap_recreation.labels import POTENTIAL_CATEGORY_LABELS
+from estimap_recreation.labels import OPPORTUNITY_CATEGORY_LABELS
+from estimap_recreation.labels import SPECTRUM_CATEGORY_LABELS
+from estimap_recreation.labels import SPECTRUM_DISTANCE_CATEGORY_LABELS
+from estimap_recreation.colors import POTENTIAL_COLORS
+from estimap_recreation.colors import OPPORTUNITY_COLORS
+from estimap_recreation.colors import SPECTRUM_COLORS
+
+# utilities
+
+from estimap_recreation.grassy_utilities import *
+from estimap_recreation.utilities import *
+
+# algorithms
+
+from estimap_recreation.distance import *
+from estimap_recreation.normalisation import *
+from estimap_recreation.accessibility import *
+from estimap_recreation.spectrum import *
+from estimap_recreation.components import *
+from estimap_recreation.mobility import *
+from estimap_recreation.supply_and_use import *
+
+def main():
+    """
+    Main program
+    """
+    atexit.register(remove_temporary_maps)
+
+    """Flags and Options"""
+    options, flags = grass.parser()
+
+    # Flags that are not being used
+    info = flags["i"]
+    save_temporary_maps = flags["s"]
+
+    # Flags that are being used
+    average_filter = flags["f"]
+    landuse_extent = flags["e"]
+    print_only = flags["p"]
+
+    timestamp = options["timestamp"]
+
+    metric = options["metric"]
+    units = options["units"]
+    if len(units) > 1:
+        units = units.split(",")
+
+    """names for input, output, output suffix, options"""
+
+    mask = options["mask"]
+
+    """
+    following some hard-coded names -- review and remove!
+    """
+
+    land = options["land"]
+    land_component_map_name = temporary_filename(filename="land_component")
+
+    water = options["water"]
+    water_component_map_name = temporary_filename(filename="water_component")
+
+    natural = options["natural"]
+    natural_component_map_name = temporary_filename(filename="natural_component")
+
+    urban = options["urban"]
+    urban_component_map = "urban_component"
+
+    infrastructure = options["infrastructure"]
+    infrastructure_component_map_name = temporary_filename(filename="infrastructure_component")
+
+    recreation = options["recreation"]
+    recreation_component_map_name = temporary_filename(filename="recreation_component")
+
+    """Land components"""
+
+    landuse = options["landuse"]
+    if landuse:
+        # Check datatype: a land use map should be categorical, i.e. of type CELL
+        landuse_datatype = grass.raster.raster_info(landuse)["datatype"]
+        if landuse_datatype != "CELL":
+            msg = (
+                "The '{landuse}' input map "
+                "should be a categorical one "
+                "and of type 'CELL'. "
+                "Perhaps you meant to use the 'land' input option instead?"
+            )
+            grass.fatal(_(msg.format(landuse=landuse)))
+
+    suitability_map_name = temporary_filename(filename="suitability")
+    suitability_scores = options["suitability_scores"]
+
+    if landuse and suitability_scores and ":" not in suitability_scores:
+        msg = "Suitability scores from file: {scores}."
+        msg = msg.format(scores=suitability_scores)
+        grass.verbose(_(msg))
+
+    if landuse and not suitability_scores:
+        msg = "Using internal rules to score land use classes in '{map}'"
+        msg = msg.format(map=landuse)
+        grass.warning(_(msg))
+
+        temporary_suitability_map_name = temporary_filename(filename=suitability_map_name)
+        suitability_scores = string_to_file(
+            SUITABILITY_SCORES, filename=temporary_suitability_map_name
+        )
+        remove_files_at_exit(suitability_scores)
+
+    if landuse and suitability_scores and ":" in suitability_scores:
+        msg = "Using provided string of rules to score land use classes in {map}"
+        msg = msg.format(map=landuse)
+        grass.verbose(_(msg))
+        temporary_suitability_map_name = temporary_filename(filename=suitability_map_name)
+        suitability_scores = string_to_file(
+            suitability_scores, filename=temporary_suitability_map_name
+        )
+        remove_files_at_exit(suitability_scores)
+
+    # FIXME -----------------------------------------------------------------
+
+    # Use one landcover input if supply is requested
+    # Use one set of land cover reclassification rules
+
+    landcover = options["landcover"]
+
+    if not landcover:
+        landcover = landuse
+        msg = "Land cover map 'landcover' not given. "
+        msg += "Attempt to use the '{landuse}' map to derive areal statistics"
+        msg = msg.format(landuse=landuse)
+        grass.verbose(_(msg))
+
+    maes_ecosystem_types = "maes_ecosystem_types"
+    maes_ecosystem_types_scores = "maes_ecosystem_types_scores"
+    landcover_reclassification_rules = options["land_classes"]
+
+    # if 'land_classes' is a file
+    if (
+        landcover
+        and landcover_reclassification_rules
+        and ":" not in landcover_reclassification_rules
+    ):
+        msg = "Land cover reclassification rules from file: {rules}."
+        msg = msg.format(rules=landcover_reclassification_rules)
+        grass.verbose(_(msg))
+
+    # if 'land_classes' not given
+    if landcover and not landcover_reclassification_rules:
+
+        # if 'landcover' is not the MAES land cover,
+        # then use internal reclassification rules
+        # how to test:
+        # 1. landcover is not a "MAES" land cover
+        # 2. landcover is an Urban Atlas one?
+
+        msg = "Using internal rules to reclassify the '{map}' map"
+        msg = msg.format(map=landcover)
+        grass.verbose(_(msg))
+
+        temporary_maes_ecosystem_types = temporary_filename(filename=maes_ecosystem_types)
+        landcover_reclassification_rules = string_to_file(
+            URBAN_ATLAS_TO_MAES_NOMENCLATURE, filename=maes_ecosystem_types
+        )
+        remove_files_at_exit(landcover_reclassification_rules)
+
+        # if landcover is a "MAES" land cover, no need to reclassify!
+
+    if (
+        landuse
+        and landcover_reclassification_rules
+        and ":" in landcover_reclassification_rules
+    ):
+        msg = "Using provided string of rules to reclassify the '{map}' map"
+        msg = msg.format(map=landcover)
+        grass.verbose(_(msg))
+        temporary_maes_land_classes = temporary_filename(filename=maes_land_classes)
+        landcover_reclassification_rules = string_to_file(
+            landcover_reclassification_rules, filename=maes_land_classes
+        )
+        remove_files_at_exit(landcover_reclassification_rules)
+
+    # FIXME -----------------------------------------------------------------
+
+    """Water components"""
+
+    lakes = options["lakes"]
+    lakes_coefficients = options["lakes_coefficients"]
+    lakes_proximity_map_name = "lakes_proximity"
+    coastline = options["coastline"]
+    coast_proximity_map_name = "coast_proximity"
+    coast_geomorphology = options["coast_geomorphology"]
+    # coast_geomorphology_coefficients = options['geomorphology_coefficients']
+    coast_geomorphology_map_name = "coast_geomorphology"
+    bathing_water = options["bathing_water"]
+    bathing_water_coefficients = options["bathing_coefficients"]
+    bathing_water_proximity_map_name = "bathing_water_proximity"
+
+    """Natural components"""
+
+    protected = options["protected"]
+    protected_scores = options["protected_scores"]
+    protected_areas_map_name = "protected_areas"
+
+    """Artificial areas"""
+
+    artificial = options["artificial"]
+    artificial_proximity_map_name = "artificial_proximity"
+    artificial_distance_categories = options["artificial_distances"]
+
+    roads = options["roads"]
+    roads_proximity_map_name = "roads_proximity"
+    roads_distance_categories = options["roads_distances"]
+
+    artificial_accessibility_map_name = "artificial_accessibility"
+
+    """Devaluation"""
+
+    devaluation = options["devaluation"]
+
+    """Aggregational boundaries"""
+
+    base = options["base"]
+    base_vector = options["base_vector"]
+    aggregation = options["aggregation"]
+
+    """Population"""
+
+    population = options["population"]
+    if population:
+        population_ns_resolution = grass.raster_info(population)["nsres"]
+        population_ew_resolution = grass.raster_info(population)["ewres"]
+
+    """Outputs"""
+
+    potential_title = "Recreation potential"
+    recreation_potential = options["potential"]  # intermediate / output
+    recreation_potential_map_name = temporary_filename(filename="recreation_potential")
+
+    opportunity_title = "Recreation opportunity"
+    recreation_opportunity = options["opportunity"]
+    recreation_opportunity_map_name = "recreation_opportunity"
+
+    spectrum_title = "Recreation spectrum"
+    # if options['spectrum']:
+    recreation_spectrum = options["spectrum"]  # output
+    # else:
+    #     recreation_spectrum = 'recreation_spectrum'
+    # recreation_spectrum_component_map_name =
+    #       temporary_filename(filename='recreation_spectrum_component_map')
+
+    spectrum_distance_categories = options["spectrum_distances"]
+    if ":" in spectrum_distance_categories:
+        temporary_recreation_spectrum = temporary_filename(filename=recreation_spectrum)
+        spectrum_distance_categories = string_to_file(
+            spectrum_distance_categories,
+            filename=temporary_recreation_spectrum
+        )
+        remove_files_at_exit(spectrum_distance_categories)
+
+    highest_spectrum = "highest_recreation_spectrum"
+    crossmap = "crossmap"  # REMOVEME
+
+    demand = options["demand"]
+    unmet_demand = options["unmet"]
+
+    flow = options["flow"]
+    flow_map_name = "flow"
+
+    supply = options["supply"]  # use as CSV filename prefix
+    use = options["use"]  # use as CSV filename prefix
+
+    """ First, care about the computational region"""
+
+    if mask:
+        msg = "Masking NULL cells based on '{mask}'".format(mask=mask)
+        grass.verbose(_(msg))
+        r.mask(raster=mask, overwrite=True, quiet=True)
+
+    if landuse_extent:
+        grass.use_temp_region()  # to safely modify the region
+        g.region(flags="p", raster=landuse)  # Set region to 'mask'
+        msg = "|! Computational resolution matched to {raster}"
+        msg = msg.format(raster=landuse)
+        g.message(_(msg))
+
+    """Land Component
+            or Suitability of Land to Support Recreation Activities (SLSRA)"""
+
+    land_component = []  # a list, use .extend() wherever required
+
+    if land:
+
+        land_component = land.split(",")
+
+    if landuse and suitability_scores:
+
+        msg = "Deriving land suitability from '{landuse}' "
+        msg += "based on rules described in file '{rules}'"
+        grass.verbose(msg.format(landuse=landuse, rules=suitability_scores))
+
+        # suitability is the 'suitability_map_name'
+        recode_map(
+            raster=landuse,
+            rules=suitability_scores,
+            colors=SCORE_COLORS,
+            output=suitability_map_name,
+        )
+
+        append_map_to_component(
+            raster=suitability_map_name,
+            component_name="land",
+            component_list=land_component,
+        )
+
+    """Water Component"""
+
+    water_component = []
+    water_components = []
+
+    if water:
+
+        water_component = water.split(",")
+        msg = "Water component includes currently: {component}"
+        msg = msg.format(component=water_component)
+        grass.debug(_(msg))
+        # grass.verbose(_(msg))
+
+    if lakes:
+
+        if lakes_coefficients:
+            metric, constant, kappa, alpha, score = get_coefficients(lakes_coefficients)
+
+        lakes_proximity = compute_attractiveness(
+            raster=lakes,
+            metric=EUCLIDEAN,
+            constant=constant,
+            kappa=kappa,
+            alpha=alpha,
+            score=score,
+            mask=lakes,
+        )
+
+        append_map_to_component(
+            raster=lakes_proximity,
+            component_name="water",
+            component_list=water_components,
+        )
+
+    if coastline:
+
+        coast_proximity = compute_attractiveness(
+            raster=coastline,
+            metric=EUCLIDEAN,
+            constant=WATER_PROXIMITY_CONSTANT,
+            alpha=WATER_PROXIMITY_ALPHA,
+            kappa=WATER_PROXIMITY_KAPPA,
+            score=WATER_PROXIMITY_SCORE,
+        )
+
+        append_map_to_component(
+            raster=coast_proximity,
+            component_name="water",
+            component_list=water_components,
+        )
+
+    if coast_geomorphology:
+
+        try:
+
+            if not coastline:
+                msg = "The coastline map is required in order to "
+                msg += "compute attractiveness based on the "
+                msg += "coast geomorphology raster map"
+                msg = msg.format(c=water_component)
+                grass.fatal(_(msg))
+
+        except NameError:
+            grass.fatal(_("No coast proximity"))
+
+        coast_attractiveness = neighborhood_function(
+            raster=coast_geomorphology,
+            method=NEIGHBORHOOD_METHOD,
+            size=NEIGHBORHOOD_SIZE,
+            distance_map=coast_proximity,
+        )
+
+        append_map_to_component(
+            raster=coast_attractiveness,
+            component_name="water",
+            component_list=water_components,
+        )
+
+    if bathing_water:
+
+        if bathing_water_coefficients:
+            metric, constant, kappa, alpha = get_coefficients(
+                bathing_water_coefficients
+            )
+
+        bathing_water_proximity = compute_attractiveness(
+            raster=bathing_water,
+            metric=EUCLIDEAN,
+            constant=constant,
+            kappa=kappa,
+            alpha=alpha,
+        )
+
+        append_map_to_component(
+            raster=bathing_water_proximity,
+            component_name="water",
+            component_list=water_components,
+        )
+
+    # merge water component related maps in one list
+    water_component += water_components
+
+    """Natural Component"""
+
+    natural_component = []
+    natural_components = []
+
+    if natural:
+
+        natural_component = natural.split(",")
+
+    if protected:
+        msg = "Scoring protected areas '{protected}' based on '{rules}'"
+        grass.verbose(_(msg.format(protected=protected, rules=protected_scores)))
+
+        protected_areas = protected_areas_map_name
+
+        recode_map(
+            raster=protected,
+            rules=protected_scores,
+            colors=SCORE_COLORS,
+            output=protected_areas,
+        )
+
+        append_map_to_component(
+            raster=protected_areas,
+            component_name="natural",
+            component_list=natural_components,
+        )
+
+    # merge natural resources component related maps in one list
+    natural_component += natural_components
+
+    """ Normalize land, water, natural inputs
+    and add them to the recreation potential component"""
+
+    recreation_potential_component = []
+
+    if land_component:
+
+        for dummy_index in land_component:
+
+            # remove 'land_map' from 'land_component'
+            # process and add it back afterwards
+            land_map = land_component.pop(0)
+
+            """
+            This section sets NULL cells to 0.
+            Because `r.null` operates on the complete input raster map,
+            manually subsetting the input map is required.
+            """
+            suitability_map = temporary_filename(filename=land_map)
+            subset_land = EQUATION.format(result=suitability_map, expression=land_map)
+            r.mapcalc(subset_land)
+
+            grass.debug(_("Setting NULL cells to 0"))  # REMOVEME ?
+            r.null(map=suitability_map, null=0)  # Set NULLs to 0
+
+            msg = "\nAdding land suitability map '{suitability}' "
+            msg += "to 'Recreation Potential' component\n"
+            msg = msg.format(suitability=suitability_map)
+            grass.verbose(_(msg))
+
+            # add 'suitability_map' to 'land_component'
+            land_component.append(suitability_map)
+
+    if len(land_component) > 1:
+        grass.verbose(_("\nNormalize 'Land' component\n"))
+        zerofy_and_normalise_component(
+            land_component, THRESHHOLD_ZERO, land_component_map_name
+        )
+        recreation_potential_component.extend(land_component)
+    else:
+        recreation_potential_component.extend(land_component)
+
+    if land_component and average_filter:
+        smooth_component(land_component, method="average", size=7)
+
+    remove_map_at_exit(land_component)
+
+    if len(water_component) > 1:
+        grass.verbose(_("\nNormalize 'Water' component\n"))
+        zerofy_and_normalise_component(
+            water_component, THRESHHOLD_ZERO, water_component_map_name
+        )
+        recreation_potential_component.append(water_component_map_name)
+    else:
+        recreation_potential_component.extend(water_component)
+
+    remove_map_at_exit(water_component_map_name)
+
+    if len(natural_component) > 1:
+        grass.verbose(_("\nNormalize 'Natural' component\n"))
+        zerofy_and_normalise_component(
+            components=natural_component,
+            threshhold=THRESHHOLD_ZERO,
+            output_name=natural_component_map_name,
+        )
+        recreation_potential_component.append(natural_component_map_name)
+    else:
+        recreation_potential_component.extend(natural_component)
+
+    if natural_component and average_filter:
+        smooth_component(natural_component, method="average", size=7)
+
+    remove_map_at_exit(natural_component_map_name)
+
+    """ Recreation Potential [Output] """
+
+    tmp_recreation_potential = temporary_filename(filename=recreation_potential_map_name)
+
+    msg = "Computing intermediate 'Recreation Potential' map: '{potential}'"
+    grass.verbose(_(msg.format(potential=tmp_recreation_potential)))
+    grass.debug(_("Maps: {maps}".format(maps=recreation_potential_component)))
+
+    zerofy_and_normalise_component(
+        components=recreation_potential_component,
+        threshhold=THRESHHOLD_ZERO,
+        output_name=tmp_recreation_potential,
+    )
+
+    # recode recreation_potential
+    tmp_recreation_potential_categories = temporary_filename(filename=recreation_potential)
+
+    msg = "\nClassifying '{potential}' map"
+    msg = msg.format(potential=tmp_recreation_potential)
+    grass.verbose(_(msg))
+
+    classify_recreation_component(
+        component=tmp_recreation_potential,
+        rules=RECREATION_POTENTIAL_CATEGORIES,
+        output_name=tmp_recreation_potential_categories,
+    )
+
+    if recreation_potential:
+
+        # export 'recreation_potential' map and
+        # use 'output_name' for the temporary 'potential' map for spectrum
+        tmp_recreation_potential_categories = export_map(
+            input_name=tmp_recreation_potential_categories,
+            title=potential_title,
+            categories=POTENTIAL_CATEGORY_LABELS,
+            colors=POTENTIAL_COLORS,
+            output_name=recreation_potential,
+            timestamp=timestamp,
+        )
+
+    # Infrastructure to access recreational facilities, amenities, services
+    # Required for recreation opportunity and successively recreation spectrum
+
+    if infrastructure and not any(
+        [recreation_opportunity, recreation_spectrum, demand, flow, supply]
+    ):
+        msg = (
+            "Infrastructure is not required "
+            "to derive the 'potential' recreation map."
+        )
+        grass.warning(_(msg))
+
+    if any([recreation_opportunity, recreation_spectrum, demand, flow, supply]):
+
+        infrastructure_component = []
+        infrastructure_components = []
+
+        if infrastructure:
+            infrastructure_component.append(infrastructure)
+
+        """Artificial surfaces (includung Roads)"""
+
+        if artificial and roads:
+
+            msg = "Roads distance categories: {c}"
+            msg = msg.format(c=roads_distance_categories)
+            grass.debug(_(msg))
+            roads_proximity = compute_artificial_proximity(
+                raster=roads,
+                distance_categories=roads_distance_categories,
+                output_name=roads_proximity_map_name,
+            )
+
+            msg = "Artificial distance categories: {c}"
+            msg = msg.format(c=artificial_distance_categories)
+            grass.debug(_(msg))
+            artificial_proximity = compute_artificial_proximity(
+                raster=artificial,
+                distance_categories=artificial_distance_categories,
+                output_name=artificial_proximity_map_name,
+            )
+
+            artificial_accessibility = compute_artificial_accessibility(
+                artificial_proximity,
+                roads_proximity,
+                output_name=artificial_accessibility_map_name,
+            )
+
+            infrastructure_components.append(artificial_accessibility)
+
+        # merge infrastructure component related maps in one list
+        infrastructure_component += infrastructure_components
+
+    # # Recreational facilities, amenities, services
+
+    # recreation_component = []
+    # recreation_components = []
+
+    # if recreation:
+    #     recreation_component.append(recreation)
+
+    # # merge recreation component related maps in one list
+    # recreation_component += recreation_components
+
+    """ Recreation Spectrum """
+
+    if any([recreation_spectrum, demand, flow, supply]):
+
+        recreation_opportunity_component = []
+
+        # input
+        zerofy_and_normalise_component(
+            components=infrastructure_component,
+            threshhold=THRESHHOLD_ZERO,
+            output_name=infrastructure_component_map_name,
+        )
+
+        recreation_opportunity_component.append(infrastructure_component_map_name)
+
+        # # input
+        # zerofy_and_normalise_component(recreation_component,
+        #         THRESHHOLD_0001, recreation_component_map_name)
+        # recreation_opportunity_component.append(recreation_component_map_name)
+        # remove_map_at_exit(recreation_component_map_name)
+
+        # intermediate
+
+        # REVIEW --------------------------------------------------------------
+        tmp_recreation_opportunity = temporary_filename(filename=recreation_opportunity_map_name)
+        msg = "Computing intermediate opportunity map '{opportunity}'"
+        grass.debug(_(msg.format(opportunity=tmp_recreation_opportunity)))
+
+        grass.verbose(_("\nNormalize 'Recreation Opportunity' component\n"))
+        grass.debug(_("Maps: {maps}".format(maps=recreation_opportunity_component)))
+
+        zerofy_and_normalise_component(
+            components=recreation_opportunity_component,
+            threshhold=THRESHHOLD_0001,
+            output_name=tmp_recreation_opportunity,
+        )
+
+        # Why threshhold 0.0003? How and why it differs from 0.0001?
+        # -------------------------------------------------------------- REVIEW
+
+        msg = "Classifying '{opportunity}' map"
+        grass.verbose(msg.format(opportunity=tmp_recreation_opportunity))
+
+        # recode opportunity_component
+        tmp_recreation_opportunity_categories = temporary_filename(filename=recreation_opportunity)
+        classify_recreation_component(
+            component=tmp_recreation_opportunity,
+            rules=RECREATION_OPPORTUNITY_CATEGORIES,
+            output_name=tmp_recreation_opportunity_categories,
+        )
+
+        """ Recreation Opportunity [Output]"""
+
+        if recreation_opportunity:
+
+            # export 'recreation_opportunity' map and
+            # use 'output_name' for the temporary 'potential' map for spectrum
+            tmp_recreation_opportunity_categories = export_map(
+                input_name=tmp_recreation_opportunity_categories,
+                title=opportunity_title,
+                categories=OPPORTUNITY_CATEGORY_LABELS,
+                colors=OPPORTUNITY_COLORS,
+                output_name=recreation_opportunity,
+                timestamp=timestamp,
+            )
+
+        # Recreation Spectrum: Potential + Opportunity [Output]
+
+        if not recreation_spectrum and any([demand, flow, supply]):
+            recreation_spectrum = temporary_filename(filename="recreation_spectrum")
+            remove_map_at_exit(recreation_spectrum)
+
+        recreation_spectrum = compute_recreation_spectrum(
+            potential=tmp_recreation_potential_categories,
+            opportunity=tmp_recreation_opportunity_categories,
+            spectrum=recreation_spectrum,
+        )
+
+        msg = "Writing '{spectrum}' map"
+        msg = msg.format(spectrum=recreation_spectrum)
+        grass.verbose(_(msg))
+        get_univariate_statistics(recreation_spectrum)
+
+        # get category labels
+        temporary_spectrum_categories = temporary_filename(filename="categories_of_" + recreation_spectrum)
+        spectrum_category_labels = string_to_file(
+            SPECTRUM_CATEGORY_LABELS, filename=temporary_spectrum_categories
+        )
+
+        # add to list for removal
+        remove_files_at_exit(spectrum_category_labels)
+
+        # update category labels, meta and colors
+        spectrum_categories = "categories_of_"
+
+        r.category(
+            map=recreation_spectrum, rules=spectrum_category_labels, separator=":"
+        )
+
+        update_meta(recreation_spectrum, spectrum_title)
+
+        r.colors(map=recreation_spectrum, rules="-", stdin=SPECTRUM_COLORS, quiet=True)
+
+        if base_vector:
+            update_vector(
+                vector=base_vector,
+                raster=recreation_spectrum,
+                methods=METHODS,
+                column_prefix="spectrum",
+            )
+
+    """Valuation Tables"""
+
+    if any([demand, flow, supply, aggregation]):
+
+        """Highest Recreation Spectrum == 9"""
+
+        expression = (
+            "if({spectrum} == {highest_recreation_category}, {spectrum}, null())"
+        )
+        highest_spectrum_expression = expression.format(
+            spectrum=recreation_spectrum,
+            highest_recreation_category=HIGHEST_RECREATION_CATEGORY,
+        )
+        highest_spectrum_equation = EQUATION.format(
+            result=highest_spectrum, expression=highest_spectrum_expression
+        )
+        r.mapcalc(highest_spectrum_equation, overwrite=True)
+
+        """Distance map"""
+
+        distance_to_highest_spectrum = temporary_filename(filename=highest_spectrum)
+        r.grow_distance(
+            input=highest_spectrum,
+            distance=distance_to_highest_spectrum,
+            metric=metric,
+            quiet=True,
+            overwrite=True,
+        )
+
+        """Distance categories"""
+
+        distance_categories_to_highest_spectrum = "categories_of_"
+        distance_categories_to_highest_spectrum += distance_to_highest_spectrum
+        remove_map_at_exit(distance_categories_to_highest_spectrum)  # FIXME
+
+        recode_map(
+            raster=distance_to_highest_spectrum,
+            rules=spectrum_distance_categories,
+            colors=SCORE_COLORS,
+            output=distance_categories_to_highest_spectrum,
+        )
+
+        temporary_distance_categories_to_highest_spectrum = temporary_filename(filename=distance_categories_to_highest_spectrum)
+        spectrum_distance_category_labels = string_to_file(
+            SPECTRUM_DISTANCE_CATEGORY_LABELS,
+            filename=temporary_distance_categories_to_highest_spectrum,
+        )
+        remove_files_at_exit(spectrum_distance_category_labels)
+
+        r.category(
+            map=distance_categories_to_highest_spectrum,
+            rules=spectrum_distance_category_labels,
+            separator=":",
+        )
+
+        """Combine Base map and Distance Categories"""
+
+        tmp_crossmap = temporary_filename(filename=crossmap)
+        r.cross(
+            input=(distance_categories_to_highest_spectrum, base),
+            flags="z",
+            output=tmp_crossmap,
+            quiet=True,
+        )
+
+        grass.use_temp_region()  # to safely modify the region
+        g.region(
+            nsres=population_ns_resolution, ewres=population_ew_resolution, flags="a"
+        )  # Resolution should match 'population' FIXME
+        msg = "|! Computational extent & resolution matched to {raster}"
+        msg = msg.format(raster=landuse)
+        grass.verbose(_(msg))
+
+        population_statistics = get_univariate_statistics(population)
+        population_total = population_statistics['sum']
+        msg = "|i Population statistics: {s}".format(s=population_total)
+        grass.verbose(_(msg))
+
+        """Demand Distribution"""
+
+        if any([flow, supply, aggregation]) and not demand:
+            demand = temporary_filename(filename="demand")
+
+        r.stats_zonal(
+            base=tmp_crossmap,
+            flags="r",
+            cover=population,
+            method="sum",
+            output=demand,
+            overwrite=True,
+            quiet=True,
+        )
+
+        # copy 'reclassed' as 'normal' map (r.mapcalc)
+        # so as to enable removal of it and its 'base' map
+        demand_copy = demand + "_copy"
+        copy_expression = "{input_raster}"
+        copy_expression = copy_expression.format(input_raster=demand)
+        copy_equation = EQUATION.format(result=demand_copy, expression=copy_expression)
+        r.mapcalc(copy_equation, overwrite=True)
+
+        # remove the reclassed map 'demand'
+        g.remove(flags="f", type="raster", name=demand, quiet=True)
+
+        # rename back to 'demand'
+        g.rename(raster=(demand_copy, demand), quiet=True)
+
+        if demand and base_vector:
+            update_vector(
+                vector=base_vector,
+                raster=demand,
+                methods=METHODS,
+                column_prefix="demand",
+            )
+
+        """Unmet Demand"""
+
+        if unmet_demand:
+
+            # compute unmet demand
+
+            unmet_demand_expression = compute_unmet_demand(
+                distance=distance_categories_to_highest_spectrum,
+                constant=MOBILITY_CONSTANT,
+                coefficients=MOBILITY_COEFFICIENTS[4],
+                population=demand,
+                score=MOBILITY_SCORE,
+            )
+            # suitability=suitability)  # Not used.
+            # Maybe it can, though, after successfully testing its
+            # integration to build_distance_function().
+
+            grass.debug(
+                _("Unmet demand function: {f}".format(f=unmet_demand_expression))
+            )
+
+            unmet_demand_equation = EQUATION.format(
+                result=unmet_demand, expression=unmet_demand_expression
+            )
+            r.mapcalc(unmet_demand_equation, overwrite=True)
+
+            if base_vector:
+                update_vector(
+                    vector=base_vector,
+                    raster=unmet_demand,
+                    methods=METHODS,
+                    column_prefix="unmet",
+                )
+
+        """Mobility function"""
+
+        if not flow and any([supply, aggregation]):
+
+            flow = flow_map_name
+            remove_map_at_exit(flow)
+
+        if flow or any([supply, aggregation]):
+
+            mobility_expression = mobility_function(
+                distance=distance_categories_to_highest_spectrum,
+                constant=MOBILITY_CONSTANT,
+                coefficients=MOBILITY_COEFFICIENTS,
+                population=demand,
+                score=MOBILITY_SCORE,
+            )
+            # suitability=suitability)  # Not used.
+            # Maybe it can, though, after successfully testing its
+            # integration to build_distance_function().
+
+            msg = "Mobility function: {f}"
+            grass.debug(_(msg.format(f=mobility_expression)))
+
+            """Flow map"""
+
+            mobility_equation = EQUATION.format(
+                result=flow, expression=mobility_expression
+            )
+            r.mapcalc(mobility_equation, overwrite=True)
+
+            if base_vector:
+                update_vector(
+                    vector=base_vector,
+                    raster=flow_map_name,
+                    methods=METHODS,
+                    column_prefix="flow",
+                )
+
+    """Supply Table"""
+
+    if aggregation:
+
+        supply_parameters = {}
+
+        if supply:
+            supply_parameters.update({"supply_filename": supply})
+
+        if use:
+            supply_parameters.update({"use_filename": use})
+
+        if base_vector:
+            supply_parameters.update({"vector": base_vector})
+
+        compute_supply(
+            base=landcover,
+            recreation_spectrum=recreation_spectrum,
+            highest_spectrum=highest_spectrum,
+            base_reclassification_rules=landcover_reclassification_rules,
+            reclassified_base=maes_ecosystem_types,
+            reclassified_base_title="MAES ecosystem types",
+            flow=flow,
+            flow_map_name=flow_map_name,
+            aggregation=aggregation,
+            ns_resolution=population_ns_resolution,
+            ew_resolution=population_ew_resolution,
+            print_only=print_only,
+            **supply_parameters
+        )
+
+    # restore region
+    if landuse_extent:
+        grass.del_temp_region()  # restoring previous region settings
+        grass.verbose("Original Region restored")
+
+    # print citation
+    citation = "Citation: " + CITATION_RECREATION_POTENTIAL
+    grass.verbose(citation)

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/mobility.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/mobility.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/mobility.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,248 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import grass.script as grass
+from .distance import build_distance_function
+
+
+def mobility_function(
+    distance, constant, coefficients, population, score, suitability=None
+):
+    """
+    The following 'mobility' function, is identical to the one used in
+    `compute_attractiveness()`, excluding, however, the 'score' term:
+
+        if(L10<>0,(1+$D$3)/($D$3+exp(-$E$3*L10))*52,0)
+
+    Source: Excel file provided by the MAES Team, Land Resources, D3
+
+    ------------------------------------------------------------------
+    if L<>0; then
+      # (1 + D) / (D + exp(-E * L)) * 52)
+
+      #  D: Kappa
+      # -E: Alpha -- Note the '-' sign in front of E
+      #  L: Population (within boundary, within distance buffer)
+    ------------------------------------------------------------------
+
+    Parameters
+    ----------
+
+    distance :
+        Map of distance categories
+
+    constant :
+        Constant for the mobility function
+
+    coefficients :
+        A dictionary with a set for coefficients for each distance category, as
+        (the example) presented in the following table:
+
+        |----------+---------+---------|
+        | Distance | Kappa   | Alpha   |
+        |----------+---------+---------|
+        | 0 to 1   | 0.02350 | 0.00102 |
+        |----------+---------+---------|
+        | 1 to 2   | 0.02651 | 0.00109 |
+        |----------+---------+---------|
+        | 2 to 3   | 0.05120 | 0.00098 |
+        |----------+---------+---------|
+        | 3 to 4   | 0.10700 | 0.00067 |
+        |----------+---------+---------|
+        | >4       | 0.06930 | 0.00057 |
+        |----------+---------+---------|
+
+        Note, the last distance category is not considered in deriving the
+        final "map of visits".
+
+        Note, the Alpha coefficient, is signed with a '-' in the mobility
+        function.
+
+    population :
+        A map with the distribution of the demand per distance category and
+        predefined geometric boundaries (see `r.stats.zonal` deriving the
+        'demand' map ).
+
+    score :
+        A score value for the mobility function
+
+    suitability :
+        [ NOTE: this argument is yet unused!  It is meant to be used only after
+        successful integration of land suitability scores in
+        build_distance_function(). ]
+        If 'suitability' is given, it is used as a multiplication
+        factor to the base equation.
+
+
+    Returns
+    -------
+
+    mobility_expression :
+        A valid mapcalc expression to compute the flow based on the
+        predefined function `build_distance_function`.
+
+    Examples
+    --------
+    ...
+    """
+    expressions = {}  # create a dictionary of expressions
+
+    for distance_category, parameters in coefficients.items():
+        kappa, alpha = parameters
+
+        # Note, alpha gets a minus, that is: -alpha
+        expressions[distance_category] = build_distance_function(
+            constant=constant,
+            kappa=kappa,
+            alpha=-alpha,
+            variable=population,
+            score=score,
+        )
+        # suitability=suitability)  # Not used.
+        # Maybe it can, though, after successfully testing its
+        # integration to build_distance_function().
+
+        grass.debug(_("For distance '{d}':".format(d=distance)))
+        grass.debug(_(expressions[distance_category]))
+
+    msg = "Expressions per distance category: {e}".format(e=expressions)
+    grass.debug(_(msg))
+
+    # build expressions -- explicit: use the'score' kwarg!
+    expression = (
+        "eval( mobility_0 = {expression_0},"
+        " \ \n mobility_1 = {expression_1},"
+        " \ \n mobility_2 = {expression_2},"
+        " \ \n mobility_3 = {expression_3},"
+        " \ \n distance_0 = {distance} == {distance_category_0},"
+        " \ \n distance_1 = {distance} == {distance_category_1},"
+        " \ \n distance_2 = {distance} == {distance_category_2},"
+        " \ \n distance_3 = {distance} == {distance_category_3},"
+        " \ \n if( distance_0, mobility_0,"
+        " \ \n if( distance_1, mobility_1,"
+        " \ \n if( distance_2, mobility_2,"
+        " \ \n if( distance_3, mobility_3,"
+        " \ \n null() )))))"
+    )
+    grass.debug(_("Mapcalc expression: {e}".format(e=expression)))
+
+    # replace keywords appropriately
+    # 'distance' is a map
+    # 'distance_category' is a value
+    # hence: 'distance' != 'distance_category'
+    mobility_expression = expression.format(
+        expression_0=expressions[0],
+        expression_1=expressions[1],
+        expression_2=expressions[2],
+        expression_3=expressions[3],
+        distance_category_0=0,
+        distance_category_1=1,
+        distance_category_2=2,
+        distance_category_3=3,
+        distance=distance,
+    )
+    # FIXME Make the above more elegant?
+
+    msg = "Big expression (after formatting): {e}".format(e=expression)
+    grass.debug(_(msg))
+
+    return mobility_expression
+
+
+def compute_unmet_demand(
+    distance, constant, coefficients, population, score, suitability=None
+):
+    """
+    Parameters
+    ----------
+
+    distance :
+        Map of distance categories
+
+    constant :
+        Constant for the mobility function
+
+    coefficients :
+        A tuple with coefficients for the last distance category, as
+        (the example) presented in the following table:
+
+        |----------+---------+---------|
+        | Distance | Kappa   | Alpha   |
+        |----------+---------+---------|
+        | >4       | 0.06930 | 0.00057 |
+        |----------+---------+---------|
+
+        Note, this is the last distance category. See also the
+        mobility_function().
+
+        Note, the Alpha coefficient, is signed with a '-' in the mobility
+        function.
+
+    population :
+        A map with the distribution of the demand per distance category and
+        predefined geometric boundaries (see `r.stats.zonal` deriving the
+        'unmet demand' map ).
+
+    score :
+        A score value for the mobility function
+
+    suitability :
+        [ NOTE: this argument is yet unused!  It is meant to be used only after
+        successful integration of land suitability scores in
+        build_distance_function(). ]
+        If 'suitability' is given, it is used as a multiplication
+        factor to the base equation.
+
+    Returns
+    -------
+
+    mobility_expression :
+        A valid mapcalc expression to compute the flow based on the
+        predefined function `build_distance_function`.
+
+    Examples
+    --------
+    ...
+    """
+    distance_category = 4  # Hardcoded! FIXME
+
+    kappa, alpha = coefficients
+    unmet_demand_expression = build_distance_function(
+        constant=constant, kappa=kappa, alpha=-alpha, variable=population, score=score
+    )
+    # suitability=suitability)  # Not used. Maybe it can, though,
+    # after successfull testing its integration to
+    # build_distance_function().
+
+    msg = "Expression for distance category '{d}': {e}"
+    msg = msg.format(d=distance_category, e=unmet_demand_expression)
+    grass.debug(_(msg))
+
+    # build expressions -- explicit: use the 'score' kwarg!
+    expression = (
+        "eval( unmet_demand = {expression},"
+        " \ \n distance = {distance} == {distance_category},"
+        " \ \n if( distance, unmet_demand,"
+        " \ \n null() ))"
+    )
+    grass.debug(_("Mapcalc expression: {e}".format(e=expression)))
+
+    # replace keywords appropriately
+    unmet_demand_expression = expression.format(
+        expression=unmet_demand_expression,
+        distance=distance,
+        distance_category=distance_category,
+    )
+
+    msg = "Big expression (after formatting): {e}".format(e=unmet_demand_expression)
+    grass.debug(_(msg))
+
+    return unmet_demand_expression

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/normalisation.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/normalisation.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/normalisation.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import grass.script as grass
+from grass.exceptions import CalledModuleError
+from grass.pygrass.modules.shortcuts import general as g
+from grass.pygrass.modules.shortcuts import raster as r
+from grass.pygrass.modules.shortcuts import vector as v
+
+from estimap_recreation.constants import *
+from estimap_recreation.grassy_utilities import *
+
+
+def zerofy_small_values(raster, threshhold, output_name):
+    """
+    Set the input raster map cell values to 0 if they are smaller than the
+    given threshhold
+
+    Parameters
+    ----------
+    raster :
+        Name of input raster map
+
+    threshhold :
+        Reference for which to flatten smaller raster pixel values to zero
+
+    output_name :
+        Name of output raster map
+
+    Returns
+    -------
+        Does not return any value
+
+    Examples
+    --------
+    ...
+    """
+    rounding = "if({raster} < {threshhold}, 0, {raster})"
+    rounding = rounding.format(raster=raster, threshhold=threshhold)
+    rounding_equation = EQUATION.format(result=output_name, expression=rounding)
+    grass.mapcalc(rounding_equation, overwrite=True)
+
+
+def normalize_map(raster, output_name):
+    """
+    Normalize all raster map cells by subtracting the raster map's minimum and
+    dividing by the range.
+
+    Parameters
+    ----------
+    raster :
+        Name of input raster map
+
+    output_name :
+        Name of output raster map
+
+    Returns
+    -------
+
+    Examples
+    --------
+    ...
+    """
+    # grass.debug(_("Input to normalize: {name}".format(name=raster)))
+    # grass.debug(_("Ouput: {name}".format(name=output_name)))
+
+    finding = grass.find_file(name=raster, element="cell")
+
+    if not finding["file"]:
+        grass.fatal("Raster map {name} not found".format(name=raster))
+    # else:
+    #     grass.debug("Raster map {name} found".format(name=raster))
+
+    # univar_string = grass.read_command('r.univar', flags='g', map=raster)
+    # univar_string = univar_string.replace('\n', '| ').replace('\r', '| ')
+    # msg = "Univariate statistics: {us}".format(us=univar_string)
+
+    minimum = grass.raster_info(raster)["min"]
+    grass.debug(_("Minimum: {m}".format(m=minimum)))
+
+    maximum = grass.raster_info(raster)["max"]
+    grass.debug(_("Maximum: {m}".format(m=maximum)))
+
+    if minimum is None or maximum is None:
+        msg = "Minimum and maximum values of the <{raster}> map are 'None'. "
+        msg += "The {raster} map may be empty "
+        msg += "OR the MASK opacifies all non-NULL cells."
+        grass.fatal(_(msg.format(raster=raster)))
+
+    normalisation = "float(({raster} - {minimum}) / ({maximum} - {minimum}))"
+    normalisation = normalisation.format(
+        raster=raster, minimum=minimum, maximum=maximum
+    )
+
+    # Maybe this can go in the parent function? 'raster' names are too long!
+    # msg = "Normalization expression: "
+    # msg += normalisation
+    # grass.verbose(_(msg))
+
+    normalisation_equation = EQUATION.format(
+        result=output_name, expression=normalisation
+    )
+    grass.mapcalc(normalisation_equation, overwrite=True)
+
+    get_univariate_statistics(output_name)
+
+
+def zerofy_and_normalise_component(components, threshhold, output_name):
+    """
+    Sums up all maps listed in the given "components" object and derives a
+    normalised output.
+
+    To Do:
+
+    * Improve `threshold` handling. What if threshholding is not desired? How
+    to skip performing it?
+
+    Parameters
+    ----------
+    components :
+        Input list of raster maps (components)
+
+    threshhold :
+        Reference value for which to flatten all smaller raster pixel values to
+        zero
+
+    output_name :
+        Name of output raster map
+
+    Returns
+    -------
+    ...
+
+    Examples
+    --------
+    ...
+    """
+    msg = "Normalising sum of: "
+    msg += ",".join(components)
+    grass.debug(_(msg))
+    grass.verbose(_(msg))
+
+    if len(components) > 1:
+
+        # prepare string for mapcalc expression
+        components = [name.split("@")[0] for name in components]
+        components_string = SPACY_PLUS.join(components)
+        components_string = components_string.replace(" ", "")
+        components_string = components_string.replace("+", "_")
+
+        # temporary map names
+        tmp_intermediate = temporary_filename(filename=components_string)
+        tmp_output = temporary_filename(filename=components_string)
+
+        # build mapcalc expression
+        component_expression = SPACY_PLUS.join(components)
+        component_equation = EQUATION.format(
+            result=tmp_intermediate, expression=component_expression
+        )
+
+        grass.mapcalc(component_equation, overwrite=True)
+
+    elif len(components) == 1:
+        # temporary map names, if components contains one element
+        tmp_intermediate = components[0]
+        tmp_output = temporary_filename(filename=tmp_intermediate)
+
+    if threshhold > THRESHHOLD_ZERO:
+        msg = "Setting values < {threshhold} in '{raster}' to zero"
+        grass.verbose(msg.format(threshhold=threshhold, raster=tmp_intermediate))
+        zerofy_small_values(tmp_intermediate, threshhold, tmp_output)
+
+    else:
+        tmp_output = tmp_intermediate
+
+    # grass.verbose(_("Temporary map name: {name}".format(name=tmp_output)))
+    grass.debug(_("Output map name: {name}".format(name=output_name)))
+    # r.info(map=tmp_output, flags='gre')
+
+    ### FIXME
+
+    normalize_map(tmp_output, output_name)
+
+    ### FIXME

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/panos.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/panos.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/panos.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,13 @@
+def add_one(value):
+    new_value = value + 1
+    return new_value
+
+
+def add_two(value):
+    new_value = value + 2
+    return new_value
+
+
+def add_ten(value):
+    new_value = value + 20
+    return new_value

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/spectrum.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/spectrum.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/spectrum.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import grass.script as grass
+from grass.exceptions import CalledModuleError
+from grass.pygrass.modules.shortcuts import general as g
+from grass.pygrass.modules.shortcuts import raster as r
+from grass.pygrass.modules.shortcuts import vector as v
+
+from .constants import EQUATION
+
+
+def recreation_spectrum_expression(potential, opportunity):
+    """
+    Build and return a valid mapcalc expression for deriving
+    the Recreation Opportunity Spectrum
+
+    |-------------------------+-----+----------+------|
+    | Potential / Opportunity | Far | Midrange | Near |
+    |-------------------------+-----+----------+------|
+    | Low                     | 1   | 2        | 3    |
+    |-------------------------+-----+----------+------|
+    | Moderate                | 4   | 5        | 6    |
+    |-------------------------+-----+----------+------|
+    | High                    | 7   | 8        | 9    |
+    |-------------------------+-----+----------+------|
+
+    Questions:
+
+    - Why not use `r.cross`?
+    - Use DUMMY strings for potential and opportunity raster map names?
+
+    Parameters
+    ----------
+    potential :
+        Map depicting potential for recreation
+
+    opportunity :
+        Map depicting opportunity for recreation
+
+    Returns
+    -------
+    expression :
+        A valid r.mapcalc expression
+
+    Examples
+    --------
+    ...
+    """
+    expression = (
+        "if( {potential} == 1 && {opportunity} == 1, 1,"
+        " \ \n if( {potential} == 1 && {opportunity} == 2, 2,"
+        " \ \n if( {potential} == 1 && {opportunity} == 3, 3,"
+        " \ \n if( {potential} == 2 && {opportunity} == 1, 4,"
+        " \ \n if( {potential} == 2 && {opportunity} == 2, 5,"
+        " \ \n if( {potential} == 2 && {opportunity} == 3, 6,"
+        " \ \n if( {potential} == 3 && {opportunity} == 1, 7,"
+        " \ \n if( {potential} == 3 && {opportunity} == 2, 8,"
+        " \ \n if( {potential} == 3 && {opportunity} == 3, 9)))))))))"
+    )
+
+    expression = expression.format(potential=potential, opportunity=opportunity)
+
+    msg = "Recreation Spectrum expression: \n"
+    msg += expression
+    grass.debug(msg)
+
+    return expression
+
+
+def compute_recreation_spectrum(potential, opportunity, spectrum):
+    """
+    Computes spectrum for recreation based on maps of potential and opportunity
+    for recreation
+
+    Parameters
+    ----------
+    potential :
+        Name for input potential for recreation map
+
+    opportunity :
+        Name for input opportunity for recreation map
+
+    Returns
+    -------
+    spectrum :
+        Name for output spectrum of recreation map
+
+    Examples
+    --------
+    ...
+    """
+    spectrum_expression = recreation_spectrum_expression(
+        potential=potential, opportunity=opportunity
+    )
+
+    spectrum_equation = EQUATION.format(result=spectrum, expression=spectrum_expression)
+
+    msg = "Recreation Spectrum equation: \n"
+    msg += spectrum_equation
+    grass.verbose(msg)
+
+    grass.mapcalc(spectrum_equation, overwrite=True)
+
+    return spectrum

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/supply_and_use.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/supply_and_use.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/supply_and_use.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,574 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import math
+import grass.script as grass
+from grass.pygrass.modules.shortcuts import general as g
+from grass.pygrass.modules.shortcuts import raster as r
+from grass.pygrass.modules.shortcuts import vector as v
+
+from .colors import MOBILITY_COLORS
+from .constants import EQUATION
+from .constants import HIGHEST_RECREATION_CATEGORY
+from .constants import SUITABILITY_SCORES_LABELS
+from .constants import COMMA
+from .constants import CSV_EXTENSION
+from .grassy_utilities import temporary_filename
+from .grassy_utilities import remove_map_at_exit, remove_files_at_exit
+from .grassy_utilities import string_to_file
+from .grassy_utilities import get_raster_statistics
+from .utilities import merge_two_dictionaries
+from .utilities import nested_dictionary_to_csv
+from .utilities import dictionary_to_csv
+
+
+def compile_use_table(supply):
+    """Compile the 'use' table out of a 'supply' table
+
+    Parameters
+    ----------
+    supply :
+        A nested Python dictionary that is compiled when runnning the
+        compute_supply() function
+
+    Returns
+    -------
+
+    Examples
+    --------
+    """
+    uses = {}
+    for outer_key, outer_value in supply.items():
+        dictionaries = outer_value
+        use_values = []
+        for key, value in dictionaries.items():
+            use_value = value[0]
+            use_values.append(use_value)
+
+        use_in_key = sum(
+            [float(x) if not math.isnan(float(x)) else 0 for x in use_values]
+        )
+
+        try:
+            uses[outer_key] = use_in_key
+        except KeyError:
+            print("Something went wrong in building the use table")
+
+    return uses
+
+
+def compute_supply(
+    base,
+    recreation_spectrum,
+    highest_spectrum,
+    base_reclassification_rules,
+    reclassified_base,
+    reclassified_base_title,
+    flow,
+    flow_map_name,
+    aggregation,
+    ns_resolution,
+    ew_resolution,
+    print_only=False,
+    flow_column_name=None,
+    vector=None,
+    supply_filename=None,
+    use_filename=None,
+):
+    """
+     Algorithmic description of the "Contribution of Ecosysten Types"
+
+     # FIXME
+     '''
+     1   B ← {0, .., m-1}     :  Set of aggregational boundaries
+     2   T ← {0, .., n-1}     :  Set of land cover types
+     3   WE ← 0               :  Set of weighted extents
+     4   R ← 0                :  Set of fractions
+     5   F ← 0
+     6   MASK ← HQR           : High Quality Recreation
+     7   foreach {b} ⊆ B do   : for each aggregational boundary 'b'
+     8      RB ← 0
+     9      foreach {t} ⊆ T do  : for each Land Type
+     10         WEt ← Et * Wt   : Weighted Extent = Extent(t) * Weight(t)
+     11         WE ← WE⋃{WEt}   : Add to set of Weighted Extents
+     12     S ← ∑t∈WEt
+     13     foreach t ← T do
+     14        Rt ← WEt / ∑WE
+     15        R ← R⋃{Rt}
+     16     RB ← RB⋃{R}
+     '''
+     # FIXME
+
+    Parameters
+    ----------
+    recreation_spectrum:
+        Map scoring access to and quality of recreation
+
+    highest_spectrum :
+        Expected is a map of areas with highest recreational value (category 9
+        as per the report ... )
+
+    base :
+        Base land types map for final zonal statistics. Specifically to
+        ESTIMAP's recrceation mapping algorithm
+
+    base_reclassification_rules :
+        Reclassification rules for the input base map
+
+    reclassified_base :
+        Name for the reclassified base cover map
+
+    reclassified_base_title :
+        Title for the reclassified base map
+
+    ecosystem_types :
+
+    flow :
+        Map of visits, derived from the mobility function, depicting the
+        number of people living inside zones 0, 1, 2, 3. Used as a cover map
+        for zonal statistics.
+
+    flow_map_name :
+        A name for the 'flow' map. This is required when the 'flow' input
+        option is not defined by the user, yet some of the requested outputs
+        required first the production of the 'flow' map. An example is the
+        request for a supply table without requesting the 'flow' map itself.
+
+    aggregation :
+
+    ns_resolution :
+
+    ew_resolution :
+
+    statistics_filename :
+
+    supply_filename :
+        Name for CSV output file of the supply table
+
+    use_filename :
+        Name for CSV output file of the use table
+
+    flow_column_name :
+        Name for column to populate with 'flow' values
+
+    vector :
+        If 'vector' is given, a vector map of the 'flow' along with appropriate
+        attributes will be produced.
+
+    ? :
+        Land cover class percentages in ROS9 (this is: relative percentage)
+
+    output :
+        Supply table (distribution of flow for each land cover class)
+
+    Returns
+    -------
+    This function produces a map to base the production of a supply table in
+    form of CSV.
+
+    Examples
+    --------
+    """
+    # Inputs
+    flow_in_base = flow + "_" + base
+    base_scores = base + ".scores"
+
+    # Define lists and dictionaries to hold intermediate data
+    statistics_dictionary = {}
+    weighted_extents = {}
+    flows = []
+
+    # MASK areas of high quality recreation
+    r.mask(raster=highest_spectrum, overwrite=True, quiet=True)
+
+    # Reclassify land cover map to MAES ecosystem types
+    r.reclass(
+        input=base,
+        rules=base_reclassification_rules,
+        output=reclassified_base,
+        quiet=True,
+    )
+    # add to "remove_at_exit" after the reclassified maps!
+
+    # Discard areas out of MASK
+    copy_equation = EQUATION.format(
+        result=reclassified_base, expression=reclassified_base
+    )
+    r.mapcalc(copy_equation, overwrite=True)
+
+    # Count flow within each land cover category
+    r.stats_zonal(
+        base=base,
+        flags="r",
+        cover=flow_map_name,
+        method="sum",
+        output=flow_in_base,
+        overwrite=True,
+        quiet=True,
+    )
+
+    # Set colors for "flow" map
+    r.colors(map=flow_in_base, color=MOBILITY_COLORS, quiet=True)
+
+    # Parse aggregation raster categories and labels
+    categories = grass.parse_command("r.category", map=aggregation, delimiter="\t")
+
+    for category in categories:
+
+        # Intermediate names
+
+        cells = highest_spectrum + ".cells" + "." + category
+        remove_map_at_exit(cells)
+
+        extent = highest_spectrum + ".extent" + "." + category
+        remove_map_at_exit(extent)
+
+        weighted = highest_spectrum + ".weighted" + "." + category
+        remove_map_at_exit(weighted)
+
+        fractions = base + ".fractions" + "." + category
+        remove_map_at_exit(fractions)
+
+        flow_category = "_flow_" + category
+        flow = base + flow_category
+        remove_map_at_exit(flow)
+
+        flow_in_reclassified_base = reclassified_base + "_flow"
+        flow_in_category = reclassified_base + flow_category
+        flows.append(flow_in_category)  # add to list for patching
+        remove_map_at_exit(flow_in_category)
+
+        # Output names
+
+        msg = "Processing aggregation raster category: {r}"
+        msg = msg.format(r=category)
+        grass.debug(_(msg))
+        # g.message(_(msg))
+
+        # First, set region to extent of the aggregation map
+        # and resolution to the one of the population map
+        # Note the `-a` flag to g.region: ?
+        # To safely modify the region: grass.use_temp_region()  # FIXME
+        g.region(
+            raster=aggregation,
+            nsres=ns_resolution,
+            ewres=ew_resolution,
+            flags="a",
+            quiet=True,
+        )
+
+        msg = "|! Computational resolution matched to {raster}"
+        msg = msg.format(raster=aggregation)
+        grass.debug(_(msg))
+
+        # Build MASK for current category & high quality recreation areas
+        msg = "Setting category '{c}' of '{a}' as a MASK"
+        grass.verbose(_(msg.format(c=category, a=aggregation)))
+
+        masking = "if( {spectrum} == {highest_quality_category} && "
+        masking += "{aggregation} == {category}, "
+        masking += "1, null() )"
+        masking = masking.format(
+            spectrum=recreation_spectrum,
+            highest_quality_category=HIGHEST_RECREATION_CATEGORY,
+            aggregation=aggregation,
+            category=category,
+        )
+        masking_equation = EQUATION.format(result="MASK", expression=masking)
+        grass.mapcalc(masking_equation, overwrite=True)
+
+        # zoom to MASK
+        g.region(zoom="MASK", nsres=ns_resolution, ewres=ew_resolution, quiet=True)
+
+        # Count number of cells within each land category
+        r.stats_zonal(
+            flags="r",
+            base=base,
+            cover=highest_spectrum,
+            method="count",
+            output=cells,
+            overwrite=True,
+            quiet=True,
+        )
+        cells_categories = grass.parse_command("r.category", map=cells, delimiter="\t")
+        grass.debug(_("Cells: {c}".format(c=cells_categories)))
+
+        # Build cell category and label rules for `r.category`
+        cells_rules = "\n".join(
+            ["{0}:{1}".format(key, value) for key, value in cells_categories.items()]
+        )
+
+        # Discard areas out of MASK
+        copy_equation = EQUATION.format(result=cells, expression=cells)
+        r.mapcalc(copy_equation, overwrite=True)
+
+        # Reassign cell category labels
+        r.category(map=cells, rules="-", stdin=cells_rules, separator=":")
+
+        # Compute extent of each land category
+        extent_expression = "@{cells} * area()"
+        extent_expression = extent_expression.format(cells=cells)
+        extent_equation = EQUATION.format(result=extent, expression=extent_expression)
+        r.mapcalc(extent_equation, overwrite=True)
+
+        # Write extent figures as labels
+        r.stats_zonal(
+            flags="r",
+            base=base,
+            cover=extent,
+            method="average",
+            output=extent,
+            overwrite=True,
+            verbose=False,
+            quiet=True,
+        )
+
+        # Write land suitability scores as an ASCII file
+        temporary_reclassified_base_map = temporary_filename(filename=reclassified_base)
+        suitability_scores_as_labels = string_to_file(
+            SUITABILITY_SCORES_LABELS, filename=temporary_reclassified_base_map
+        )
+        remove_files_at_exit(suitability_scores_as_labels)
+
+        # Write scores as raster category labels
+        r.reclass(
+            input=base,
+            output=base_scores,
+            rules=suitability_scores_as_labels,
+            overwrite=True,
+            quiet=True,
+            verbose=False,
+        )
+        remove_map_at_exit(base_scores)
+
+        # Compute weighted extents
+        weighted_expression = "@{extent} * float(@{scores})"
+        weighted_expression = weighted_expression.format(
+            extent=extent, scores=base_scores
+        )
+        weighted_equation = EQUATION.format(
+            result=weighted, expression=weighted_expression
+        )
+        r.mapcalc(weighted_equation, overwrite=True)
+
+        # Write weighted extent figures as labels
+        r.stats_zonal(
+            flags="r",
+            base=base,
+            cover=weighted,
+            method="average",
+            output=weighted,
+            overwrite=True,
+            verbose=False,
+            quiet=True,
+        )
+
+        # Get weighted extents in a dictionary
+        weighted_extents = grass.parse_command(
+            "r.category", map=weighted, delimiter="\t"
+        )
+
+        # Compute the sum of all weighted extents and add to dictionary
+        category_sum = sum(
+            [
+                float(x) if not math.isnan(float(x)) else 0
+                for x in weighted_extents.values()
+            ]
+        )
+        weighted_extents["sum"] = category_sum
+
+        # Create a map to hold fractions of each weighted extent to the sum
+        # See also:
+        # https://grasswiki.osgeo.org/wiki/LANDSAT#Hint:_Minimal_disk_space_copies
+        r.reclass(
+            input=base,
+            output=fractions,
+            rules="-",
+            stdin="*=*",
+            verbose=False,
+            quiet=True,
+        )
+
+        # Compute weighted fractions of land types
+        fraction_category_label = {
+            key: float(value) / weighted_extents["sum"]
+            for (key, value) in weighted_extents.iteritems()
+            if key is not "sum"
+        }
+
+        # Build fraction category and label rules for `r.category`
+        fraction_rules = "\n".join(
+            [
+                "{0}:{1}".format(key, value)
+                for key, value in fraction_category_label.items()
+            ]
+        )
+
+        # Set rules
+        r.category(map=fractions, rules="-", stdin=fraction_rules, separator=":")
+
+        # Assert that sum of fractions is ~1
+        fraction_categories = grass.parse_command(
+            "r.category", map=fractions, delimiter="\t"
+        )
+
+        fractions_sum = sum(
+            [
+                float(x) if not math.isnan(float(x)) else 0
+                for x in fraction_categories.values()
+            ]
+        )
+        msg = "Fractions: {f}".format(f=fraction_categories)
+        grass.debug(_(msg))
+
+        # g.message(_("Sum: {:.17g}".format(fractions_sum)))
+        assert abs(fractions_sum - 1) < 1.0e-6, "Sum of fractions is != 1"
+
+        # Compute flow
+        flow_expression = "@{fractions} * @{flow}"
+        flow_expression = flow_expression.format(fractions=fractions, flow=flow_in_base)
+        flow_equation = EQUATION.format(result=flow, expression=flow_expression)
+        r.mapcalc(flow_equation, overwrite=True)
+
+        # Write flow figures as raster category labels
+        r.stats_zonal(
+            base=reclassified_base,
+            flags="r",
+            cover=flow,
+            method="sum",
+            output=flow_in_category,
+            overwrite=True,
+            verbose=False,
+            quiet=True,
+        )
+
+        # Parse flow categories and labels
+        flow_categories = grass.parse_command(
+            "r.category", map=flow_in_category, delimiter="\t"
+        )
+        grass.debug(_("Flow: {c}".format(c=flow_categories)))
+
+        # Build flow category and label rules for `r.category`
+        flow_rules = "\n".join(
+            ["{0}:{1}".format(key, value) for key, value in flow_categories.items()]
+        )
+
+        # Discard areas out of MASK
+
+        # Check here again!
+        # Output patch of all flow maps?
+
+        copy_equation = EQUATION.format(
+            result=flow_in_category, expression=flow_in_category
+        )
+        r.mapcalc(copy_equation, overwrite=True)
+
+        # Reassign cell category labels
+        r.category(map=flow_in_category, rules="-", stdin=flow_rules, separator=":")
+
+        # Update title
+        reclassified_base_title += " " + category
+        r.support(flow_in_category, title=reclassified_base_title)
+
+        # debugging
+        # r.report(
+        #     flags='hn',
+        #     map=(flow_in_category),
+        #     units=('k','c','p'),
+        # )
+
+        if print_only:
+            r.stats(
+                input=(flow_in_category),
+                output="-",
+                flags="nacpl",
+                separator=COMMA,
+                quiet=True,
+            )
+
+        if not print_only:
+
+            if flow_column_name:
+                flow_column_prefix = flow_column_name + category
+            else:
+                flow_column_name = "flow"
+                flow_column_prefix = flow_column_name + category
+
+            # Produce vector map(s)
+            if vector:
+
+                # The following is wrong
+
+                # update_vector(vector=vector,
+                #         raster=flow_in_category,
+                #         methods=METHODS,
+                #         column_prefix=flow_column_prefix)
+
+                # What can be done?
+
+                # Maybe update columns of an existing map from the columns of
+                # the following vectorised raster map(s)
+                # ?
+
+                raster_to_vector(
+                    raster=flow_in_category, vector=flow_in_category, type="area"
+                )
+
+            # get statistics
+            dictionary = get_raster_statistics(
+                map_one=aggregation,  # reclassified_base
+                map_two=flow_in_category,
+                separator="|",
+                flags="nlcap",
+            )
+
+            # merge 'dictionary' with global 'statistics_dictionary'
+            statistics_dictionary = merge_two_dictionaries(
+                statistics_dictionary, dictionary
+            )
+
+        # It is important to remove the MASK!
+        r.mask(flags="r", quiet=True)
+
+    # FIXME
+
+    # Add "reclassified_base" map to "remove_at_exit" here, so as to be after
+    # all reclassified maps that derive from it
+
+    # remove the map 'reclassified_base'
+    # g.remove(flags='f', type='raster', name=reclassified_base, quiet=True)
+    # remove_map_at_exit(reclassified_base)
+
+    if not print_only:
+        r.patch(flags="", input=flows, output=flow_in_reclassified_base, quiet=True)
+
+        if vector:
+            # Patch all flow vector maps in one
+            v.patch(
+                flags="e",
+                input=flows,
+                output=flow_in_reclassified_base,
+                overwrite=True,
+                quiet=True,
+            )
+
+        # export to csv
+        if supply_filename:
+            supply_filename += CSV_EXTENSION
+            nested_dictionary_to_csv(supply_filename, statistics_dictionary)
+
+        if use_filename:
+            use_filename += CSV_EXTENSION
+            uses = compile_use_table(statistics_dictionary)
+            dictionary_to_csv(use_filename, uses)
+
+    # Maybe return list of flow maps?  Requires unique flow map names
+    return flows

Added: grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/utilities.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/utilities.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/estimap_recreation/utilities.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,155 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ at author Nikos Alexandris |
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+import csv
+
+
+def merge_two_dictionaries(first, second):
+    """Merge two dictionaries in via shallow copy.
+    Source: https://stackoverflow.com/a/26853961/1172302"""
+    merged_dictionary = first.copy()
+    merged_dictionary.update(second)
+    return merged_dictionary
+
+
+def dictionary_to_csv(filename, dictionary):
+    """Write a Python dictionary as CSV named 'filename'
+
+    Parameters
+    ----------
+    filename :
+        Name for output file
+
+    dictionary :
+        Name of input Python dictionary to write to 'filename'
+
+    Returns
+    -------
+        This function does not return anything
+
+    Examples
+    --------
+    """
+    f = open(filename, "wb")
+    w = csv.writer(f)
+
+    # write a header
+    w.writerow(["category", "label", "value"])
+
+    # terminology: from 'base' and 'cover' maps
+    for base_key, value in dictionary.items():
+        base_category = base_key[0]
+        base_label = base_key[1]  # .decode('utf-8')
+        if value is None or value == "":
+            continue
+        w.writerow([base_category, base_label, value])
+
+    f.close()
+
+
+def nested_dictionary_to_csv(filename, dictionary):
+    """Write out a nested Python dictionary as CSV named 'filename'
+
+    Parameters
+    ----------
+    filename :
+        Name for output file
+
+    dictionary :
+        Name of the input Python dictionary
+    """
+    f = open(filename, "wb")
+    w = csv.writer(f)
+
+    # write a header
+    w.writerow(
+        ["base", "base_label", "cover", "cover_label", "area", "count", "percents"]
+    )
+
+    # terminology: from 'base' and 'cover' maps
+    for base_key, inner_dictionary in dictionary.items():
+        base_category = base_key[0]
+        base_label = base_key[1]  # .decode('utf-8')
+
+        for cover_category, inner_value in inner_dictionary.items():
+            if inner_value is None or inner_value == "":
+                continue
+            cover_label = inner_value[0]
+            area = inner_value[1]
+            pixel_count = inner_value[2]
+            pixel_percentage = inner_value[3]
+            w.writerow(
+                [
+                    base_category,
+                    base_label,
+                    cover_category,
+                    cover_label,
+                    area,
+                    pixel_count,
+                    pixel_percentage,
+                ]
+            )
+
+    f.close()
+
+
+# This function should be better off this module  # FIXME
+def get_coefficients(coefficients_string):
+    """Returns coefficients from an input coefficients_string
+
+    Parameters
+    ----------
+    coefficients_string:
+        One string which lists a metric and coefficients separated by comma
+        without spaces
+
+    Returns
+    -------
+    metric:
+        Metric to use an input option to the `r.grow.distance` module
+
+    constant:
+        A constant value for the 'attractiveness' function
+
+    kappa:
+        A Kappa coefficients for the 'attractiveness' function
+
+    alpha:
+        An alpha coefficient for the 'attractiveness' function
+
+    score
+        A score value to multiply by the generic 'attractiveness' function
+
+    Examples
+    --------
+    ...
+    """
+    coefficients = coefficients_string.split(",")
+    msg = "Distance function coefficients: "
+    metric = coefficients[0]
+    msg += "Metric='{metric}', ".format(metric=metric)
+    constant = coefficients[1]
+    msg += "Constant='{constant}', ".format(constant=constant)
+    kappa = coefficients[2]
+    msg += "Kappa='{Kappa}', ".format(Kappa=kappa)
+    alpha = coefficients[3]
+    msg += "Alpha='{alpha}', ".format(alpha=alpha)
+    try:
+        if coefficients[4]:
+            score = coefficients[4]
+            msg += "Score='{score}'".format(score=score)
+            grass.verbose(_(msg))  # FIXME REMOVEME ?
+            return metric, constant, kappa, alpha, score
+    except IndexError:
+        grass.verbose(_("Score not provided"))  # FIXME REMOVEME ?
+
+    grass.verbose(_(msg))  # FIXME REMOVEME ?
+    return metric, constant, kappa, alpha

Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_area_of_interest.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_area_of_interest.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_area_of_interest.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_area_of_interest.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_area_of_interest.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_bathing_water_quality.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_bathing_water_quality.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_bathing_water_quality.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_bathing_water_quality.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_bathing_water_quality.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006_suitability.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006_suitability.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006_suitability.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006_suitability.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_2006_suitability.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_legend.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_legend.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_legend.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_legend.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_corine_land_cover_legend.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_demand.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_demand.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_demand.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_demand.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_demand.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_distance_to_infrastructure.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_distance_to_infrastructure.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_distance_to_infrastructure.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_distance_to_infrastructure.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_distance_to_infrastructure.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_land_suitability.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_land_suitability.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_land_suitability.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_land_suitability.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_land_suitability.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_local_administrative_units.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_local_administrative_units.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_local_administrative_units.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_local_administrative_units.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_local_administrative_units.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_mobility.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_mobility.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_mobility.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_mobility.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_mobility.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_opportunity.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_opportunity.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_opportunity.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_opportunity.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_opportunity.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_population_2015.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_population_2015.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_population_2015.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_population_2015.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_population_2015.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_1.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_1.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_1.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_1.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_1.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_2.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_2.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_2.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_2.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_2.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_3.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_3.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_3.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_3.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_3.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_4.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_4.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_4.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_4.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_4.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_based_on_corine_land_cover_2006.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_based_on_corine_land_cover_2006.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_based_on_corine_land_cover_2006.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_based_on_corine_land_cover_2006.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_based_on_corine_land_cover_2006.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_corine.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_corine.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_corine.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_corine.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_corine.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_masked.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_masked.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_masked.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_masked.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_potential_masked.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_protected_areas.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_protected_areas.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_protected_areas.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_protected_areas.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_protected_areas.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum_high_provision_near.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum_high_provision_near.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum_high_provision_near.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum_high_provision_near.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_spectrum_high_provision_near.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_unmet_demand.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_unmet_demand.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_unmet_demand.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_unmet_demand.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_unmet_demand.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_water_resources.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_water_resources.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_water_resources.png	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_water_resources.png	2019-03-01 11:47:49 UTC (rev 74143)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/images/r_estimap_recreation_water_resources.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property
Deleted: grass-addons/grass7/raster/r.estimap.recreation/land_suitability.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/local_administrative_units.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/mobility.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/opportunity.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/population_2015.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_1.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_2.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_3.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_4.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_based_on_corine_land_cover_2006.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_corine.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/potential_masked.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/protected_areas.png
===================================================================
(Binary files differ)

Added: grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/Makefile
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/Makefile	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/Makefile	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,7 @@
+MODULE_TOPDIR = ../../..
+
+PGM = r.estimap.recreation
+
+include $(MODULE_TOPDIR)/include/Make/Script.make
+
+default: script

Added: grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.html
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.html	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.html	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,1238 @@
+<h2>DESCRIPTION</h2>
+
+<em>r.estimap.recreation</em> is an implementation of the ESTIMAP recreation algorithm
+to support mapping and modelling of ecosystem services (Zulian, 2014).
+
+<p>
+The algorithm
+estimates the capacity of ecosystems to provide opportunities
+for nature-based recreation
+and leisure
+(recreation opportunity spectrum).
+
+First,
+it bases upon look-up tables,
+to score access to or the quality
+of natural features
+(land suitability, protected areas, infrastructure, water resources)
+for their potential to support for outdoor recreation
+(potential recreation).
+
+Second,
+it implements a proximity-remoteness concept
+to integrate
+the recreation potential
+and the existing infrastructure.
+
+
+<p>
+The module offers two functionalities.
+
+One is the production of recreation related maps by using pre-processed maps
+that depict the quality of or the access to areas of recreational value.
+The other is to transform maps that depict natural features into scored maps
+that reflect the potential to support for outdoor recreational.
+
+<em>Nevertheless, it is strongly advised to understand first the concepts and
+  the terminology behind the algorithm, by reading the related sources.</em>
+
+
+<h5>Terminology</h5>
+
+First, an overview of the terminology
+
+<dl>
+  <dt> Recreation Potential</dt>
+  <dd> is ... </dd>
+  <dt> Recreation Opportunity</dt>
+  <dd> is ... </dd>
+  <dt> Recreation (Opportunity) Spectrum</dt>
+  <dd> is ... </dd>
+  <dt> Demand Distribution</dt>
+  <dd> is ... </dd>
+  <dt> Unmet Demand Distribution</dt>
+  <dd> is ... </dd>
+  <dt> Mobidtty</dt>
+  <dd> is ... </dd>
+  <dt> Flow</dt>
+  <dd> is ... </dd>
+  <dt> Supply</dt>
+  <dd> is ... </dd>
+  <dt> Use</dt>
+  <dd> is ... </dd>
+</dl>
+
+<h5>Recreation Potential</h5>
+
+<p>
+The recreation potential map,
+derives by adding and normalizing
+maps of natural <i>components</i>
+that may provide recreation opportunities.
+
+Components are user-defined, pre-processed, input raster maps,
+that score access to or quality of resources such as:
+
+<ul>
+  <li> land</li>
+  <li> natural</li>
+  <li> water</li>
+  <li> urban</li>
+</ul>
+
+<p>
+Alternatively,
+the module treats unprocessed maps,
+by providing a set of relevant scores or coefficients,
+to derive component maps required by the algorithm.
+
+
+FIXME
+1. an ASCII file with a set of land suitability scores (see below)
+2. a string listing a set of comma-separated scores for each raster category.. -- FIXME
+3. in the case of the CORINE map, use of internal rules
+FIXME
+
+For example,
+a CORINE land cover map
+may be given to the 'landuse' input option
+along with a set of land suitability scores,
+that correspond to the CORINE nomenclature.
+The latter is fed as an ASCII file
+to the 'suitability_scores' input option.
+
+<h5>Recreation Opportunity</h5>
+
+<p>
+...
+
+<h5>Recreation Spectrum</h5>
+
+<p>
+The recreation (opportunity) spectrum map,
+derives by combining the recreation potential
+and maps that depict access (i.e. infrastructure) and/or
+areas that provide opportunities for recreational activities.
+
+<p>
+<strong>Explain here</strong>
+significance of areas with the
+<em>Highest Recreation Spectrum</em>.
+
+<p>
+<div class="tg-wrap"><table>
+  <tr>
+    <th>Potential | Opportunity</th>
+    <th>Near</th>
+    <th>Midrange</th>
+    <th>Far</th>
+  </tr>
+  <tr>
+    <td>Near</td>
+    <td>1</td>
+    <td>2</td>
+    <td>3</td>
+  </tr>
+  <tr>
+    <td>Midrange</td>
+    <td>4</td>
+    <td>5</td>
+    <td>6</td>
+  </tr>
+  <tr>
+    <td>Far</td>
+    <td>7</td>
+    <td>8</td>
+    <td>9</td>
+  </tr>
+</table></div>
+
+<h5>Flow, Supply and Use</h5>
+
+<p>
+By integrating
+maps of
+regions of interest
+and population,
+the module
+supports the production of
+a series of
+<i>demand</i>
+and <i>flow</i> maps
+as well as exporting
+related <i>supply</i> and <i>use</i> tables.
+
+<h3>Mathematical Background</h3>
+
+<p>
+The following equation represents the logic behind ESTIMAP:
+<pre>
+Recreation <strong>Spectrum</strong> = Recreation <strong>Potential</strong> + Recreation <strong>Opportunity</strong>
+</pre>
+
+<h5>Remoteness and Proximity</h5>
+
+The base
+<em>distance</em>
+function
+to quantify
+<em>attractiveness</em>, is:
+
+<div class="code"><pre>
+( {Constant} + {Kappa} ) / ( {Kappa} + exp({alpha} * {Variable}) )
+</pre></div>
+
+where
+
+<ul>
+  <li>Constant</li>
+  <li>Coefficients
+    <ul>
+      <li>Kappa</li>
+      <li>Alpha</li>
+    </ul>
+  </li>
+  <li>Variable</li>
+</ul>
+
+<h5>Accessibility</h5>
+
+<h5>Normalization</h5>
+
+Each <em>component</em> is normalized.
+
+That is,
+all maps listed in a given component
+are summed up and normalised.
+Normalizing any raster map,
+be it a single map
+or the sum of a series of maps,
+is performed by
+subtracting its minimum value
+and dividing by its range.
+
+<h2>EXAMPLES</h2>
+
+For the sake of demonstrating the usage of the module,
+we use the following "component" maps
+<ul>
+  <li><pre>area_of_interest</pre></li>
+  <li><pre>land_suitability</pre></li>
+  <li><pre>water_resources</pre></li>
+  <li><pre>protected_areas</pre></li>
+</ul>
+(available to download at: ...)
+to derive a recreation <i>potential</i> map.
+
+<p>
+Below,
+a table overviewing
+all input and output maps
+used or produced in the examples.
+
+<p>
+<div class="tg-wrap"><table>
+  <tr>
+    <th></th>
+    <th>Input map name</th>
+    <th>Spatial Resolution</th>
+    <th>Remarks</th>
+  </tr>
+  <tr>
+    <td></td>
+    <td>area_of_interest</td>
+    <td>50 m</td>
+    <td>A map that can be used as a 'mask'</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>land_suitability</td>
+    <td>50 m</td>
+    <td>A map scoring the potential for recreation over CORINE land classes</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>water_resources</td>
+    <td>50 m</td>
+    <td>A map scoring access to water resources</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>protected_areas</td>
+    <td>50 m</td>
+    <td>A map scoring the recreational value of natural protected areas</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>distance_to_infrastructure</td>
+    <td>50 m</td>
+    <td>A map scoring access to infrastructure</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>population_2015</td>
+    <td>1000 m</td>
+    <td>The resolution of the raster map given to the 'populatio' input option
+      will define the resolution of the output maps 'demand', 'unmet' and
+      'flow'</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>local_administrative_unit</td>
+    <td>50 m</td>
+    <td>A rasterised version of Eurostat's Local Administrative Units map</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td></td>
+    <td></td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <th>Output map name</th>
+    <th>Spatial Resolution</th>
+    <th>Remarks</th>
+  </tr>
+  <tr>
+    <td></td>
+    <td>potential<br></td>
+    <td>50 m</td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>potential_1</td>
+    <td>50 m</td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>potential_2</td>
+    <td>50 m</td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>potential_3</td>
+    <td>50 m</td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>potential_4</td>
+    <td>50 m</td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>spectrum</td>
+    <td>50 m</td>
+    <td></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>opportunity</td>
+    <td>50 m</td>
+    <td>Requires to request for the 'spectrum' output</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>demand</td>
+    <td>1000 m</td>
+    <td>Depends on the 'flow' map which, in turn, depends on the 'population' input map</td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>unmet</td>
+    <td>1000 m</td>
+    <td>Depends on the 'flow' map which, in turn, depends on the 'population' input map<br></td>
+  </tr>
+  <tr>
+    <td></td>
+    <td>flow</td>
+    <td>1000 m</td>
+    <td>Depends on the 'population' input map</td>
+  </tr>
+  <tr>
+    <td></td>
+    <th>Output table name</th>
+    <th></th>
+    <th></th>
+  </tr>
+  <tr>
+    <td></td>
+    <td>supply</td>
+    <td>NA</td>
+    <td></td>
+  </tr>
+</table></div>
+
+<p>
+<div>
+<center>
+  <img src="images/area_of_interest.png" alt="Example of a land suitability input map">
+  <img src="images/land_suitability.png" alt="Example of a land suitability input map">
+  <img src="images/water_resources.png" alt="Example of a water resources input map">
+  <img src="images/protected_areas.png" alt="Example of a protected areas input map">
+</center>
+</div>
+
+
+<p>
+Before anything, we need to define the extent of interest using
+<div class="code"><pre>
+g.region  raster=area_of_interest
+</pre></div>
+
+
+<h3>Using pre-processed maps</h3>
+
+The first four input options of the module are designed to receive
+pre-processed input maps that classify as either
+<code>land</code>,
+<code>natural</code>,
+<code>water</code>
+and <code>infrastructure</code>
+resources.
+
+<h4>Potential</h4>
+
+<p>
+To compute a <em>potential</em> output map,
+the simplest possible command call
+requires the user
+to define the input map option
+<code>land</code> and
+define a name for the output map option
+<code>potential</code>.
+
+Using a pre-processed map
+that depicts the suitability of different land types
+to support for recreation
+(here the map named
+<code>land_suitability</code>)
+the command to execute is:
+
+<div class="code"><pre>
+r.estimap.recreation  land=land_suitability  potential=potential
+</pre></div>
+
+<p>
+<center>
+<img src="images/potential.png" alt="Example of a recreation potential output map">
+</center>
+
+<p>
+Note,
+this will process the input map
+<code>land_suitability</code>
+over the extent defined previously via
+<code>g.region</code>,
+which is the standard behaviour in GRASS GIS.
+
+<p>
+To exclude certain areas from the computations,
+we may use a raster map
+as a mask and feed it to the input map
+option <code>mask</code>:
+
+<div class="code"><pre>
+r.estimap.recreation  land=land_suitability  mask=area_of_interest  potential=potential_1
+</pre></div>
+
+<p>
+<center>
+<img src="images/potential_1.png" alt="Example of a recreation potential output map while using a MASK">
+</center>
+
+The use of a mask
+(in GRASS GIS' terminology known as <strong>MASK</strong>)
+will ignore areas of <strong>No Data</strong>
+(pixels in the
+<code>area_of_interest</code>
+map assigned the NULL value).
+Successively,
+these areas will be empty in the output map
+<code>potential_1</code>.
+
+Actually,
+the same effect can be achieved
+by using GRASS GIS' native mask creation module <code>r.mask</code>
+and feed it with a raster map of interest.
+The result will be a raster map named <strong>MASK</strong>
+whose presence acts as a filter.
+
+In the following examples,
+it becomes obvious that
+if a single input map
+features such <strong>No Data</strong> areas,
+they will be propagated in the output map.
+
+<p>
+Nonetheless, it is good practice to use a <code>MASK</code>
+when one needs to ensure
+the exclusion of undesired areas
+from any computations.
+
+Also,
+note the <code>--o</code> flag:
+it is required to overwrite
+the already existing map named
+<code>potential_1</code>.
+
+<p>
+Next, we add a water component, a map named
+<code>water_resources</code>,
+modify the output map name to <code>potential_2</code>
+and execute again, without a mask:
+
+<div class="code"><pre>
+r.estimap.recreation  land=land_suitability  water=water_resources  potential=potential_2
+</pre></div>
+
+<p>
+<center>
+<img src="images/potential_2.png" alt="Example of a recreation potential output map while using a MASK, a land suitability map and a water resources map">
+</center>
+
+At this point it becomes clear that all <code>NULL</code> cells present in the
+"water" map, are propagated in the output map <code>potential_2</code>.
+
+<p>
+Following, we provide a map of protected areas named
+<code>protected_areas</code>,
+modify the output map name to
+<code>potential_3</code>
+and repeat the command execution:
+
+<div class="code"><pre>
+r.estimap.recreation  land=land_suitability  water=water_resources  natural=protected_areas  potential=potential_3
+</pre></div>
+
+<p>
+<center>
+<img src="images/potential_3.png" alt="Example of a recreation potential output map
+while using a MASK, a land suitability map, a water resources map and a natural
+resources map">
+</center>
+
+<p>
+While the <code>land</code> option
+accepts only one map as an input,
+both the <code>water</code> and the <code>natural</code> options
+accept multiple maps as inputs.
+
+In example,
+we add a second map named
+<code>bathing_water_quality</code>
+to the water component and modify the output map name to
+<code>potential_4</code>:
+<div class="code"><pre>
+r.estimap.recreation  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  potential=potential_4
+</pre></div>
+
+
+<p>
+In general, arbitrary number of maps,
+separated by comma,
+may be added to options that accept multiple inputs.
+
+<p>
+<center>
+<img src="images/potential_4.png" alt="Example of a recreation potential output map
+while using a MASK, a land suitability map, two water resources maps and a natural
+resources map">
+</center>
+
+<p>
+This example, features also a title and a legend, so as to make sense of the
+map.
+
+<div class="code"><pre>
+d.rast  potential_4
+d.legend  -c  -b  potential_4  at=0,15,0,1  border_color=white
+d.text  text="Potential"  bgcolor=white
+</pre></div>
+
+
+<p>
+The different output map names
+are purposefully selected
+so as to enable a visual
+comparison of the differences
+among the differenct examples.
+
+The output maps
+<code>potential_1</code>,
+<code>potential_2</code>,
+<code>potential_3</code>
+and <code>potential_4</code>
+range within [0,3].
+Yet, they differ in the distribution of values
+due to the different set of input maps.
+
+<p>
+All of the above examples
+base upon pre-processed maps
+that score the access to and quality of
+land, water and natural resources.
+
+For using <i>raw</i>, unprocessed maps,
+read section <b>Using unprocessed maps</b>.
+
+<h4>Spectrum</h4>
+
+To derive a map
+with the recreation (opportunity)
+<code>spectrum</code>,
+we need in addition an
+<code>infrastructure</code>
+component.
+
+In this example a map that scores distance to infrastructure (such as the road
+network) named
+<code>distance_to_infrastructure</code>
+is defined as an input:
+
+<p>
+<center>
+<img src="images/distance_to_infrastructure.png" alt="Example of an input map showing distances to infrastructure">
+</center>
+
+Naturally,
+we need to define the output map option
+<code>spectrum</code> too:
+
+<div class="code"><pre>
+r.estimap.recreation  \
+  land=land_suitability \
+  water=water_resources,bathing_water_quality \
+  natural=protected_areas \
+  infrastructure=distance_to_infrastructure
+  spectrum=spectrum  \
+</pre></div>
+
+or, the same command in a copy-paste friendly way:
+<div class="code"><pre>
+r.estimap.recreation  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  spectrum=spectrum
+</pre></div>
+
+<p>
+<center>
+<img src="images/spectrum.png" alt="Example of a recreation spectrum output map
+while using a MASK, a land suitability map, a water resources map and a natural
+resources map">
+</center>
+
+Missing to define the
+<code>infrastructure</code> map,
+the command will abort and inform about.
+
+<p>
+The image above, was produced via the following native GRASS GIS commands
+<div class="code"><pre>
+d.rast  spectrum
+d.legend  -c  -b  spectrum  at=0,30,0,1  border_color=white
+d.text  text="Spectrum"  bgcolor=white
+</pre></div>
+
+<h5>Opportunity</h5>
+
+The <code>opportunity</code> map
+is actually an intermediate step
+of the algorithm.
+The option to output this map
+<code>opportunity</code>
+is meant for expert users
+who want to explore
+the fundamentals of the processing steps.
+Hence,
+it requires to define
+the output option <code>spectrum</code>
+map as well.
+
+Building upon the previous command, we add the <code>opportunity</code> output
+option:
+
+<div class="code"><pre>
+r.estimap.recreation  \
+  mask=area_of_interest \
+  land=land_suitability \
+  water=water_resources,bathing_water_quality \
+  natural=protected_areas \
+  spectrum=spectrum  \
+  infrastructure=distance_to_infrastructure \
+  opportunity=opportunity
+</pre></div>
+
+or, the same command in a copy-paste friendly way:
+<div class="code"><pre>
+r.estimap.recreation  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  spectrum=spectrum  infrastructure=distance_to_infrastructure  opportunity=opportunity
+</pre></div>
+
+<p>
+<center>
+<img src="images/opportunity.png" alt="Example of a recreation spectrum output map
+while using a MASK, a land suitability map, a water resources map and a natural
+resources map">
+</center>
+
+
+<h4>More input maps</h4>
+
+To derive the outputs
+met <code>demand</code> distributiom,
+<code>unmet</code> demand distributiom
+and the actual <code>flow</code>,
+additional requirements are
+a <code>population</code> map
+and one of boundaries,
+as an input to the option
+<code>base</code>,
+within which to quantify the distribution of the population.
+Using a map of administrative boundaries for the latter option,
+serves for deriving comparable figures across these boundaries.
+
+The algorithm sets internally the spatial resolution
+of all related output maps
+<code>demand</code>,
+<code>unmet</code> and
+<code>flow</code>
+to the spatial resolution of the
+<code>population</code>
+input map.
+
+<p>
+Population
+<center>
+<img src="images/population_2015.png" alt="Fragment of a population map (GHSL, 2015)">
+</center>
+In this example, the population map named <code>population_2015</code> is of
+1000m^2.
+
+
+<p>
+Local administrative units
+<center>
+<img src="images/local_administrative_units.png" alt="Fragment of a local administrative units input map">
+</center>
+The map named
+<code>local_administrative_units</code>
+serves in the following example
+as the base map for the zonal statistics to obtain the demand map.
+
+<h4>Demand</h4>
+
+<div class="code"><pre>
+r.estimap.recreation --o \
+  mask=area_of_interest \
+  land=land_suitability \
+  water=water_resources,bathing_water_quality \
+  natural=protected_areas \
+  infrastructure=distance_to_infrastructure \
+  demand=demand \
+  population=population_2015 \
+  base=local_administrative_units
+</pre></div>
+
+or, the same command in a copy-paste friendly way:
+<div class="code"><pre>
+r.estimap.recreation --o  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  demand=demand  population=population_2015  base=local_administrative_units
+</pre></div>
+
+<p>
+<center>
+<img src="images/demand.png" alt="Example of a demand distribution output map while using
+a MASK
+and inputs for
+land suitability,
+water resources,
+natural resources,
+infrastructure,
+population
+and base">
+</center>
+
+<h4>Unmet Demand</h4>
+
+<div class="code"><pre>
+r.estimap.recreation --o \
+  mask=area_of_interest \
+  land=land_suitability \
+  water=water_resources,bathing_water_quality \
+  natural=protected_areas \
+  infrastructure=distance_to_infrastructure \
+  demand=demand \
+  unmet=unmet_demand \
+  population=population_2015 \
+  base=local_administrative_units
+</pre></div>
+
+or, the same command in a copy-paste friendly way:
+<div class="code"><pre>
+r.estimap.recreation --o  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  demand=demand  unmet=unmet_demand  population=population_2015  base=local_administrative_units
+</pre></div>
+
+<p>
+<center>
+<img src="images/unmet_demand.png" alt="Example of an 'unmet demand' output map while using
+a MASK
+and inputs for
+land suitability,
+water resources,
+natural resources,
+infrastructure,
+population
+and base">
+</center>
+
+<h4>Flow</h4>
+
+<p>
+The
+<i>flow</i>
+bases upon the same function
+used to quantify the attractiveness
+of locations for their recreational value.
+It includes an extra
+<em>score</em>
+term.
+
+<p>
+The computation involves a
+<em>distance</em> map,
+reclassified in 5 categories
+as shown in the following table.
+
+For each distance category,
+a unique pair of coefficient values
+is assigned to the basic equation.
+
+<div class="tg-wrap"><table>
+  <tr>
+    <th>Distance</th>
+    <th>Kappa</th>
+    <th>Alpha</th>
+  </tr>
+  <tr>
+    <td>0 to 1</td>
+    <td>0.02350</td>
+    <td>0.00102</td>
+  </tr>
+  <tr>
+    <td>1 to 2</td>
+    <td>0.02651</td>
+    <td>0.00109</td>
+  </tr>
+  <tr>
+    <td>2 to 3</td>
+    <td>0.05120</td>
+    <td>0.00098</td>
+  </tr>
+  <tr>
+    <td>3 to 4</td>
+    <td>0.10700</td>
+    <td>0.00067</td>
+  </tr>
+  <tr>
+    <td>>4</td>
+    <td>0.06930</td>
+    <td>0.00057</td>
+  </tr>
+</table></div>
+
+<p>
+Note, the last distance category is not considered in deriving the final "map
+of visits".
+
+The output is essentially
+a raster map
+with the distribution of the demand
+per distance category and within predefined geometric boundaries
+
+<p>
+<div class="code"><pre>
+r.estimap.recreation --o \
+  mask=area_of_interest \
+  land=land_suitability \
+  water=water_resources,bathing_water_quality \
+  natural=protected_areas \
+  infrastructure=distance_to_infrastructure \
+  mobility=mobility \
+  population=population_2015 \
+  base=local_administrative_units
+</pre></div>
+
+or, the same command in a copy-paste friendly way:
+<div class="code"><pre>
+r.estimap.recreation --o  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  mobility=mobility  population=population_2015  base=local_administrative_units
+</pre></div>
+
+<p>
+<center>
+<img src="images/mobility.png" alt="Example of a mobility output map while using
+a MASK
+and inputs for
+land suitability,
+water resources,
+natural resources,
+infrastructure,
+population
+and base">
+</center>
+
+<h4>All in one call</h4>
+
+Of course it is possible to derive all output maps with one call:
+<div class="code"><pre>
+r.estimap.recreation --o  \
+  mask=area_of_interest  \
+  land=land_suitability  \
+  water=water_resources,bathing_water_quality  \
+  natural=protected_areas  \
+  infrastructure=distance_to_infrastructure  \
+  potential=potential  \
+  opportunity=opportunity  \
+  spectrum=spectrum  \
+  demand=demand  \
+  unmet=unmet_demand  \
+  mobility=mobility  \
+  population=population_2015  \
+  base=local_administrative_units
+  timestamp='2018'
+</pre></div>
+
+or, the same command in a copy-paste friendly way:
+<div class="code"><pre>
+r.estimap.recreation --o  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  potential=potential  opportunity=opportunity  spectrum=spectrum  demand=demand  unmet=unmet_demand  mobility=mobility  population=population_2015  base=local_administrative_units  timestamp='2018'
+</pre></div>
+
+<p>
+Note the use of
+the <code>timestamp</code>
+parameter!
+This concerns the <code>spectrum</code> map.
+If plans
+include working with GRASS GIS' temporal framework
+on time-series,
+this will be useful.
+
+<h4>Supply and Use tables</h4>
+
+The module can export
+<em>supply</em> and
+<em>use</em> tables,
+in form of CSV files,
+given the identically named
+<code>supply</code>
+and <code>use</code>
+file name output options are defined.
+
+In order to extract a supply table, the module requires maps
+that enable the estimation of the actual flow and how each different ecosystem
+type contributes, in terms of its areal extent, to this flow.
+
+The dependencies to extract a supply table are the following:
+
+<ul>
+  <li>the recreation potential which requires either of, or any
+    combination of, or all of the land, natural and water related
+    components</li>
+
+  <li>the recreation opportunity spectrum which requires any of the above in
+    addition to either of, or any combination of, or all of the inputs related
+    to the infrastructure component</li>
+
+  <li>the demand distribution which requires the combined minimim requirements
+    of the previous items, so as to derive areas of high recreational value,
+    in addition to a population map and a <em>base</em> map of zones, over
+    which to aggregate the population</li>
+
+  <li>and finally the land types (be it a land cover, a land use or a mixture)
+    map so as to estimate the contribution of each land type in the actual flow
+    in addition to a map of zones over which to aggregate the actual flow</li>
+</ul>
+
+Practically and in terms of components (that is pre-processed maps), the
+module requires at minimum the following input options
+
+<ul>
+  <li><code>land</code> or <code>water</code> or <code>natural</code></li>
+  <li><code>infrastructure</code></li>
+  <li><code>population</code></li>
+  <li><code>base</code></li>
+  <li><code>landcover</code></li>
+  <li><code>aggregation</code></li>
+</ul>
+
+and the output option <code>supply</code>
+
+<p>
+An example command to derive a supply table is:
+
+<div class="code"><pre>
+r.estimap.recreation  \
+  land=land_suitability  \
+  infrastructure=distance_to_infrastructure  \
+  population=population_2015  \
+  base=local_administrative_units  \
+  landcover=corine_land_cover_2006  \
+  aggregation=regions  \
+  supply=supply
+</pre></div>
+
+or, instead of the land component, only using the <code>water</code> component
+
+<div class="code"><pre>
+r.estimap.recreation  \
+  water=water_resources  \
+  infrastructure=distance_to_infrastructure  \
+  population=population_2015  \
+  base=local_administrative_units  \
+  landcover=corine_land_cover_2006  \
+  land_classes=corine_accounting_to_maes_land_classes.rules \
+  aggregation=regions  \
+  supply=supply
+</pre></div>
+
+or, instead, using only the <code>natural</code> component:
+
+<div class="code"><pre>
+r.estimap.recreation  \
+  natural=protected_areas  \
+  infrastructure=distance_to_infrastructure  \
+  population=population_2015  \
+  base=local_administrative_units  \
+  landcover=corine_land_cover_2006  \
+  land_classes=corine_accounting_to_maes_land_classes.rules  \
+  aggregation=regions  \
+  supply=supply
+</pre></div>
+
+Here a "real" example:
+
+<div class="code"><pre>
+r.estimap.recreation --o  mask=area_of_interest  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  infrastructure=distance_to_infrastructure  potential=potential  opportunity=opportunity  spectrum=spectrum  demand=demand  unmet=unmet_demand  population=population_2015  base=local_administrative_units timestamp='2018'  landcover=corine_land_cover_2006  aggregation=regions  land_classes=../categories_and_rules/corine_accounting_to_maes_land_classes.rules  supply=supply  use=us
+</pre></div>
+
+which will output the following supply table
+
+<div class="code"><pre>
+base,base_label,cover,cover_label,area,count,percents
+3,Region 3,1,355.747658,6000000.000000,6,6.38%
+3,Region 3,3,216304.146140,46000000.000000,46,48.94%
+3,Region 3,2,26627.415787,46000000.000000,46,48.94%
+1,Region 1,1,1466.340177,11000000.000000,11,9.09%
+1,Region 1,3,13837.701610,10000000.000000,10,8.26%
+1,Region 1,2,105488.837775,88000000.000000,88,72.73%
+1,Region 1,4,902.359018,13000000.000000,13,10.74%
+1,Region 1,7,53.747332,4000000.000000,4,3.31%
+4,Region 4,1,26884.220460,65000000.000000,65,28.26%
+4,Region 4,3,291863.216396,70000000.000000,70,30.43%
+4,Region 4,2,48260.411774,92000000.000000,92,40.00%
+4,Region 4,4,477.251251,7000000.000000,7,3.04%
+2,Region 2,1,1113.270785,11000000.000000,11,10.19%
+2,Region 2,3,157977.541352,58000000.000000,58,53.70%
+2,Region 2,2,7701.208609,29000000.000000,29,26.85%
+2,Region 2,4,3171.919491,15000000.000000,15,13.89%
+5,Region 5,1,27748.714430,37000000.000000,37,44.58%
+5,Region 5,3,133262.033972,31000000.000000,31,37.35%
+5,Region 5,2,2713.756942,15000000.000000,15,18.07%
+5,Region 5,4,677.823622,5000000.000000,5,6.02%
+6,Region 6,1,14377.698637,31000000.000000,31,57.41%
+6,Region 6,3,56746.359740,14000000.000000,14,25.93%
+6,Region 6,2,4117.270100,13000000.000000,13,24.07%
+</pre></div>
+
+The <em>use</em> table can be requested via the <code>use</code> output option.
+
+<h3>Using unprocessed input maps</h3>
+
+<p>
+The module
+offers a pre-processing functionality
+for all of the following input components:
+
+<ul>
+  <li>landuse</li>
+  <li>suitability_scores</li>
+</ul>
+<ul>
+  <li>landcover</li>
+  <li>land_classes</li>
+</ul>
+<ul>
+  <li>lakes</li>
+  <li>lakes_coefficients</li>
+  <li>default is set to: euclidean,1,30,0.008,1</li>
+</ul>
+<ul>
+  <li>coastline</li>
+  <li>coastline_coefficients</li>
+  <li>default is set to: euclidean,1,30,0.008,1</li>
+  <li>coast_geomorphology</li>
+</ul>
+<ul>
+  <li>bathing_water</li>
+  <li>bathing_coefficients</li>
+  <li>default is set to: euclidean,1,5,0.01101</li>
+</ul>
+<ul>
+  <li>protected</li>
+  <li>protected_scores</li>
+  <li>11:11:0,12:12:0.6,2:2:0.8,3:3:0.6,4:4:0.6,5:5:1,6:6:0.8,7:7:0,8:8:0,9:9:0</li>
+</ul>
+<ul>
+  <li>anthropic</li>
+  <li>anthropic_distances</li>
+  <li>0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5</li>
+</ul>
+<ul>
+  <li>roads</li>
+  <li>roads_distances</li>
+  <li>0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5</li>
+</ul>
+
+<p>
+A first look on how this works,
+is to experiment with
+the <code>landuse</code>
+and <code>suitability_scores</code>
+input options.
+
+<p>
+Let's return to the first example, and use a fragment from the unprocessed
+CORINE land data set, instead of the <code>land_suitability</code> map. This
+requires a set of "score" rules, that correspond to the CORINE nomenclature, to
+translate the land cover types into recreation potential.
+
+<p>
+<div>
+  <center>
+  <img src="images/corine_land_cover_2006.png" alt="Fragment from the CORINE land data base ">
+  <img src="images/corine_land_cover_legend.png" alt="Legend for the CORINE land data base">
+  </center>
+</div>
+
+<p>
+In this case,
+the rules are a simple ASCII file
+(for example named <code>corine_suitability.scores</code>
+that contains the following
+
+<div class="code"><pre>
+1:1:0:0
+2:2:0.1:0.1
+3:9:0:0
+10:10:1:1
+11:11:0.1:0.1
+12:13:0.3:0.3
+14:14:0.4:0.4
+15:17:0.5:0.5
+18:18:0.6:0.6
+19:20:0.3:0.3
+21:22:0.6:0.6
+23:23:1:1
+24:24:0.8:0.8
+25:25:1:1
+26:29:0.8:0.8
+30:30:1:1
+31:31:0.8:0.8
+32:32:0.7:0.7
+33:33:0:0
+34:34:0.8:0.8
+35:35:1:1
+36:36:0.8:0.8
+37:37:1:1
+38:38:0.8:0.8
+39:39:1:1
+40:42:1:1
+43:43:0.8:0.8
+44:44:1:1
+45:45:0.3:0.3
+</pre></div>
+
+This file is provided in the <code>suitability_scores</code> option:
+
+<div class="code"><pre>
+r.estimap.recreation  landuse=corine_land_cover_2006 suitability_scores=corine_suitability.scores  potential=potential_corine --o
+</pre></div>
+
+<p>
+<center>
+<img src="images/potential_corine.png" alt="Example of a recreation spectrum output map
+while using a MASK, based on a fragment from the CORINE land data base">
+</center>
+
+The same can be achieved with a long one-line string too:
+
+<div class="code"><pre>
+r.estimap.recreation \
+  landuse=corine_land_cover_2006 \
+  suitability_scores="1:1:0:0,2:2:0.1:0.1,3:9:0:0,10:10:1:1,11:11:0.1:0.1,12:13:0.3:0.3,14:14:0.4:0.4,15:17:0.5:0.5,18:18:0.6:0.6,19:20:0.3:0.3,21:22:0.6:0.6,23:23:1:1,24:24:0.8:0.8,25:25:1:1,26:29:0.8:0.8,30:30:1:1,31:31:0.8:0.8,32:32:0.7:0.7,33:33:0:0,34:34:0.8:0.8,35:35:1:1,36:36:0.8:0.8,37:37:1:1,38:38:0.8:0.8,39:39:1:1,40:42:1:1,43:43:0.8:0.8,44:44:1:1,45:45:0.3:0.3"  potential=potential_1 --o
+</pre></div>
+
+In fact,
+this very scoring scheme,
+for CORINE land data sets,
+is integrated in the module,
+so we obtain the same output
+even by discarding the <code>suitability_scores</code> option:
+
+<div class="code"><pre>
+r.estimap.recreation  landuse=corine_land_cover_2006  suitability_scores=corine_suitability.scores  potential=potential_1 --o
+</pre></div>
+
+This is so
+because CORINE is a standard choice
+among existing land data bases
+that cover european territories.
+
+In case of a user requirement to provide an alternative scoring scheme, all
+what is required is either of
+
+<ul>
+  <li>provide a new "rules" file with the desired set of scoring rules</li>
+  <li>provide a string to the <code>suitability_scores</code> option</li>
+</ul>
+
+<h2>REFERENCES</h2>
+
+<ul>
+  <li>http://publications.jrc.ec.europa.eu/repository/bitstream/JRC87585/lb-na-26474-en-n.pd</li>
+</ul>
+
+<h2>SEE ALSO</h2>
+
+<em>
+<a href="r.univar.html">r.univar</a>,
+<a href="r.mapcalc.html">r.mapcalc</a>
+</em>
+
+<h2>AUTHOR</h2>
+
+Nikos Alexandris
+
+<h2>COPYRIGHT</h2>
+
+Copyright 2018 European Union
+
+Licensed under the EUPL, Version 1.2 or – as soon they will be
+approved by the European Commission – subsequent versions of the
+EUPL (the "Licence");
+
+You may not use this work except in compliance with the Licence.
+You may obtain a copy of the Licence at:
+
+https://joinup.ec.europa.eu/collection/eupl/eupl-text-11-12
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the Licence is distributed on an
+"AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+either express or implied. See the Licence for the specific
+language governing permissions and limitations under the Licence.
+
+Consult the LICENCE file for details.
+
+<p><i>Last changed: $Date: 2017-11-03 18:21:39 +0100 (Fri, 03 Nov 2017) $</i>

Added: grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,668 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+ MODULE:       r.estimap.recreation
+
+ AUTHOR(S):    Nikos Alexandris <nik at nikosalexandris.net>
+
+               First implementation as a collection of Python scripts by
+               Grazia Zulian <Grazia.Zulian at ec.europa.eu>
+
+ PURPOSE:      An implementation of the Ecosystem Services Mapping Tool
+               (ESTIMAP). ESTIMAP is a collection of spatially explicit models
+               to support mapping and modelling of ecosystem services
+               at European scale.
+
+ SOURCES:      https://www.bts.gov/archive/publications/journal_of_transportation_and_statistics/volume_04_number_23/paper_03/index
+
+ COPYRIGHT:    Copyright 2018 European Union
+
+               Licensed under the EUPL, Version 1.2 or – as soon they will be
+               approved by the European Commission – subsequent versions of the
+               EUPL (the "Licence");
+
+               You may not use this work except in compliance with the Licence.
+               You may obtain a copy of the Licence at:
+
+               https://joinup.ec.europa.eu/collection/eupl/eupl-text-11-12
+
+               Unless required by applicable law or agreed to in writing,
+               software distributed under the Licence is distributed on an
+               "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+               either express or implied. See the Licence for the specific
+               language governing permissions and limitations under the Licence.
+
+               Consult the LICENCE file for details.
+"""
+
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import print_function
+
+# --- Flags ---
+
+#%Module
+#%  description: Implementation of ESTIMAP to support mapping and modelling of ecosystem services (Zulian, 2014)
+#%  keywords: estimap
+#%  keywords: ecosystem services
+#%  keywords: recreation potential
+#%End
+
+#%flag
+#%  key: e
+#%  description: Match computational region to extent of land use map
+#%end
+
+#%flag
+#%  key: f
+#%  description: Filter maps in land and natural components before computing recreation maps
+#%end
+
+#%flag
+#%  key: s
+#%  description: Save temporary maps for debugging
+#%end
+
+#%flag
+#%  key: i
+#%  description: Print out citation and other information
+#%end
+
+#%flag
+#%  key: p
+#%  description: Print out results (i.e. supply table), don't export to file
+#%end
+
+"""
+exclusive: at most one of the options may be given
+required: at least one of the options must be given
+requires: if the first option is given, at least one of the subsequent options must also be given
+requires_all: if the first option is given, all of the subsequent options must also be given
+excludes: if the first option is given, none of the subsequent options may be given
+collective: all or nothing; if any option is given, all must be given
+"""
+
+# --- Components section ---
+
+#%option G_OPT_R_INPUT
+#% key: land
+#% type: string
+#% key_desc: name
+#% label: Input map scoring access to and suitability of land resources for recreation
+#% description: Arbitrary number of maps scoring access to and land resources suitability of land use classes to support recreation activities
+#% required: no
+#% guisection: Components
+#%end
+
+#%option G_OPT_R_INPUTS
+#% key: natural
+#% key_desc: name
+#% label: Input maps scoring access to and quality of inland natural resources
+#% description: Arbitrary number of maps scoring access to and quality of inland natural resources
+#% required: no
+#% guisection: Components
+#%end
+
+#%option G_OPT_R_INPUTS
+#% key: water
+#% key_desc: name
+#% label: Input maps scoring access to and quality of water resources
+#% description: Arbitrary number of maps scoring access to and quality of water resources such as lakes, sea, bathing waters and riparian zones
+#% required: no
+#% guisection: Components
+#%end
+
+#%option G_OPT_R_INPUTS
+#% key: urban
+#% key_desc: name
+#% description: Input maps scoring recreational value of urban surfaces
+#% required: no
+#% guisection: Components
+#%end
+
+#%option G_OPT_R_INPUTS
+#% key: infrastructure
+#% type: string
+#% key_desc: name
+#% label: Input maps scoring infrastructure to reach locations of recreation activities
+#% description: Infrastructure to reach locations of recreation activities [required to derive recreation spectrum map]
+#% required: no
+#% guisection: Components
+#%end
+
+#%option G_OPT_R_INPUTS
+#% key: recreation
+#% type: string
+#% key_desc: name
+#% label: Input maps scoring recreational facilities, amenities and services
+#% description: Recreational opportunities facilities, amenities and services [required to derive recreation spectrum map]
+#% required: no
+#% guisection: Components
+#%end
+
+# ---Land---
+
+#%option G_OPT_R_INPUT
+#% key: landuse
+#% type: string
+#% key_desc: name
+#% label: Input land features map from which to derive suitability for recreation
+#% description: Input to derive suitability of land use classes to support recreation activities. Requires scores, overrides suitability.
+#% required: no
+#% guisection: Land
+#%end
+
+#%rules
+#%  exclusive: land
+#%end
+
+#%option G_OPT_F_INPUT
+#% key: suitability_scores
+#% type: string
+#% key_desc: filename
+#% label: Input recreational suitability scores for the categories of the 'landuse' map
+#% description: Scores for suitability of land to support recreation activities. Expected are rules for `r.recode` that correspond to categories of the input 'landuse' map. If the 'landuse' map is given and 'suitability_scores not provided, the module will use internal rules for the CORINE land classes.
+#% required: no
+#% guisection: Land
+#%end
+
+#%rules
+#%  excludes: land, suitability_scores
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: landcover
+#% type: string
+#% key_desc: name
+#% label: Input land cover map from which to derive cover percentages within zones of high recreational value
+#% description: Input to derive percentage of land classes within zones of high recreational value.
+#% required: no
+#% guisection: Land
+#%end
+
+#%option G_OPT_F_INPUT
+#% key: land_classes
+#% type: string
+#% key_desc: filename
+#% label: Input reclassification rules for the classes of the 'landcover' map
+#% description: Expected are rules for `r.reclass` that correspond to classes of the input 'landcover' map. If 'landcover' map is given and 'land_classess' not provided, the module will use internal rules for the Urban Atlas land classes
+#% required: no
+#% guisection: Land
+#%end
+
+# --- Water ---
+
+#%option G_OPT_R_INPUT
+#% key: lakes
+#% key_desc: name
+#% label: Input map of inland waters resources for which to score accessibility
+#% description: Map of inland water resources to compute proximity for, score accessibility based on a distance function
+#% required: no
+#% guisection: Water
+#%end
+
+#%option
+#% key: lakes_coefficients
+#% type: string
+#% key_desc: Coefficients
+#% label: Input distance function coefficients for the 'lakes' map
+#% description: Distance function coefficients to compute proximity: distance metric, constant, kappa, alpha and score. Refer to the manual for details.
+#% multiple: yes
+#% required: no
+#% guisection: Water
+#% answer: euclidean,1,30,0.008,1
+#%end
+
+##%rules
+##%  requires: lakes, lakes_coefficients
+##%end
+
+#%option G_OPT_R_INPUT
+#% key: coastline
+#% key_desc: name
+#% label: Input sea coast map for which to compute proximity
+#% description: Input map to compute coast proximity, scored based on a distance function
+#% required: no
+#% guisection: Water
+#%end
+
+#%option
+#% key: coastline_coefficients
+#% key_desc: Coefficients
+#% label: Input distance function coefficients for the 'coastline' map
+#% description: Distance function coefficients to compute proximity: distance metric, constant, kappa, alpha and score. Refer to the manual for details.
+#% multiple: yes
+#% required: no
+#% guisection: Water
+#% answer: euclidean,1,30,0.008,1
+#%end
+
+#%rules
+#%  requires: coastline, coastline_coefficients
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: coast_geomorphology
+#% key_desc: name
+#% label: Input map scoring recreation potential in coast
+#% description: Coastal geomorphology, scored as suitable to support recreation activities
+#% required: no
+#% guisection: Water
+#%end
+
+#%rules
+#%  requires: coast_geomorphology, coastline
+#%end
+
+##%option
+##% key: geomorphology_coefficients
+##% key_desc: Coefficients
+##% label: Input distance function coefficients
+##% description: Distance function coefficients to compute proximity: distance metric, constant, kappa, alpha and score. Refer to the manual for details.
+##% multiple: yes
+##% required: no
+##% guisection: Water
+##% answer: euclidean,1,30,0.008,1
+##%end
+
+##%rules
+##%  requires: coast_geomorphology, geomorphology_coefficients
+##%end
+
+#%option G_OPT_R_INPUT
+#% key: bathing_water
+#% key_desc: name
+#% label: Input bathing water quality map
+#% description: Bathing water quality index. The higher, the greater is the recreational value.
+#% required: no
+#% guisection: Water
+#%end
+
+#%option
+#% key: bathing_coefficients
+#% type: string
+#% key_desc: Coefficients
+#% label: Input distance function coefficients for the 'bathing_water' map
+#% description: Distance function coefficients to compute proximity to bathing waters: distance metric, constant, kappa and alpha. Refer to the manual for details.
+#% multiple: yes
+#% required: no
+#% guisection: Water
+#% answer: euclidean,1,5,0.01101
+#%end
+
+#%rules
+#%  requires: bathing_water, bathing_coefficients
+#%end
+
+##%rules
+##%  exclusive: lakes
+##%  exclusive: coastline
+##%  excludes: water, coast_geomorphology, coast_proximity, marine, lakes, lakes_proximity, bathing_water
+##%end
+
+# --- Natural ---
+
+#%option G_OPT_R_INPUT
+#% key: protected
+#% key_desc: filename
+#% label: Input protected areas map
+#% description: Input map depicting natural protected areas
+#% required: no
+#% guisection: Natural
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: protected_scores
+#% type: string
+#% key_desc: rules
+#% label: Input recreational value scores for the classes of the 'protected' map
+#% description: Scores for recreational value of designated areas. Expected are rules for `r.recode` that correspond to classes of the input land use map. If the 'protected' map is given and 'protected_scores' are not provided, the module will use internal rules for the IUCN categories.
+#% required: no
+#% guisection: Anthropic
+#% answer: 11:11:0,12:12:0.6,2:2:0.8,3:3:0.6,4:4:0.6,5:5:1,6:6:0.8,7:7:0,8:8:0,9:9:0
+#%end
+
+##%rules
+##% exclusive: natural, protected
+##% exclusive: protected, natural
+###% requires: protected, protected_scores
+##%end
+
+# --- Artificial areas ---
+
+#%option G_OPT_R_INPUT
+#% key: artificial
+#% key_desc: name
+#% label: Input map of artificial surfaces
+#% description: Partial input map to compute proximity to artificial areas, scored via a distance function
+#% required: no
+#% guisection: Water
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: artificial_distances
+#% type: string
+#% key_desc: rules
+#% label: Input distance classification rules
+#% description: Categories for distance to artificial surfaces. Expected are rules for `r.recode` that correspond to distance values in the 'artificial' map
+#% required: no
+#% guisection: Anthropic
+#% answer: 0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5
+#%end
+
+#%rules
+#%  requires: artificial, artificial_distances
+#%end
+
+# --- Road ---
+
+#%option G_OPT_R_INPUT
+#% key: roads
+#% key_desc: name
+#% label: Input map of primary road network
+#% description: Input map to compute roads proximity, scored based on a distance function
+#% required: no
+#% guisection: Infrastructure
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: roads_distances
+##% key: roads_scores
+#% type: string
+#% key_desc: rules
+#% label: Input distance classification rules
+#% description: Categories for distance to roads. Expected are rules for `r.recode` that correspond to distance values in the roads map
+#% required: no
+#% guisection: Anthropic
+#% answer: 0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5
+#%end
+
+#%rules
+#%  requires: roads, roads_distances
+#%  collective: artificial, roads
+#%end
+
+# --- Recreation ---
+
+#######################################################################
+# Offer input for potential?
+
+# #%option G_OPT_R_OUTPUT
+# #% key: recreation_potential
+# #% key_desc: name
+# #% description: Recreation potential map
+# #% required: no
+# #% answer: recreation_potential
+# #% guisection: Components
+# #%end
+
+#
+#######################################################################
+
+## Review the following item's "parsing rules"!
+
+##%rules
+##%  excludes: infrastructure, roads
+##%end
+
+# --- Devaluation ---
+
+#%option G_OPT_R_INPUTS
+#% key: devaluation
+#% key_desc: name
+#% label: Input map of devaluing elements
+#% description: Maps hindering accessibility to and degrading quality of various resources or infrastructure relating to recreation
+#% required: no
+#% guisection: Devaluation
+#%end
+
+# --- MASK ---
+
+#%option G_OPT_R_INPUT
+#% key: mask
+#% key_desc: name
+#% description: A raster map to apply as a MASK
+#% required: no
+#%end
+
+# --- Output ---
+
+#%option G_OPT_R_OUTPUT
+#% key: potential
+#% key_desc: name
+#% label: Output map of recreation potential
+#% description: Recreation potential map classified in 3 categories
+#% required: no
+#% guisection: Output
+#%end
+
+#%rules
+#%  requires: potential, land, natural, water, landuse, protected, lakes, coastline
+#%end
+
+#%option G_OPT_R_OUTPUT
+#% key: opportunity
+#% key_desc: name
+#% label: Output intermediate map of recreation opportunity
+#% description: Intermediate step in deriving the 'spectrum' map, classified in 3 categories, meant for expert use
+#% required: no
+#% guisection: Output
+#%end
+
+#%rules
+#%  requires: opportunity, infrastructure, roads
+#%end
+
+#%option G_OPT_R_OUTPUT
+#% key: spectrum
+#% key_desc: name
+#% label: Output map of recreation spectrum
+#% description: Recreation spectrum map classified by default in 9 categories
+#% required: no
+#% guisection: Output
+#%end
+
+#%rules
+#%  requires: spectrum, infrastructure, roads
+##%  requires: spectrum, landcover
+#%  required: land, landcover, landuse
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: spectrum_distances
+#% type: string
+#% key_desc: rules
+#% label: Input distance classification rules for the 'spectrum' map
+#% description: Classes for distance to areas of high recreational spectrum. Expected are rules for `r.recode` that correspond to classes of the input spectrum of recreation use map.
+#% required: no
+#% guisection: Output
+#% answer: 0:1000:1,1000:2000:2,2000:3000:3,3000:4000:4,4000:*:5
+#%end
+
+# --- Required for Cumulative Opportunity Model ---
+
+#%option G_OPT_R_INPUT
+#% key: base
+#% key_desc: name
+#% description: Input base map for zonal statistics
+#% required: no
+#%end
+
+#%option G_OPT_V_INPUT
+#% key: base_vector
+#% key_desc: name
+#% description: Input base vector map for zonal statistics
+#% required: no
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: aggregation
+#% key_desc: name
+#% description: Input map of regions over which to aggregate the actual flow
+#% required: no
+#%end
+
+#%option G_OPT_R_INPUT
+#% key: population
+#% key_desc: name
+#% description: Input map of population density
+#% required: no
+#%end
+
+#%option G_OPT_R_OUTPUT
+#% key: demand
+#% type: string
+#% key_desc: name
+#% label: Output map of demand distribution
+#% description: Demand distribution output map: population density per Local Administrative Unit and areas of high recreational value
+#% required: no
+#% guisection: Output
+#%end
+
+#%option G_OPT_R_OUTPUT
+#% key: unmet
+#% type: string
+#% key_desc: name
+#% label: Output map of unmet demand distribution
+#% description: Unmet demand distribution output map: population density per Local Administrative Unit and areas of high recreational value
+#% required: no
+#% guisection: Output
+#%end
+
+#%rules
+#%  requires_all: demand, population, base
+#%  requires: demand, infrastructure, artificial, roads
+#%  requires: unmet, demand
+#%end
+
+#%option G_OPT_R_OUTPUT
+#% key: flow
+#% type: string
+#% key_desc: name
+#% label: Output map of flow
+#% description: Flow output map: population (per Local Administrative Unit) near areas of high recreational value
+#% required: no
+#% guisection: Output
+#%end
+
+#%rules
+#%  requires_all: flow, population, base
+#%  requires: flow, infrastructure, artificial, roads
+#%end
+
+#%option G_OPT_F_OUTPUT
+#% key: supply
+#% key_desc: prefix
+#% type: string
+#% label: Output prefix for the file name of the supply table CSV
+#% description: Supply table CSV output file names will get this prefix
+#% multiple: no
+#% required: no
+#% guisection: Output
+#%end
+
+#%option G_OPT_F_OUTPUT
+#% key: use
+#% key_desc: prefix
+#% type: string
+#% label: Output prefix for the file name of the supply table CSV
+#% description: Supply table CSV output file names will get this prefix
+#% multiple: no
+#% required: no
+#% guisection: Output
+#%end
+
+#%rules
+#%  requires: opportunity, spectrum, demand, flow, supply
+#%  required: potential, spectrum, demand, flow, supply, use
+#%end
+
+#%rules
+#%  requires: supply, land, natural, water, landuse, protected, lakes, coastline
+#%  requires_all: supply, population
+#%  requires_all: supply, landcover, land_classes
+#%  requires: supply, base, base_vector, aggregation
+#%  requires: supply, landcover, landuse
+#%  requires_all: use, population
+#%  requires: use, base, base_vector, aggregation
+#%  requires: use, landcover, landuse
+#%end
+
+# --- Various ---
+
+#%option
+#% key: metric
+#% key_desc: Metric
+#% label: Distance metric to areas of highest recreation opportunity spectrum
+#% description: Distance metric to areas of highest recreation opportunity spectrum
+#% multiple: yes
+#% options: euclidean,squared,maximum,manhattan,geodesic
+#% required: no
+#% guisection: Output
+#% answer: euclidean
+#%end
+
+#%option
+#% key: units
+#% key_desc: Units
+#% label: Units to report
+#% description: Units to report the demand distribution
+#% multiple: yes
+#% options: mi,me,k,a,h,c,p
+#% required: no
+#% guisection: Output
+#% answer: k
+#%end
+
+#%option
+#% key: timestamp
+#% type: string
+#% label: Timestamp
+#% description: Timestamp for the recreation potential raster map
+#% required: no
+#%end
+
+# required librairies
+
+import datetime
+import os
+import subprocess
+import sys
+import time
+from pprint import pprint as pp
+
+if "GISBASE" not in os.environ:
+    sys.exit("Exiting: You must be in GRASS GIS to run this program.")
+
+import grass.script as grass
+
+from grass.script.utils import set_path
+try:
+    # set python path to the shared libraries
+    set_path('r.estimap.recreation', 'estimap_recreation', '..')
+    # constants
+    from estimap_recreation.colors import *
+    from estimap_recreation.constants import *
+    from estimap_recreation.labels import *
+    # main
+    from estimap_recreation.main import main as main_estimap
+except ImportError:
+    try:
+        set_path('r.estimap.recreation', 'estimap_recreation', os.path.join('..', 'etc', 'r.estimap.recreation'))
+        # constants
+        from estimap_recreation.colors import *
+        from estimap_recreation.constants import *
+        from estimap_recreation.labels import *
+        # main
+        from estimap_recreation.main import main as main_estimap
+    except ImportError:
+        gcore.warning('estimap_recreation not in the python path!')
+
+
+def main(options, flags):
+    sys.exit(main_estimap())
+
+if __name__ == "__main__":
+    options, flags = grass.parser()
+    main(options, flags)


Property changes on: grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation/r.estimap.recreation.py
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.html
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.html	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.html	2019-03-01 11:47:49 UTC (rev 74143)
@@ -221,7 +221,7 @@
   <li><pre>water_resources</pre></li>
   <li><pre>protected_areas</pre></li>
 </ul>
-(available to download at: ...) 
+(available to download at: ...)
 to derive a recreation <i>potential</i> map.
 
 <p>
@@ -371,10 +371,10 @@
 <p>
 <div>
 <center>
-  <img src="area_of_interest.png" alt="Example of a land suitability input map">
-  <img src="land_suitability.png" alt="Example of a land suitability input map">
-  <img src="water_resources.png" alt="Example of a water resources input map">
-  <img src="protected_areas.png" alt="Example of a protected areas input map">
+  <img src="images/r_estimap_recreation_area_of_interest.png" alt="Example of a land suitability input map">
+  <img src="images/r_estimap_recreation_land_suitability.png" alt="Example of a land suitability input map">
+  <img src="images/r_estimap_recreation_water_resources.png" alt="Example of a water resources input map">
+  <img src="images/r_estimap_recreation_protected_areas.png" alt="Example of a protected areas input map">
 </center>
 </div>
 
@@ -420,7 +420,7 @@
 
 <p>
 <center>
-<img src="potential.png" alt="Example of a recreation potential output map">
+<img src="images/r_estimap_recreation_potential.png" alt="Example of a recreation potential output map">
 </center>
 
 <p>
@@ -443,7 +443,7 @@
 
 <p>
 <center>
-<img src="potential_1.png" alt="Example of a recreation potential output map while using a MASK">
+<img src="images/r_estimap_recreation_potential_1.png" alt="Example of a recreation potential output map while using a MASK">
 </center>
 
 The use of a mask
@@ -493,7 +493,7 @@
 
 <p>
 <center>
-<img src="potential_2.png" alt="Example of a recreation potential output map while using a MASK, a land suitability map and a water resources map">
+<img src="images/r_estimap_recreation_potential_2.png" alt="Example of a recreation potential output map while using a MASK, a land suitability map and a water resources map">
 </center>
 
 At this point it becomes clear that all <code>NULL</code> cells present in the
@@ -512,7 +512,7 @@
 
 <p>
 <center>
-<img src="potential_3.png" alt="Example of a recreation potential output map
+<img src="images/r_estimap_recreation_potential_3.png" alt="Example of a recreation potential output map
 while using a MASK, a land suitability map, a water resources map and a natural
 resources map">
 </center>
@@ -540,7 +540,7 @@
 
 <p>
 <center>
-<img src="potential_4.png" alt="Example of a recreation potential output map
+<img src="images/r_estimap_recreation_potential_4.png" alt="Example of a recreation potential output map
 while using a MASK, a land suitability map, two water resources maps and a natural
 resources map">
 </center>
@@ -597,7 +597,7 @@
 
 <p>
 <center>
-<img src="distance_to_infrastructure.png" alt="Example of an input map showing distances to infrastructure">
+<img src="images/r_estimap_recreation_distance_to_infrastructure.png" alt="Example of an input map showing distances to infrastructure">
 </center>
 
 Naturally,
@@ -620,7 +620,7 @@
 
 <p>
 <center>
-<img src="spectrum.png" alt="Example of a recreation spectrum output map
+<img src="images/r_estimap_recreation_spectrum.png" alt="Example of a recreation spectrum output map
 while using a MASK, a land suitability map, a water resources map and a natural
 resources map">
 </center>
@@ -673,7 +673,7 @@
 
 <p>
 <center>
-<img src="opportunity.png" alt="Example of a recreation spectrum output map
+<img src="images/r_estimap_recreation_opportunity.png" alt="Example of a recreation spectrum output map
 while using a MASK, a land suitability map, a water resources map and a natural
 resources map">
 </center>
@@ -706,7 +706,7 @@
 <p>
 Population
 <center>
-<img src="population_2015.png" alt="Fragment of a population map (GHSL, 2015)">
+<img src="images/r_estimap_recreation_population_2015.png" alt="Fragment of a population map (GHSL, 2015)">
 </center>
 In this example, the population map named <code>population_2015</code> is of
 1000m^2.
@@ -715,7 +715,7 @@
 <p>
 Local administrative units
 <center>
-<img src="local_administrative_units.png" alt="Fragment of a local administrative units input map">
+<img src="images/r_estimap_recreation_local_administrative_units.png" alt="Fragment of a local administrative units input map">
 </center>
 The map named
 <code>local_administrative_units</code>
@@ -743,7 +743,7 @@
 
 <p>
 <center>
-<img src="demand.png" alt="Example of a demand distribution output map while using
+<img src="images/r_estimap_recreation_demand.png" alt="Example of a demand distribution output map while using
 a MASK
 and inputs for
 land suitability,
@@ -776,7 +776,7 @@
 
 <p>
 <center>
-<img src="unmet_demand.png" alt="Example of an 'unmet demand' output map while using
+<img src="images/r_estimap_recreation_unmet_demand.png" alt="Example of an 'unmet demand' output map while using
 a MASK
 and inputs for
 land suitability,
@@ -871,7 +871,7 @@
 
 <p>
 <center>
-<img src="mobility.png" alt="Example of a mobility output map while using
+<img src="images/r_estimap_recreation_mobility.png" alt="Example of a mobility output map while using
 a MASK
 and inputs for
 land suitability,
@@ -946,7 +946,7 @@
 
   <li>the demand distribution which requires the combined minimim requirements
     of the previous items, so as to derive areas of high recreational value,
-    in addition to a population map and a <em>base</em> map of zones, over 
+    in addition to a population map and a <em>base</em> map of zones, over
     which to aggregate the population</li>
 
   <li>and finally the land types (be it a land cover, a land use or a mixture)
@@ -1110,8 +1110,8 @@
 <p>
 <div>
   <center>
-  <img src="corine_land_cover_2006.png" alt="Fragment from the CORINE land data base ">
-  <img src="corine_land_cover_legend.png" alt="Legend for the CORINE land data base">
+  <img src="images/r_estimap_recreation_corine_land_cover_2006.png" alt="Fragment from the CORINE land data base ">
+  <img src="images/r_estimap_recreation_corine_land_cover_legend.png" alt="Legend for the CORINE land data base">
   </center>
 </div>
 
@@ -1161,7 +1161,7 @@
 
 <p>
 <center>
-<img src="potential_corine.png" alt="Example of a recreation spectrum output map
+<img src="images/r_estimap_recreation_potential_corine.png" alt="Example of a recreation spectrum output map
 while using a MASK, based on a fragment from the CORINE land data base">
 </center>
 

Deleted: grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.py	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -1,4103 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
- MODULE:       r.estimap.recreation
-
- AUTHOR(S):    Nikos Alexandris <nik at nikosalexandris.net>
-
-               First implementation as a collection of Python scripts by
-               Grazia Zulian <Grazia.Zulian at ec.europa.eu>
-
- PURPOSE:      An implementation of the Ecosystem Services Mapping Tool
-               (ESTIMAP). ESTIMAP is a collection of spatially explicit models
-               to support mapping and modelling of ecosystem services
-               at European scale.
-
- SOURCES:      https://www.bts.gov/archive/publications/journal_of_transportation_and_statistics/volume_04_number_23/paper_03/index
-
- COPYRIGHT:    Copyright 2018 European Union
-
-               Licensed under the EUPL, Version 1.2 or – as soon they will be
-               approved by the European Commission – subsequent versions of the
-               EUPL (the "Licence");
-
-               You may not use this work except in compliance with the Licence.
-               You may obtain a copy of the Licence at:
-
-               https://joinup.ec.europa.eu/collection/eupl/eupl-text-11-12
-
-               Unless required by applicable law or agreed to in writing,
-               software distributed under the Licence is distributed on an
-               "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
-               either express or implied. See the Licence for the specific
-               language governing permissions and limitations under the Licence.
-
-               Consult the LICENCE file for details.
-"""
-
-'''Flags'''
-
-#%Module
-#%  description: Implementation of ESTIMAP to support mapping and modelling of ecosystem services (Zulian, 2014)
-#%  keywords: estimap
-#%  keywords: ecosystem services
-#%  keywords: recreation potential
-#%End
-
-#%flag
-#%  key: e
-#%  description: Match computational region to extent of land use map
-#%end
-
-#%flag
-#%  key: f
-#%  description: Filter maps in land and natural components before computing recreation maps
-#%end
-
-#%flag
-#%  key: s
-#%  description: Save temporary maps for debugging
-#%end
-
-#%flag
-#%  key: i
-#%  description: Print out citation and other information
-#%end
-
-#%flag
-#%  key: p
-#%  description: Print out results (i.e. supply table), don't export to file
-#%end
-
-'''
-exclusive: at most one of the options may be given
-required: at least one of the options must be given
-requires: if the first option is given, at least one of the subsequent options must also be given
-requires_all: if the first option is given, all of the subsequent options must also be given
-excludes: if the first option is given, none of the subsequent options may be given
-collective: all or nothing; if any option is given, all must be given
-'''
-
-'''Components section'''
-
-#%option G_OPT_R_INPUT
-#% key: land
-#% type: string
-#% key_desc: name
-#% label: Input map scoring access to and suitability of land resources for recreation
-#% description: Arbitrary number of maps scoring access to and land resources suitability of land use classes to support recreation activities
-#% required : no
-#% guisection: Components
-#%end
-
-#%option G_OPT_R_INPUTS
-#% key: natural
-#% key_desc: name
-#% label: Input maps scoring access to and quality of inland natural resources
-#% description: Arbitrary number of maps scoring access to and quality of inland natural resources
-#% required : no
-#% guisection: Components
-#%end
-
-#%option G_OPT_R_INPUTS
-#% key: water
-#% key_desc: name
-#% label: Input maps scoring access to and quality of water resources
-#% description: Arbitrary number of maps scoring access to and quality of water resources such as lakes, sea, bathing waters and riparian zones
-#% required : no
-#% guisection: Components
-#%end
-
-#%option G_OPT_R_INPUTS
-#% key: urban
-#% key_desc: name
-#% description: Input maps scoring recreational value of urban surfaces
-#% required : no
-#% guisection: Components
-#%end
-
-#%option G_OPT_R_INPUTS
-#% key: infrastructure
-#% type: string
-#% key_desc: name
-#% label: Input maps scoring infrastructure to reach locations of recreation activities
-#% description: Infrastructure to reach locations of recreation activities [required to derive recreation spectrum map]
-#% required: no
-#% guisection: Components
-#%end
-
-#%option G_OPT_R_INPUTS
-#% key: recreation
-#% type: string
-#% key_desc: name
-#% label: Input maps scoring recreational facilities, amenities and services
-#% description: Recreational opportunities facilities, amenities and services [required to derive recreation spectrum map]
-#% required: no
-#% guisection: Components
-#%end
-
-'''Land'''
-
-#%option G_OPT_R_INPUT
-#% key: landuse
-#% type: string
-#% key_desc: name
-#% label: Input land features map from which to derive suitability for recreation
-#% description: Input to derive suitability of land use classes to support recreation activities. Requires scores, overrides suitability.
-#% required : no
-#% guisection: Land
-#%end
-
-#%rules
-#%  exclusive: land
-#%end
-
-#%option G_OPT_F_INPUT
-#% key: suitability_scores
-#% type: string
-#% key_desc: filename
-#% label: Input recreational suitability scores for the categories of the 'landuse' map
-#% description: Scores for suitability of land to support recreation activities. Expected are rules for `r.recode` that correspond to categories of the input 'landuse' map. If the 'landuse' map is given and 'suitability_scores not provided, the module will use internal rules for the CORINE land classes.
-#% required: no
-#% guisection: Land
-#%end
-
-#%rules
-#%  excludes: land, suitability_scores
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: landcover
-#% type: string
-#% key_desc: name
-#% label: Input land cover map from which to derive cover percentages within zones of high recreational value
-#% description: Input to derive percentage of land classes within zones of high recreational value.
-#% required : no
-#% guisection: Land
-#%end
-
-#%option G_OPT_F_INPUT
-#% key: land_classes
-#% type: string
-#% key_desc: filename
-#% label: Input reclassification rules for the classes of the 'landcover' map
-#% description: Expected are rules for `r.reclass` that correspond to classes of the input 'landcover' map. If 'landcover' map is given and 'land_classess' not provided, the module will use internal rules for the Urban Atlas land classes
-#% required: no
-#% guisection: Land
-#%end
-
-'''Water'''
-
-#%option G_OPT_R_INPUT
-#% key: lakes
-#% key_desc: name
-#% label: Input map of inland waters resources for which to score accessibility
-#% description: Map of inland water resources to compute proximity for, score accessibility based on a distance function
-#% required : no
-#% guisection: Water
-#%end
-
-#%option
-#% key: lakes_coefficients
-#% type: string
-#% key_desc: Coefficients
-#% label: Input distance function coefficients for the 'lakes' map
-#% description: Distance function coefficients to compute proximity: distance metric, constant, kappa, alpha and score. Refer to the manual for details.
-#% multiple: yes
-#% required: no
-#% guisection: Water
-#% answer: euclidean,1,30,0.008,1
-#%end
-
-##%rules
-##%  requires: lakes, lakes_coefficients
-##%end
-
-#%option G_OPT_R_INPUT
-#% key: coastline
-#% key_desc: name
-#% label: Input sea coast map for which to compute proximity
-#% description: Input map to compute coast proximity, scored based on a distance function
-#% required : no
-#% guisection: Water
-#%end
-
-#%option
-#% key: coastline_coefficients
-#% key_desc: Coefficients
-#% label: Input distance function coefficients for the 'coastline' map
-#% description: Distance function coefficients to compute proximity: distance metric, constant, kappa, alpha and score. Refer to the manual for details.
-#% multiple: yes
-#% required: no
-#% guisection: Water
-#% answer: euclidean,1,30,0.008,1
-#%end
-
-#%rules
-#%  requires: coastline, coastline_coefficients
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: coast_geomorphology
-#% key_desc: name
-#% label: Input map scoring recreation potential in coast
-#% description: Coastal geomorphology, scored as suitable to support recreation activities
-#% required : no
-#% guisection: Water
-#%end
-
-#%rules
-#%  requires: coast_geomorphology, coastline
-#%end
-
-##%option
-##% key: geomorphology_coefficients
-##% key_desc: Coefficients
-##% label: Input distance function coefficients
-##% description: Distance function coefficients to compute proximity: distance metric, constant, kappa, alpha and score. Refer to the manual for details.
-##% multiple: yes
-##% required: no
-##% guisection: Water
-##% answer: euclidean,1,30,0.008,1
-##%end
-
-##%rules
-##%  requires: coast_geomorphology, geomorphology_coefficients
-##%end
-
-#%option G_OPT_R_INPUT
-#% key: bathing_water
-#% key_desc: name
-#% label: Input bathing water quality map
-#% description: Bathing water quality index. The higher, the greater is the recreational value.
-#% required : no
-#% guisection: Water
-#%end
-
-#%option
-#% key: bathing_coefficients
-#% type: string
-#% key_desc: Coefficients
-#% label: Input distance function coefficients for the 'bathing_water' map
-#% description: Distance function coefficients to compute proximity to bathing waters: distance metric, constant, kappa and alpha. Refer to the manual for details.
-#% multiple: yes
-#% required: no
-#% guisection: Water
-#% answer: euclidean,1,5,0.01101
-#%end
-
-#%rules
-#%  requires: bathing_water, bathing_coefficients
-#%end
-
-##%rules
-##%  exclusive: lakes
-##%  exclusive: coastline
-##%  excludes: water, coast_geomorphology, coast_proximity, marine, lakes, lakes_proximity, bathing_water
-##%end
-
-'''Natural'''
-
-#%option G_OPT_R_INPUT
-#% key: protected
-#% key_desc: filename
-#% label: Input protected areas map
-#% description: Input map depicting natural protected areas
-#% required : no
-#% guisection: Natural
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: protected_scores
-#% type: string
-#% key_desc: rules
-#% label: Input recreational value scores for the classes of the 'protected' map
-#% description: Scores for recreational value of designated areas. Expected are rules for `r.recode` that correspond to classes of the input land use map. If the 'protected' map is given and 'protected_scores' are not provided, the module will use internal rules for the IUCN categories.
-#% required : no
-#% guisection: Anthropic
-#% answer: 11:11:0,12:12:0.6,2:2:0.8,3:3:0.6,4:4:0.6,5:5:1,6:6:0.8,7:7:0,8:8:0,9:9:0
-#%end
-
-##%rules
-##% exclusive: natural, protected
-##% exclusive: protected, natural
-###% requires: protected, protected_scores
-##%end
-
-'''Artificial areas'''
-
-#%option G_OPT_R_INPUT
-#% key: artificial
-#% key_desc: name
-#% label: Input map of artificial surfaces
-#% description: Partial input map to compute proximity to artificial areas, scored via a distance function
-#% required : no
-#% guisection: Water
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: artificial_distances
-#% type: string
-#% key_desc: rules
-#% label: Input distance classification rules
-#% description: Categories for distance to artificial surfaces. Expected are rules for `r.recode` that correspond to distance values in the 'artificial' map
-#% required : no
-#% guisection: Anthropic
-#% answer: 0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5
-#%end
-
-#%rules
-#%  requires: artificial, artificial_distances
-#%end
-
-'''Roads'''
-
-#%option G_OPT_R_INPUT
-#% key: roads
-#% key_desc: name
-#% label: Input map of primary road network
-#% description: Input map to compute roads proximity, scored based on a distance function
-#% required : no
-#% guisection: Infrastructure
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: roads_distances
-##% key: roads_scores
-#% type: string
-#% key_desc: rules
-#% label: Input distance classification rules
-#% description: Categories for distance to roads. Expected are rules for `r.recode` that correspond to distance values in the roads map
-#% required : no
-#% guisection: Anthropic
-#% answer: 0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5
-#%end
-
-#%rules
-#%  requires: roads, roads_distances
-#%  collective: artificial, roads
-#%end
-
-'''Recreation'''
-
-#######################################################################
-# Offer input for potential?
-
-# #%option G_OPT_R_OUTPUT
-# #% key: recreation_potential
-# #% key_desc: name
-# #% description: Recreation potential map
-# #% required: no
-# #% answer: recreation_potential
-# #% guisection: Components
-# #%end
-
-#
-#######################################################################
-
-## Review the following item's "parsing rules"!
-
-##%rules
-##%  excludes: infrastructure, roads
-##%end
-
-'''Devaluation'''
-
-#%option G_OPT_R_INPUTS
-#% key: devaluation
-#% key_desc: name
-#% label: Input map of devaluing elements
-#% description: Maps hindering accessibility to and degrading quality of various resources or infrastructure relating to recreation
-#% required : no
-#% guisection: Devaluation
-#%end
-
-'''MASK'''
-
-#%option G_OPT_R_INPUT
-#% key: mask
-#% key_desc: name
-#% description: A raster map to apply as a MASK
-#% required : no
-#%end
-
-'''Output'''
-
-#%option G_OPT_R_OUTPUT
-#% key: potential
-#% key_desc: name
-#% label: Output map of recreation potential
-#% description: Recreation potential map classified in 3 categories
-#% required: no
-#% guisection: Output
-#%end
-
-#%rules
-#%  requires: potential, land, natural, water, landuse, protected, lakes, coastline
-#%end
-
-#%option G_OPT_R_OUTPUT
-#% key: opportunity
-#% key_desc: name
-#% label: Output intermediate map of recreation opportunity
-#% description: Intermediate step in deriving the 'spectrum' map, classified in 3 categories, meant for expert use
-#% required: no
-#% guisection: Output
-#%end
-
-#%rules
-#%  requires: opportunity, infrastructure, roads
-#%end
-
-#%option G_OPT_R_OUTPUT
-#% key: spectrum
-#% key_desc: name
-#% label: Output map of recreation spectrum
-#% description: Recreation spectrum map classified by default in 9 categories
-#% required: no
-#% guisection: Output
-#%end
-
-#%rules
-#%  requires: spectrum, infrastructure, roads
-##%  requires: spectrum, landcover
-#%  required: land, landcover, landuse
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: spectrum_distances
-#% type: string
-#% key_desc: rules
-#% label: Input distance classification rules for the 'spectrum' map
-#% description: Classes for distance to areas of high recreational spectrum. Expected are rules for `r.recode` that correspond to classes of the input spectrum of recreation use map.
-#% required : no
-#% guisection: Output
-#% answer: 0:1000:1,1000:2000:2,2000:3000:3,3000:4000:4,4000:*:5
-#%end
-
-'''Required for Cumulative Opportunity Model'''
-
-#%option G_OPT_R_INPUT
-#% key: base
-#% key_desc: name
-#% description: Input base map for zonal statistics
-#% required : no
-#%end
-
-#%option G_OPT_V_INPUT
-#% key: base_vector
-#% key_desc: name
-#% description: Input base vector map for zonal statistics
-#% required : no
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: aggregation
-#% key_desc: name
-#% description: Input map of regions over which to aggregate the actual flow
-#% required : no
-#%end
-
-#%option G_OPT_R_INPUT
-#% key: population
-#% key_desc: name
-#% description: Input map of population density
-#% required : no
-#%end
-
-#%option G_OPT_R_OUTPUT
-#% key: demand
-#% type: string
-#% key_desc: name
-#% label: Output map of demand distribution
-#% description: Demand distribution output map: population density per Local Administrative Unit and areas of high recreational value
-#% required : no
-#% guisection: Output
-#%end
-
-#%option G_OPT_R_OUTPUT
-#% key: unmet
-#% type: string
-#% key_desc: name
-#% label: Output map of unmet demand distribution
-#% description: Unmet demand distribution output map: population density per Local Administrative Unit and areas of high recreational value
-#% required : no
-#% guisection: Output
-#%end
-
-#%rules
-#%  requires_all: demand, population, base
-#%  requires: demand, infrastructure, artificial, roads
-#%  requires: unmet, demand
-#%end
-
-#%option G_OPT_R_OUTPUT
-#% key: flow
-#% type: string
-#% key_desc: name
-#% label: Output map of flow
-#% description: Flow output map: population (per Local Administrative Unit) near areas of high recreational value
-#% required : no
-#% guisection: Output
-#%end
-
-#%rules
-#%  requires_all: flow, population, base
-#%  requires: flow, infrastructure, artificial, roads
-#%end
-
-#%option G_OPT_F_OUTPUT
-#% key: supply
-#% key_desc: prefix
-#% type: string
-#% label: Output prefix for the file name of the supply table CSV
-#% description: Supply table CSV output file names will get this prefix
-#% multiple: no
-#% required: no
-#% guisection: Output
-#%end
-
-#%option G_OPT_F_OUTPUT
-#% key: use
-#% key_desc: prefix
-#% type: string
-#% label: Output prefix for the file name of the supply table CSV
-#% description: Supply table CSV output file names will get this prefix
-#% multiple: no
-#% required: no
-#% guisection: Output
-#%end
-
-#%rules
-#%  requires: opportunity, spectrum, demand, flow, supply
-#%  required: potential, spectrum, demand, flow, supply, use
-#%end
-
-#%rules
-#%  requires: supply, land, natural, water, landuse, protected, lakes, coastline
-#%  requires_all: supply, population
-#%  requires_all: supply, landcover, land_classes
-#%  requires: supply, base, base_vector, aggregation
-#%  requires: supply, landcover, landuse
-#%  requires_all: use, population
-#%  requires: use, base, base_vector, aggregation
-#%  requires: use, landcover, landuse
-#%end
-
-'''Various'''
-
-#%option
-#% key: metric
-#% key_desc: Metric
-#% label: Distance metric to areas of highest recreation opportunity spectrum
-#% description: Distance metric to areas of highest recreation opportunity spectrum
-#% multiple: yes
-#% options: euclidean,squared,maximum,manhattan,geodesic
-#% required: no
-#% guisection: Output
-#% answer: euclidean
-#%end
-
-#%option
-#% key: units
-#% key_desc: Units
-#% label: Units to report
-#% description: Units to report the demand distribution
-#% multiple: yes
-#% options: mi,me,k,a,h,c,p
-#% required: no
-#% guisection: Output
-#% answer: k
-#%end
-
-#%option
-#% key: timestamp
-#% type: string
-#% label: Timestamp
-#% description: Timestamp for the recreation potential raster map
-#% required: no
-#%end
-
-# required librairies
-
-import os, sys, subprocess
-import datetime, time
-import csv
-import math
-# import heapq
-
-'''Fake a file-like object from an in-script hardcoded string?'''
-# import StringIO
-# from cStringIO import StringIO
-
-import atexit
-import grass.script as grass
-from grass.exceptions import CalledModuleError
-from grass.pygrass.modules.shortcuts import general as g
-from grass.pygrass.modules.shortcuts import raster as r
-from grass.pygrass.modules.shortcuts import vector as v
-
-if "GISBASE" not in os.environ:
-    g.message(_("You must be in GRASS GIS to run this program."))
-    sys.exit(1)
-
-# from scoring_schemes import corine
-
-# globals
-
-global grass_render_directory
-grass_render_directory = "/geo/grassdb/render"
-
-global equation, citation, spacy_plus
-citation_recreation_potential='Zulian (2014)'
-spacy_plus = ' + '
-equation = "{result} = {expression}"  # basic equation for mapcalc
-
-global THRESHHOLD_ZERO, THRESHHOLD_0001, THRESHHOLD_0003
-THRESHHOLD_ZERO = 0
-THRESHHOLD_0001 = 0.0001
-
-global CSV_EXTENSION, COMMA, EUCLIDEAN, NEIGHBORHOOD_SIZE, NEIGHBORHOOD_METHOD
-CSV_EXTENSION = '.csv'
-COMMA='comma'
-EUCLIDEAN='euclidean'
-# units='k'
-NEIGHBORHOOD_SIZE = 11  # this and below, required for neighborhood_function
-NEIGHBORHOOD_METHOD = 'mode'
-
-WATER_PROXIMITY_CONSTANT = 1
-WATER_PROXIMITY_KAPPA = 30
-WATER_PROXIMITY_ALPHA = 0.008
-WATER_PROXIMITY_SCORE = 1
-BATHING_WATER_PROXIMITY_CONSTANT = 1
-BATHING_WATER_PROXIMITY_KAPPA = 5
-BATHING_WATER_PROXIMITY_ALPHA = 0.1101
-
-SUITABILITY_SCORES='''1:1:0:0
-2:2:0.1:0.1
-3:9:0:0
-10:10:1:1
-11:11:0.1:0.1
-12:13:0.3:0.3
-14:14:0.4:0.4
-15:17:0.5:0.5
-18:18:0.6:0.6
-19:20:0.3:0.3
-21:22:0.6:0.6
-23:23:1:1
-24:24:0.8:0.8
-25:25:1:1
-26:29:0.8:0.8
-30:30:1:1
-31:31:0.8:0.8
-32:32:0.7:0.7
-33:33:0:0
-34:34:0.8:0.8
-35:35:1:1
-36:36:0.8:0.8
-37:37:1:1
-38:38:0.8:0.8
-39:39:1:1
-40:42:1:1
-43:43:0.8:0.8
-44:44:1:1
-45:45:0.3:0.3'''
-
-SUITABILITY_SCORES_LABELS='''1 thru 1 = 1 0
-2 thru 2 = 2 0.1
-3 thru 9 = 9 0
-10 thru 10 = 10 1
-11 thru 11 = 11 0.1
-12 thru 13 = 13 0.3
-14 thru 14 = 14 0.4
-15 thru 17 = 17 0.5
-18 thru 18 = 18 0.6
-19 thru 20 = 20 0.3
-21 thru 22 = 22 0.6
-23 thru 23 = 23 1
-24 thru 24 = 24 0.8
-25 thru 25 = 25 1
-26 thru 29 = 29 0.8
-30 thru 30 = 30 1
-31 thru 31 = 31 0.8
-32 thru 32 = 32 0.7
-33 thru 33 = 33 0
-34 thru 34 = 34 0.8
-35 thru 35 = 35 1
-36 thru 36 = 36 0.8
-37 thru 37 = 37 1
-38 thru 38 = 38 0.8
-39 thru 39 = 39 1
-40 thru 42 = 42 1
-43 thru 43 = 43 0.8
-44 thru 44 = 44 1
-45 thru 45 = 45 0.3'''
-
-URBAN_ATLAS_CATEGORIES = '''11100
-11200
-12100
-12200
-12300
-12400
-13100
-13200
-13300
-14100
-14200
-21100
-21200
-21300
-22100
-22200
-22300
-23100
-24100
-24200
-24300
-24400
-31100
-31200
-31300
-32100
-32200
-32300
-32400
-33100
-33200
-33300
-33400
-33500
-41100
-41200
-42100
-42200
-42300
-'''
-
-URBAN_ATLAS_TO_MAES_NOMENCLATURE='''
-11100 = 1 Urban
-11210 = 1 Urban
-11220 = 1 Urban
-11230 = 1 Urban
-11240 = 1 Urban
-11300 = 1 Urban
-12100 = 1 Urban
-12210 = 1 Urban
-12220 = 1 Urban
-12230 = 1 Urban
-12300 = 1 Urban
-12400 = 1 Urban
-13100 = 1 Urban
-13300 = 1 Urban
-13400 = 1 Urban
-14100 = 1 Urban
-14200 = 1 Urban
-21000 = 2 Cropland
-22000 = 2 Cropland
-23000 = 2 Cropland
-25400 = 2 Cropland
-31000 = 3 Woodland and forest
-32000 = 3 Woodland and forest
-33000 = 3 Woodland and forest
-40000 = 4 Grassland
-50000 = 5 Heathland and shrub
-'''
-
-RECREATION_POTENTIAL_CATEGORIES = '''0.0:0.2:1
-0.2:0.4:2
-0.4:*:3'''
-#artificial_distance_categories=
-#'0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:*:5'
-RECREATION_OPPORTUNITY_CATEGORIES = RECREATION_POTENTIAL_CATEGORIES
-
-#
-## FIXME -- No hardcodings please.
-#
-
-POTENTIAL_CATEGORY_LABELS = '''1:Low
-2:Moderate
-3:High
-'''
-
-OPPORTUNITY_CATEGORY_LABELS = '''1:Far
-2:Midrange
-3:Near
-'''
-
-SPECTRUM_CATEGORY_LABELS = '''1:Low provision (far)
-2:Low provision (midrange)
-3:Low provision (near)
-4:Moderate provision (far)
-5:Moderate provision (midrange)
-6:Moderate provision (near)
-7:High provision (far)
-8:High provision (midrange)
-9:High provision (near)
-'''
-
-SPECTRUM_DISTANCE_CATEGORY_LABELS = '''1:0 to 1 km
-2:1 to 2 km
-3:2 to 3 km
-4:3 to 4 km
-5:>4 km
-'''
-
-#
-## FIXME -- No hardcodings please.
-#
-
-HIGHEST_RECREATION_CATEGORY = 9
-
-SCORE_COLORS = """ # http://colorbrewer2.org/?type=diverging&scheme=RdYlGn&n=11
-0.0% 165:0:38
-10.0% 215:48:39
-20.0% 244:109:67
-30.0% 253:174:97
-40.0% 254:224:139
-50.0% 255:255:191
-60.0% 217:239:139
-70.0% 166:217:106
-80.0% 102:189:99
-90.0% 26:152:80
-100.0% 0:104:55"""
-
-POTENTIAL_COLORS = """ # Cubehelix color table generated using:
-#   r.colors.cubehelix -dn ncolors=3 map=recreation_potential nrotations=0.33 gamma=1.5 hue=0.9 dark=0.3 output=recreation_potential.colors
-0.000% 55:29:66
-33.333% 55:29:66
-33.333% 157:85:132
-66.667% 157:85:132
-66.667% 235:184:193
-100.000% 235:184:193"""
-
-OPPORTUNITY_COLORS = """# Cubehelix color table generated using:
-#   r.colors.cubehelix -dn ncolors=3 map=recreation_potential nrotations=0.33 gamma=1.5 hue=0.9 dark=0.3 output=recreation_potential.colors
-0.000% 55:29:66
-33.333% 55:29:66
-33.333% 157:85:132
-66.667% 157:85:132
-66.667% 235:184:193
-100.000% 235:184:193"""
-
-SPECTRUM_COLORS = """# Cubehelix color table generated using:
-#   r.colors.cubehelix -dn ncolors=9 map=recreation_spectrum nrotations=0.33 gamma=1.5 hue=0.9 dark=0.3 output=recreation_spectrum.colors
-0.000% 55:29:66
-11.111% 55:29:66
-11.111% 79:40:85
-22.222% 79:40:85
-22.222% 104:52:102
-33.333% 104:52:102
-33.333% 131:67:118
-44.444% 131:67:118
-44.444% 157:85:132
-55.556% 157:85:132
-55.556% 180:104:145
-66.667% 180:104:145
-66.667% 202:128:159
-77.778% 202:128:159
-77.778% 221:156:175
-88.889% 221:156:175
-88.889% 235:184:193
-100.000% 235:184:193"""
-
-MOBILITY_CONSTANT = 1
-MOBILITY_COEFFICIENTS = { 0 : (0.02350, 0.00102),
-                          1 : (0.02651, 0.00109),
-                          2 : (0.05120, 0.00098),
-                          3 : (0.10700, 0.00067),
-                          4 : (0.06930, 0.00057)}
-MOBILITY_SCORE = 52
-MOBILITY_COLORS = 'wave'
-LANDCOVER_FRACTIONS_COLOR='wave'
-
-METHODS='sum'
-
-# helper functions
-
-def run(cmd, **kwargs):
-    """Pass required arguments to grass commands (?)"""
-    grass.run_command(cmd, quiet=True, **kwargs)
-
-def tmp_map_name(**kwargs):
-    """Return a temporary map name, for example:
-
-    Parameters
-    ----------
-    name :
-        Name of input raster map
-
-    Returns
-    -------
-    temporary_filename :
-        A temporary file name for the input raster map
-
-    Examples
-    --------
-    >>> tmp_map_name(potential)
-    tmp.SomeTemporaryString.potential
-    """
-    temporary_absolute_filename = grass.tempfile()
-    temporary_filename = "tmp." + grass.basename(temporary_absolute_filename)
-    if 'name' in kwargs:
-        name = kwargs.get('name')
-        temporary_filename = temporary_filename + '.' + str(name)
-    return temporary_filename
-
-def cleanup():
-    """Clean up temporary maps"""
-
-    if rename_at_exit:
-        if demand in rename_at_exit:
-            g.rename(raster=(demand_copy,demand))
-
-    # get list of temporary maps
-    temporary_raster_maps = grass.list_strings(type='raster',
-            pattern='tmp.{pid}*'.format(pid=os.getpid()))
-
-    # inform
-    if any([temporary_raster_maps, remove_at_exit,
-        remove_normal_files_at_exit]):
-        g.message("Removing temporary files")
-
-        # remove temporary maps
-        if temporary_raster_maps:
-            g.remove(flags='f',
-                    type="raster",
-                    pattern='tmp.{pid}*'.format(pid=os.getpid()),
-                    quiet=True)
-
-        # remove raster and vector maps in handcrafted list
-        if remove_at_exit:
-            g.remove(flags='f',
-                    type=('raster','vector'),
-                    name=','.join(remove_at_exit),
-                    quiet=True)
-
-        # remove normal files at OS level
-        if remove_normal_files_at_exit:
-            for item in remove_normal_files_at_exit:
-                os.unlink(item)
-
-    # # remove MASK ? FIXME
-    # if grass.find_file(name='MASK', element='cell')['file']:
-    #     r.mask(flags='r', verbose=True)
-
-def string_to_file(string, **kwargs):
-    """Split series of strings separated by comma in lines and write as an
-    ASCII file
-
-    Parameters
-    ----------
-    string :
-        A string where commas will be replaced by a newline
-
-    kwargs :
-        Optional key-word argument 'name'
-
-    Returns
-    -------
-    filename :
-        Name of the ASCII file into where the transformed string is written
-
-    Examples
-    --------
-
-    """
-    string = string.split(',')
-    string = '\n'.join(string)
-    # string = string.splitlines()
-
-    msg = "String split in lines: {s}".format(s=string)
-    grass.debug(_(msg))
-
-    if 'name' in kwargs:
-        name = kwargs.get('name')
-    filename = tmp_map_name(name=name)
-
-    # # Use a file-like object instead?
-    # import tempfile
-    # ascii_file = tempfile.TemporaryFile()
-
-    try:
-        ascii_file = open(filename, "w")
-        ascii_file.writelines(string)
-        # ascii_file.seek(0)  # in case of a file-like object
-
-    # if DEBUG, then do:
-        # for line in ascii_file:
-        #     grass.debug(_(line.rstrip()))
-
-    except IOError as error:
-        print ("IOError :", error)
-        return
-
-    finally:
-        ascii_file.close()
-        return filename  # how would that work with a file-like object?
-        # Will be removed right after `.close()` -- How to possibly re-use it
-            # outside the function?
-        # Wrap complete main() in a `try` statement?
-
-def save_map(mapname):
-    """Helper function to save some in-between maps, assisting in debugging
-
-    Parameters
-    ----------
-    mapname :
-        ...
-
-    Returns
-    -------
-    newname :
-        New name for the input raster map
-
-    Examples
-    --------
-    """
-    # run('r.info', map=mapname, flags='r')
-    # run('g.copy', raster=(mapname, 'DebuggingMap'))
-
-    #
-    # Needs re-design! FIXME
-    #
-
-    newname = mapname
-    if save_temporary_maps:
-        newname = 'output_' + mapname
-        run('g.rename', raster=(mapname, newname))
-    return newname
-
-def merge_two_dictionaries(a, b):
-    """Merge two dictionaries in via shallow copy.
-    Source: https://stackoverflow.com/a/26853961/1172302"""
-    merged_dictionary = a.copy()
-    merged_dictionary.update(b)
-    return merged_dictionary
-
-def dictionary_to_csv(filename, dictionary):
-    """Write a Python dictionary as CSV named 'filename'
-
-    Parameters
-    ----------
-    filename :
-        Name for output file
-
-    dictionary :
-        Name of input Python dictionary to write to 'filename'
-
-    Returns
-    -------
-        This function does not return anything
-
-    Examples
-    --------
-    """
-    f = open(filename, "wb")
-    w = csv.writer(f)
-
-    # write a header
-    w.writerow(['category', 'label', 'value'])
-
-    # terminology: from 'base' and 'cover' maps
-    for base_key, value in dictionary.items():
-        base_category = base_key[0]
-        base_label = base_key[1]  # .decode('utf-8')
-        if value is None or value == '':
-            continue
-        w.writerow([base_category,
-            base_label,
-            value])
-
-    f.close()
-
-def nested_dictionary_to_csv(filename, dictionary):
-    """Write out a nested Python dictionary as CSV named 'filename'
-
-    Parameters
-    ----------
-    filename :
-        Name for output file
-
-    dictionary :
-        Name of the input Python dictionary
-    """
-    f = open(filename, "wb")
-    w = csv.writer(f)
-
-    # write a header
-    w.writerow(['base',
-                'base_label',
-                'cover',
-                'cover_label',
-                'area',
-                'count',
-                'percents'])
-
-    # terminology: from 'base' and 'cover' maps
-    for base_key, inner_dictionary in dictionary.items():
-        base_category = base_key[0]
-        base_label = base_key[1]  # .decode('utf-8')
-
-        for cover_category, inner_value in inner_dictionary.items():
-            if inner_value is None or inner_value == '':
-                continue
-            cover_label = inner_value[0]
-            area = inner_value[1]
-            pixel_count = inner_value[2]
-            pixel_percentage = inner_value[3]
-            w.writerow([base_category,
-                base_label,
-                cover_category,
-                cover_label,
-                area,
-                pixel_count,
-                pixel_percentage])
-
-    f.close()
-
-def append_map_to_component(raster, component_name, component_list):
-    """Appends raster map to given list of components
-
-    Parameters
-    ----------
-    raster :
-        Input raster map name
-
-    component_name :
-        Name of the component to add the raster map to
-
-    component_list :
-        List of raster maps to add the input 'raster' map
-
-    Returns
-    -------
-
-    Examples
-    --------
-    ...
-    """
-    component_list.append(raster)
-    msg = "Map {name} included in the '{component}' component"
-    msg = msg.format(name=raster, component=component_name)
-    grass.verbose(_(msg))
-
-def get_univariate_statistics(raster):
-    """
-    Return and print basic univariate statistics of the input raster map
-
-    Parameters
-    ----------
-    raster :
-        Name of input raster map
-
-    Returns
-    -------
-    univariate :
-        Univariate statistics min, mean, max and variance of the input raster
-        map
-
-    Example
-    -------
-    ...
-    """
-    univariate = grass.parse_command('r.univar', flags='g', map=raster)
-    if info:
-        minimum = univariate['min']
-        mean = univariate['mean']
-        maximum = univariate['max']
-        variance = univariate['variance']
-        msg = "min {mn} | mean {avg} | max {mx} | variance {v}"
-        msg = msg.format(mn=minimum, avg=mean, mx=maximum, v=variance)
-        grass.verbose(_(msg))
-    return univariate
-
-def recode_map(raster, rules, colors, output):
-    """Scores a raster map based on a set of category recoding rules.
-
-    This is a wrapper around r.recode
-
-    Parameters
-    ----------
-    raster :
-        Name of input raster map
-
-    rules :
-        Rules for r.recode
-
-    colors :
-        Color rules for r.colors
-
-    output :
-        Name of output raster map
-
-    Returns
-    -------
-        Does not return any value
-
-    Examples
-    --------
-    ...
-    """
-    msg = "Setting NULL cells in {name} map to 0"
-    msg = msg.format(name=raster)
-    grass.debug(_(msg))
-
-    # ------------------------------------------
-    r.null(map=raster, null=0)  # Set NULLs to 0
-    msg = "To Do: confirm if setting the '{raster}' map's NULL cells to 0 is right"
-    msg = msg.format(raster=raster)
-    grass.debug(_(msg))
-    # Is this right?
-    # ------------------------------------------
-
-    r.recode(input=raster,
-            rules=rules,
-            output=output)
-
-    r.colors(map=output,
-            rules='-',
-            stdin=SCORE_COLORS,
-            quiet=True)
-
-    # if save_temporary_maps:
-    #     tmp_output = save_map(output)
-
-    grass.verbose(_("Scored map {name}:".format(name=raster)))
-
-def float_to_integer(double):
-    """Converts an FCELL or DCELL raster map into a CELL raster map
-
-    Parameters
-    ----------
-    double :
-            An 'FCELL' or 'DCELL' type raster map
-
-    Returns
-    -------
-    This function does not return any value
-
-    Examples
-    --------
-    ..
-    """
-    expression = 'int({double})'
-    expression = expression.format(double=double)
-    equation=equation.format(result=double, expression=expression)
-    r.mapcalc(equation)
-
-def get_coefficients(coefficients_string):
-    """Returns coefficients from an input coefficients_string
-
-    Parameters
-    ----------
-    coefficients_string:
-        One string which lists a metric and coefficients separated by comma
-        without spaces
-
-    Returns
-    -------
-    metric:
-        Metric to use an input option to the `r.grow.distance` module
-
-    constant:
-        A constant value for the 'attractiveness' function
-
-    kappa:
-        A Kappa coefficients for the 'attractiveness' function
-
-    alpha:
-        An alpha coefficient for the 'attractiveness' function
-
-    score
-        A score value to multiply by the generic 'attractiveness' function
-
-    Examples
-    --------
-    ...
-    """
-    coefficients = coefficients_string.split(',')
-    msg = "Distance function coefficients: "
-    metric = coefficients[0]
-    msg += "Metric='{metric}', ".format(metric=metric)
-    constant = coefficients[1]
-    msg += "Constant='{constant}', ".format(constant=constant)
-    kappa = coefficients[2]
-    msg += "Kappa='{Kappa}', ".format(Kappa=kappa)
-    alpha = coefficients[3]
-    msg += "Alpha='{alpha}', ".format(alpha=alpha)
-    try:
-        if coefficients[4]:
-            score = coefficients[4]
-            msg += "Score='{score}'".format(score=score)
-            grass.verbose(_(msg))
-            return metric, constant, kappa, alpha, score
-    except IndexError:
-        grass.verbose(_("Score not provided"))
-
-    grass.verbose(_(msg))
-    return metric, constant, kappa, alpha
-
-def build_distance_function(constant, kappa, alpha, variable, **kwargs):
-    """
-    Build a valid `r.mapcalc` expression based on the following "space-time"
-    function:
-
-        ( {constant} + {kappa} ) / ( {kappa} + exp({alpha} * {variable}) )
-
-    Parameters
-    ----------
-    constant :
-        1
-
-    kappa :
-        A constant named 'K'
-
-    alpha :
-        A constant named 'a'
-
-    variable :
-        The main input variable: for 'attractiveness' it is 'distance', for
-        'flow' it is 'population'
-
-    kwargs :
-        More keyword arguments such as: 'score', 'suitability'
-
-        score :
-            If 'score' is given, it used as a multiplication factor to the base
-            equation.
-
-        suitability :
-            If 'suitability' is given, it is used as a multiplication factor to
-            the base equation.
-
-    Returns
-    -------
-    function:
-        A valid `r.mapcalc` expression
-
-    Examples
-    --------
-    ..
-    """
-    numerator = "{constant} + {kappa}"
-    numerator = numerator.format(constant = constant, kappa = kappa)
-
-    denominator = "{kappa} + exp({alpha} * {variable})"
-    denominator = denominator.format(
-            kappa = kappa,
-            alpha = alpha,
-            variable = variable)  # variable "formatted" by a caller function
-
-    function = " ( {numerator} ) / ( {denominator} )"
-    function = function.format(
-            numerator = numerator,
-            denominator = denominator)
-    grass.debug("Function without score: {f}".format(f=function))
-
-    if 'score' in kwargs:
-        score = kwargs.get('score')
-        function += " * {score}"  # need for float()?
-        function = function.format(score=score)
-    grass.debug(_("Function after adding 'score': {f}".format(f=function)))
-
-    # Integrate land suitability scores in the distance function ? ------------
-
-    # if 'suitability' in kwargs:
-    #     suitability = kwargs.get('suitability')
-    #     function += " * {suitability}"  # FIXME : Confirm Correctness
-    #     function = function.format(suitability=suitability)
-    # msg = "Function after adding 'suitability': {f}".format(f=function)
-    # grass.debug(_(msg))
-
-    # -------------------------------------------------------------------------
-
-    del(numerator)
-    del(denominator)
-
-    return function
-
-def compute_attractiveness(raster, metric, constant, kappa, alpha, **kwargs):
-    """
-    Compute a raster map whose values follow an (euclidean) distance function
-    ( {constant} + {kappa} ) / ( {kappa} + exp({alpha} * {distance}) ), where:
-
-    Source: http://publications.jrc.ec.europa.eu/repository/bitstream/JRC87585/lb-na-26474-en-n.pd
-
-    Parameters
-    ----------
-    constant : 1
-
-    kappa :
-        A constant named 'K'
-
-    alpha :
-        A constant named 'a'
-
-    distance :
-        A distance map based on the input raster
-
-    score :
-        A score term to multiply the distance function
-
-    kwargs :
-        Optional keyword arguments, such as 'mask' and 'output'. The 'mask'
-        is inverted to selectively exclude non-NULL cells from distance
-        related computations. The 'output' is used to name the computed
-        proximity map.
-
-    Returns
-    -------
-    tmp_output :
-        A temporary proximity to features raster map.
-
-    Examples
-    --------
-    ...
-
-    """
-    distance_terms = [str(raster),
-                      str(metric),
-                      'distance',
-                      str(constant),
-                      str(kappa),
-                      str(alpha)]
-
-    if 'score' in kwargs:
-        score = kwargs.get('score')
-        grass.debug(_("Score for attractiveness equation: {s}".format(s=score)))
-        distance_terms += str(score)
-
-    # tmp_distance = tmp_map_name('_'.join(distance_terms))
-    tmp_distance = tmp_map_name(name='_'.join([raster, metric]))
-    r.grow_distance(input=raster,
-            distance=tmp_distance,
-            metric=metric,
-            quiet=True,
-            overwrite=True)
-
-    if 'mask' in kwargs:
-        mask = kwargs.get('mask')
-        # global mask
-        msg = "Inverted masking to exclude non-NULL cells "
-        msg += "from distance related computations based on '{mask}'"
-        msg = msg.format(mask=mask)
-        grass.verbose(_(msg))
-        r.mask(raster=mask, flags='i', overwrite=True, quiet=True)
-
-    # FIXME: use a parameters dictionary, avoid conditionals
-    if 'score' in kwargs:
-        distance_function = build_distance_function(
-                constant=constant,
-                kappa=kappa,
-                alpha=alpha,
-                variable=tmp_distance,
-                score=score)
-
-    # FIXME: use a parameters dictionary, avoid conditionals
-    if not 'score' in kwargs:
-        distance_function = build_distance_function(
-                constant=constant,
-                kappa=kappa,
-                alpha=alpha,
-                variable=tmp_distance)
-
-    # temporary maps will be removed
-    if 'output_name' in kwargs:
-        tmp_distance_map = tmp_map_name(name=kwargs.get('output_name'))
-    else:
-        basename = '_'.join([raster, 'attractiveness'])
-        tmp_distance_map = tmp_map_name(name=basename)
-
-    distance_function = equation.format(result=tmp_distance_map,
-            expression=distance_function)
-
-    msg = "Distance function: {f}".format(f=distance_function)
-    grass.verbose(_(msg))
-    del(msg)
-
-    grass.mapcalc(distance_function, overwrite=True)
-
-    r.null(map=tmp_distance_map, null=0)  # Set NULLs to 0
-
-    compress_status = grass.read_command(
-            'r.compress',
-            flags='g',
-            map=tmp_distance_map)
-    grass.verbose(_("Compress status: {s}".format(s=compress_status)))
-
-    del(distance_function)
-
-    tmp_output = save_map(tmp_distance_map)
-    return tmp_distance_map
-
-def neighborhood_function(raster, method, size, distance_map):
-    """
-    Parameters
-    ----------
-    raster :
-        Name of input raster map for which to apply r.neighbors
-
-    method :
-        Method for r.neighbors
-
-    size :
-        Size for r.neighbors
-
-    distance :
-        A distance map
-
-    Returns
-    -------
-    filtered_output :
-        A neighborhood filtered raster map
-
-    Examples
-    --------
-    ...
-    """
-    r.null(map=raster, null=0)  # Set NULLs to 0
-
-    neighborhood_output = distance_map + '_' + method
-    msg = "Neighborhood operator '{method}' and size '{size}' for map '{name}'"
-    msg = msg.format(method=method, size=size, name=neighborhood_output)
-    grass.verbose(_(msg))
-
-    r.neighbors(input=raster,
-            output=neighborhood_output,
-            method=method,
-            size=size,
-            overwrite=True)
-
-    scoring_function = "{neighborhood} * {distance}"
-    scoring_function = scoring_function.format(
-            neighborhood=neighborhood_output,
-            distance=distance_map)
-
-    filtered_output = distance_map
-    filtered_output += '_' + method + '_' + str(size)
-
-    neighborhood_function = equation.format(result=filtered_output,
-            expression=scoring_function)
-    # ---------------------------------------------------------------
-    grass.debug(_("Expression: {e}".format(e=neighborhood_function)))
-    # ---------------------------------------------------------------
-    grass.mapcalc(neighborhood_function, overwrite=True)
-
-    # tmp_distance_map = filtered_output
-
-    # r.compress(distance_map, flags='g')
-
-    del(method)
-    del(size)
-    del(neighborhood_output)
-    del(scoring_function)
-    # del(filtered_output)
-
-    return filtered_output
-
-def smooth_map(raster, method, size):
-    """
-    Parameters
-    ----------
-    raster :
-
-    method :
-
-    size :
-
-    Returns
-    -------
-
-    Examples
-    --------
-    """
-    r.neighbors(
-            input=raster,
-            output=raster,
-            method=method,
-            size=size,
-            overwrite=True,
-            quiet=True)
-
-def smooth_component(component, method, size):
-    """
-    component:
-
-    method:
-
-    size:
-    """
-    try:
-        if len(component) > 1:
-            for item in component:
-                smooth_map(item,
-                        method=method,
-                        size=size)
-        else:
-            smooth_map(component[0],
-                    method=method,
-                    size=size)
-
-    except IndexError:
-        grass.verbose(_("Index Error"))  # FIXME: some useful message... ?
-
-def zerofy_small_values(raster, threshhold, output_name):
-    """
-    Set the input raster map cell values to 0 if they are smaller than the
-    given threshhold
-
-    Parameters
-    ----------
-    raster :
-        Name of input raster map
-
-    threshhold :
-        Reference for which to flatten smaller raster pixel values to zero
-
-    output_name :
-        Name of output raster map
-
-    Returns
-    -------
-        Does not return any value
-
-    Examples
-    --------
-    ...
-    """
-    rounding='if({raster} < {threshhold}, 0, {raster})'
-    rounding = rounding.format(raster=raster, threshhold=threshhold)
-    rounding_equation = equation.format(result=output_name, expression=rounding)
-    grass.mapcalc(rounding_equation, overwrite=True)
-
-def normalize_map (raster, output_name):
-    """
-    Normalize all raster map cells by subtracting the raster map's minimum and
-    dividing by the range.
-
-    Parameters
-    ----------
-    raster :
-        Name of input raster map
-
-    output_name :
-        Name of output raster map
-
-    Returns
-    -------
-
-    Examples
-    --------
-    ...
-    """
-    # grass.debug(_("Input to normalize: {name}".format(name=raster)))
-    # grass.debug(_("Ouput: {name}".format(name=output_name)))
-
-    finding = grass.find_file(name=raster, element='cell')
-
-    if not finding['file']:
-        grass.fatal("Raster map {name} not found".format(name=raster))
-    # else:
-    #     grass.debug("Raster map {name} found".format(name=raster))
-
-    # univar_string = grass.read_command('r.univar', flags='g', map=raster)
-    # univar_string = univar_string.replace('\n', '| ').replace('\r', '| ')
-    # msg = "Univariate statistics: {us}".format(us=univar_string)
-
-    minimum = grass.raster_info(raster)['min']
-    grass.debug(_("Minimum: {m}".format(m=minimum)))
-
-    maximum = grass.raster_info(raster)['max']
-    grass.debug(_("Maximum: {m}".format(m=maximum)))
-
-    if minimum is None or maximum is None:
-        msg = "Minimum and maximum values of the <{raster}> map are 'None'. "
-        msg += "The {raster} map may be empty "
-        msg += "OR the MASK opacifies all non-NULL cells."
-        grass.fatal(_(msg.format(raster=raster)))
-
-    normalisation = 'float(({raster} - {minimum}) / ({maximum} - {minimum}))'
-    normalisation = normalisation.format(raster=raster, minimum=minimum,
-            maximum=maximum)
-
-    # Maybe this can go in the parent function? 'raster' names are too long!
-    if info:
-        msg = "Normalization expression: "
-        msg += normalisation
-        grass.verbose(_(msg))
-    #
-
-    normalisation_equation = equation.format(result=output_name,
-        expression=normalisation)
-    grass.mapcalc(normalisation_equation, overwrite=True)
-
-    get_univariate_statistics(output_name)
-
-    del(minimum)
-    del(maximum)
-    del(normalisation)
-    del(normalisation_equation)
-
-def zerofy_and_normalise_component(components, threshhold, output_name):
-    """
-    Sums up all maps listed in the given "components" object and derives a
-    normalised output.
-
-    To Do:
-
-    * Improve `threshold` handling. What if threshholding is not desired? How
-    to skip performing it?
-
-    Parameters
-    ----------
-    components :
-        Input list of raster maps (components)
-
-    threshhold :
-        Reference value for which to flatten all smaller raster pixel values to
-        zero
-
-    output_name :
-        Name of output raster map
-
-    Returns
-    -------
-    ...
-
-    Examples
-    --------
-    ...
-    """
-    msg = "Normalising sum of: "
-    msg += ','.join(components)
-    grass.debug(_(msg))
-    grass.verbose(_(msg))
-
-    if len(components) > 1:
-
-        # prepare string for mapcalc expression
-        components = [ name.split('@')[0] for name in components ]
-        components_string = spacy_plus.join(components)
-        components_string = components_string.replace(' ', '')
-        components_string = components_string.replace('+', '_')
-
-        # temporary map names
-        tmp_intermediate = tmp_map_name(name=components_string)
-        tmp_output = tmp_map_name(name=components_string)
-
-        # build mapcalc expression
-        component_expression = spacy_plus.join(components)
-        component_equation = equation.format(result=tmp_intermediate,
-                expression=component_expression)
-
-        grass.mapcalc(component_equation, overwrite=True)
-
-        del(components_string)
-        del(component_expression)
-        del(component_equation)
-
-    elif len(components) == 1:
-        # temporary map names, if components contains one element
-        tmp_intermediate = components[0]
-        tmp_output = tmp_map_name(name=tmp_intermediate)
-
-    if threshhold > THRESHHOLD_ZERO:
-        msg = "Setting values < {threshhold} in '{raster}' to zero"
-        grass.verbose(msg.format(threshhold=threshhold, raster=tmp_intermediate))
-        zerofy_small_values(tmp_intermediate, threshhold, tmp_output)
-
-    else:
-        tmp_output = tmp_intermediate
-
-    # grass.verbose(_("Temporary map name: {name}".format(name=tmp_output)))
-    grass.debug(_("Output map name: {name}".format(name=output_name)))
-    # r.info(map=tmp_output, flags='gre')
-
-    ### FIXME
-
-    normalize_map(tmp_output, output_name)
-
-    ### FIXME
-
-    del(tmp_intermediate)
-    del(tmp_output)
-    del(output_name)
-
-def classify_recreation_component(component, rules, output_name):
-    """
-    Recode an input recreation component based on given rules
-
-    To Do:
-
-    - Potentially, test range of input recreation component, i.e. ranging in
-      [0,1]
-
-    Parameters
-    ----------
-    component :
-        Name of input raster map
-
-    rules :
-        Rules for r.recode
-
-    output_name :
-        Name for output raster map
-
-    Returns
-    -------
-        Does not return any value
-
-    Examples
-    --------
-    ...
-
-    """
-    r.recode(input=component,
-            rules='-',
-            stdin=rules,
-            output=output_name)
-
-def compute_artificial_proximity(raster, distance_categories, **kwargs):
-    """
-    Compute proximity to artificial surfaces
-
-    1. Distance to features
-    2. Classify distances
-
-    Parameters
-    ----------
-    raster :
-        Name of input raster map
-
-    distance_categories :
-        Category rules to recode the input distance map
-
-    kwargs :
-        Optional arguments: output_file
-
-    Returns
-    -------
-    tmp_output :
-        Name of the temporary output map for internal, in-script, re-use
-
-    Examples
-    --------
-    ...
-    """
-    artificial_distances = tmp_map_name(name=raster)
-
-    grass.run_command("r.grow.distance",
-            input = raster,
-            distance = artificial_distances,
-            metric = EUCLIDEAN,
-            quiet = True,
-            overwrite = True)
-
-    if 'output_name' in kwargs:
-        # temporary maps will be removed
-        tmp_output = tmp_map_name(name=kwargs.get('output_name'))
-        grass.debug(_("Pre-defined output map name {name}".format(name=tmp_output)))
-
-    else:
-        tmp_output = tmp_map_name(name='artificial_proximity')
-        grass.debug(_("Hardcoded temporary map name {name}".format(name=tmp_output)))
-
-    msg = "Computing proximity to '{mapname}'"
-    msg = msg.format(mapname=raster)
-    grass.verbose(_(msg))
-    del(msg)
-    grass.run_command("r.recode",
-            input = artificial_distances,
-            output = tmp_output,
-            rules = distance_categories,
-            overwrite = True)
-
-    output = grass.find_file(name=tmp_output, element='cell')
-    if not output['file']:
-        grass.fatal("Proximity map {name} not created!".format(name=raster))
-#     else:
-#         g.message(_("Output map {name}:".format(name=tmp_output)))
-
-    return tmp_output
-
-def artificial_accessibility_expression(artificial_proximity, roads_proximity):
-    """
-    Build an r.mapcalc compatible expression to compute accessibility to
-    artificial surfaces based on the following accessibility classification
-    rules for artificial surfaces:
-
-|-------------------+-------+------------+-------------+--------------+---------|
-| Anthropic \ Roads | < 500 | 500 - 1000 | 1000 - 5000 | 5000 - 10000 | > 10000 |
-|-------------------+-------+------------+-------------+--------------+---------|
-| < 500             | 1     | 1          | 2           | 3            | 4       |
-|-------------------+-------+------------+-------------+--------------+---------|
-| 500 - 1000        | 1     | 1          | 2           | 3            | 4       |
-|-------------------+-------+------------+-------------+--------------+---------|
-| 1000 - 5000       | 2     | 2          | 2           | 4            | 5       |
-|-------------------+-------+------------+-------------+--------------+---------|
-| 5000 - 10000      | 3     | 3          | 4           | 5            | 5       |
-|-------------------+-------+------------+-------------+--------------+---------|
-| > 10000           | 3     | 4          | 4           | 5            | 5       |
-|-------------------+-------+------------+-------------+--------------+---------|
-
-    Parameters
-    ----------
-    artificial :
-        Proximity to artificial surfaces
-
-    roads :
-        Proximity to roads
-
-    Returns
-    -------
-    expression
-        Valid r.mapcalc expression
-
-
-    Examples
-    --------
-    ...
-    """
-    expression = ('if( {artificial} <= 2 && {roads} <= 2, 1,'
-            ' \ \n if( {artificial} == 1 && {roads} == 3, 2,'
-            ' \ \n if( {artificial} == 2 && {roads} == 3, 2,'
-            ' \ \n if( {artificial} == 3 && {roads} <= 3, 2,'
-            ' \ \n if( {artificial} <= 2 && {roads} == 4, 3,'
-            ' \ \n if( {artificial} == 4 && {roads} == 2, 3,'
-            ' \ \n if( {artificial} >= 4 && {roads} == 1, 3,'
-            ' \ \n if( {artificial} <= 2 && {roads} == 5, 4,'
-            ' \ \n if( {artificial} == 3 && {roads} == 4, 4,'
-            ' \ \n if( {artificial} >= 4 && {roads} == 3, 4,'
-            ' \ \n if( {artificial} == 5 && {roads} == 2, 4,'
-            ' \ \n if( {artificial} >= 3 && {roads} == 5, 5,'
-            ' \ \n if( {artificial} >= 4 && {roads} == 4, 5)))))))))))))')
-
-    expression = expression.format(artificial=artificial_proximity,
-            roads=roads_proximity)
-    return expression
-
-def compute_artificial_accessibility(artificial_proximity, roads_proximity, **kwargs):
-    """Compute artificial proximity
-
-    Parameters
-    ----------
-    artificial_proximity :
-        Artificial surfaces...
-
-    roads_proximity :
-        Road infrastructure
-
-    kwargs :
-        Optional input parameters
-
-    Returns
-    -------
-    output :
-        ...
-
-    Examples
-    --------
-    ...
-    """
-    artificial = grass.find_file(name=artificial_proximity, element='cell')
-    if not artificial['file']:
-        grass.fatal("Raster map {name} not found".format(name=artificial_proximity))
-
-    roads = grass.find_file(name=roads_proximity, element='cell')
-    if not roads['file']:
-        grass.fatal("Raster map {name} not found".format(name=roads_proximity))
-
-    accessibility_expression = artificial_accessibility_expression(
-            artificial_proximity,
-            roads_proximity)
-    if 'output_name' in kwargs:
-        # temporary maps will be removed!
-        tmp_output = tmp_map_name(name=kwargs.get('output_name'))
-    else:
-        basename = 'artificial_accessibility'
-        tmp_output = tmp_map_name(name=basename)
-
-    accessibility_equation = equation.format(result=tmp_output,
-            expression=accessibility_expression)
-
-    if info:
-        msg = "Equation for proximity to artificial areas: \n"
-        msg += accessibility_equation
-        grass.debug(msg)
-        del(msg)
-
-    grass.verbose(_("Computing accessibility to artificial surfaces"))
-    grass.mapcalc(accessibility_equation, overwrite=True)
-
-    del(accessibility_expression)
-    del(accessibility_equation)
-
-    output = save_map(tmp_output)
-
-    return output
-
-def recreation_spectrum_expression(potential, opportunity):
-    """
-    Build and return a valid mapcalc expression for deriving
-    the Recreation Opportunity Spectrum
-
-    |-------------------------+-----+----------+------|
-    | Potential / Opportunity | Far | Midrange | Near |
-    |-------------------------+-----+----------+------|
-    | Low                     | 1   | 2        | 3    |
-    |-------------------------+-----+----------+------|
-    | Moderate                | 4   | 5        | 6    |
-    |-------------------------+-----+----------+------|
-    | High                    | 7   | 8        | 9    |
-    |-------------------------+-----+----------+------|
-
-    Questions:
-
-    - Why not use `r.cross`?
-    - Use DUMMY strings for potential and opportunity raster map names?
-
-    Parameters
-    ----------
-    potential :
-        Map depicting potential for recreation
-
-    opportunity :
-        Map depicting opportunity for recreation
-
-    Returns
-    -------
-    expression :
-        A valid r.mapcalc expression
-
-    Examples
-    --------
-    ...
-    """
-    expression = ('if( {potential} == 1 && {opportunity} == 1, 1,'
-            ' \ \n if( {potential} == 1 && {opportunity} == 2, 2,'
-            ' \ \n if( {potential} == 1 && {opportunity} == 3, 3,'
-            ' \ \n if( {potential} == 2 && {opportunity} == 1, 4,'
-            ' \ \n if( {potential} == 2 && {opportunity} == 2, 5,'
-            ' \ \n if( {potential} == 2 && {opportunity} == 3, 6,'
-            ' \ \n if( {potential} == 3 && {opportunity} == 1, 7,'
-            ' \ \n if( {potential} == 3 && {opportunity} == 2, 8,'
-            ' \ \n if( {potential} == 3 && {opportunity} == 3, 9)))))))))')
-
-    expression = expression.format(potential=potential,
-            opportunity=opportunity)
-
-    msg = "Recreation Spectrum expression: \n"
-    msg += expression
-    grass.debug(msg)
-    del(msg)
-
-    return expression
-
-def compute_recreation_spectrum(potential, opportunity, spectrum):
-    """
-    Computes spectrum for recreation based on maps of potential and opportunity
-    for recreation
-
-    Parameters
-    ----------
-    potential :
-        Name for input potential for recreation map
-
-    opportunity :
-        Name for input opportunity for recreation map
-
-    Returns
-    -------
-    spectrum :
-        Name for output spectrum of recreation map
-
-    Examples
-    --------
-    ...
-    """
-    spectrum_expression = recreation_spectrum_expression(
-            potential=potential,
-            opportunity=opportunity)
-
-    spectrum_equation = equation.format(result=spectrum,
-            expression=spectrum_expression)
-
-    if info:
-        msg = "Recreation Spectrum equation: \n"
-        msg += spectrum_equation
-        g.message(msg)
-        del(msg)
-
-    grass.mapcalc(spectrum_equation, overwrite=True)
-
-    del(spectrum_expression)
-    del(spectrum_equation)
-    return spectrum
-
-def update_meta(raster, title):
-    """
-    Update metadata of given raster map
-
-    Parameters
-    ----------
-    raster :
-        ...
-
-    title :
-        ...
-
-    Returns
-    -------
-        Does not return any value
-
-    Examples
-    --------
-    ...
-    """
-    history = '\n' + citation_recreation_potential
-    description_string = 'Recreation {raster} map'
-    description = description_string.format(raster=raster)
-
-    title = '{title}'.format(title=title)
-    units = 'Meters'
-
-    source1 = 'Source 1'
-    source2 = 'Source 2'
-
-    r.support(map=raster, title=title, description=description, units=units,
-            source1=source1, source2=source2, history=history)
-
-    if timestamp:
-        r.timestamp(map=raster, date=timestamp)
-
-    del(history)
-    del(description)
-    del(title)
-    del(units)
-    del(source1)
-    del(source2)
-
-def export_map(input_name, title, categories, colors, output_name):
-    """
-    Export a raster map by renaming the (temporary) raster map name
-    'input_name' to the requested output raster map name 'output_name'.
-    This function is (mainly) used to export either of the intermediate
-    recreation 'potential' or 'opportunity' maps.
-
-    Parameters
-    ----------
-    raster :
-        Input raster map name
-
-    title :
-        Title for the output raster map
-
-    categories :
-        Categories and labels for the output raster map
-
-    colors :
-        Colors for the output raster map
-
-    output_name :
-        Output raster map name
-
-    Returns
-    -------
-    output_name :
-        This function will return the requested 'output_name'
-
-    Examples
-    --------
-    ..
-    """
-    finding = grass.find_file(name=input_name,
-            element='cell')
-    if not finding['file']:
-        grass.fatal("Raster map {name} not found".format(name=input_name))
-    del(finding)
-
-    # inform
-    msg = "\nOutputting '{raster}' map\n"
-    msg = msg.format(raster=input_name)
-    grass.verbose(_(msg))
-    del(msg)
-
-    # get categories and labels
-    raster_categories = 'categories_of_'
-    raster_categories += input_name
-    raster_category_labels = string_to_file(
-            string = categories,
-            name = raster_categories)
-
-    # add ascii file to removal list
-    remove_normal_files_at_exit.append(raster_category_labels)
-
-    # apply categories and description
-    r.category(map=input_name,
-            rules=raster_category_labels,
-            separator=':')
-
-    # update meta and colors
-    update_meta(input_name, title)
-    r.colors(map=input_name,
-            rules='-',
-            stdin=colors,
-            quiet=True)
-    # rename to requested output name
-    g.rename(
-            raster=(
-                input_name,
-                output_name),
-            quiet=True)
-
-    return output_name
-
-def mobility_function(distance, constant, coefficients, population, score,
-        **kwargs):
-    """
-    The following 'mobility' function, is identical to the one used in
-    `compute_attractiveness()`, excluding, however, the 'score' term:
-
-        if(L10<>0,(1+$D$3)/($D$3+exp(-$E$3*L10))*52,0)
-
-    Source: Excel file provided by the MAES Team, Land Resources, D3
-
-    ------------------------------------------------------------------
-    if L<>0; then
-      # (1 + D) / (D + exp(-E * L)) * 52)
-
-      #  D: Kappa
-      # -E: Alpha -- Note the '-' sign in front of E
-      #  L: Population (within boundary, within distance buffer)
-    ------------------------------------------------------------------
-
-    Parameters
-    ----------
-
-    distance :
-        Map of distance categories
-
-    constant :
-        Constant for the mobility function
-
-    coefficients :
-        A dictionary with a set for coefficients for each distance category, as
-        (the example) presented in the following table:
-
-        |----------+---------+---------|
-        | Distance | Kappa   | Alpha   |
-        |----------+---------+---------|
-        | 0 to 1   | 0.02350 | 0.00102 |
-        |----------+---------+---------|
-        | 1 to 2   | 0.02651 | 0.00109 |
-        |----------+---------+---------|
-        | 2 to 3   | 0.05120 | 0.00098 |
-        |----------+---------+---------|
-        | 3 to 4   | 0.10700 | 0.00067 |
-        |----------+---------+---------|
-        | >4       | 0.06930 | 0.00057 |
-        |----------+---------+---------|
-
-        Note, the last distance category is not considered in deriving the
-        final "map of visits".
-
-        Note, the Alpha coefficient, is signed with a '-' in the mobility
-        function.
-
-    population :
-        A map with the distribution of the demand per distance category and
-        predefined geometric boundaries (see `r.stats.zonal` deriving the
-        'demand' map ).
-
-    score :
-        A score value for the mobility function
-
-    **kwargs :
-        Optional parameters. For example, the land suitability if integration
-        in the build_distance_function() will be successfull.
-
-    Returns
-    -------
-
-    mobility_expression :
-        A valid mapcalc expression to compute the flow based on the
-        predefined function `build_distance_function`.
-
-    Examples
-    --------
-    ...
-    """
-    expressions={}  # create a dictionary of expressions
-
-    # Not used. It can be though if integration to build_distance_function() is
-    # successfull. ------------------------------------------------------------
-
-    # if 'suitability' in kwargs:
-    #     suitability = kwargs.get('suitability')
-    # -------------------------------------------------------------------------
-
-    for distance_category, parameters in coefficients.items():
-
-        kappa, alpha = parameters
-
-        # Note, alpha gets a minus, that is: -alpha
-        expressions[distance_category]=build_distance_function(
-                constant=constant,
-                kappa=kappa,
-                alpha=-alpha,
-                variable=population,
-                score=score)
-                # suitability=suitability)  # Not used.
-                # Maybe it can, though, after successfully testing its
-                # integration to build_distance_function().
-
-        grass.debug(_("For distance '{d}':".format(d=distance)))
-        grass.debug(_(expressions[distance_category]))
-
-    msg = "Expressions per distance category: {e}".format(e=expressions)
-    grass.debug(_(msg))
-
-    # build expressions -- explicit: use the'score' kwarg!
-    expression = ('eval( mobility_0 = {expression_0},'
-                  ' \ \n mobility_1 = {expression_1},'
-                  ' \ \n mobility_2 = {expression_2},'
-                  ' \ \n mobility_3 = {expression_3},'
-                  ' \ \n distance_0 = {distance} == {distance_category_0},'
-                  ' \ \n distance_1 = {distance} == {distance_category_1},'
-                  ' \ \n distance_2 = {distance} == {distance_category_2},'
-                  ' \ \n distance_3 = {distance} == {distance_category_3},'
-                  ' \ \n if( distance_0, mobility_0,'
-                  ' \ \n if( distance_1, mobility_1,'
-                  ' \ \n if( distance_2, mobility_2,'
-                  ' \ \n if( distance_3, mobility_3,'
-                  ' \ \n null() )))))')
-    grass.debug(_("Mapcalc expression: {e}".format(e=expression)))
-
-    # replace keywords appropriately
-        # 'distance' is a map
-        # 'distance_category' is a value
-        # hence: 'distance' != 'distance_category'
-    mobility_expression = expression.format(
-            expression_0 = expressions[0],
-            expression_1 = expressions[1],
-            expression_2 = expressions[2],
-            expression_3 = expressions[3],
-            distance_category_0 = 0,
-            distance_category_1 = 1,
-            distance_category_2 = 2,
-            distance_category_3 = 3,
-            distance = distance)
-    # FIXME Make the above more elegant?
-
-    msg = "Big expression (after formatting): {e}".format(e=expression)
-    grass.debug(_(msg))
-
-    return mobility_expression
-
-def compute_unmet_demand(distance, constant, coefficients, population, score,
-        **kwargs):
-    """
-    Parameters
-    ----------
-
-    distance :
-        Map of distance categories
-
-    constant :
-        Constant for the mobility function
-
-    coefficients :
-        A tuple with coefficients for the last distance category, as
-        (the example) presented in the following table:
-
-        |----------+---------+---------|
-        | Distance | Kappa   | Alpha   |
-        |----------+---------+---------|
-        | >4       | 0.06930 | 0.00057 |
-        |----------+---------+---------|
-
-        Note, this is the last distance category. See also the
-        mobility_function().
-
-        Note, the Alpha coefficient, is signed with a '-' in the mobility
-        function.
-
-    population :
-        A map with the distribution of the demand per distance category and
-        predefined geometric boundaries (see `r.stats.zonal` deriving the
-        'unmet demand' map ).
-
-    score :
-        A score value for the mobility function
-
-    **kwargs :
-        Optional parameters. For example, the land suitability if integration
-        in the build_distance_function() will be successfull.
-
-    Returns
-    -------
-
-    mobility_expression :
-        A valid mapcalc expression to compute the flow based on the
-        predefined function `build_distance_function`.
-
-    Examples
-    --------
-    ...
-    """
-    distance_category = 4  # Hardcoded! FIXME
-
-    kappa, alpha = coefficients
-    unmet_demand_expression = build_distance_function(
-            constant=constant,
-            kappa=kappa,
-            alpha=-alpha,
-            variable=population,
-            score=score)
-            # suitability=suitability)  # Not used. Maybe it can, though,
-            # after successfull testing its integration to
-            # build_distance_function().
-
-    msg = "Expression for distance category '{d}': {e}"
-    msg = msg.format(d=distance_category, e=unmet_demand_expression)
-    grass.debug(_(msg))
-
-    # build expressions -- explicit: use the 'score' kwarg!
-    expression = ('eval( unmet_demand = {expression},'
-                  ' \ \n distance = {distance} == {distance_category},'
-                  ' \ \n if( distance, unmet_demand,'
-                  ' \ \n null() ))')
-    grass.debug(_("Mapcalc expression: {e}".format(e=expression)))
-
-    # replace keywords appropriately
-    unmet_demand_expression = expression.format(
-            expression = unmet_demand_expression,
-            distance = distance,
-            distance_category = distance_category)
-
-    msg = "Big expression (after formatting): {e}".format(
-            e=unmet_demand_expression)
-    grass.debug(_(msg))
-
-    return unmet_demand_expression
-
-def update_vector(vector, raster, methods, column_prefix):
-    """
-
-    Parameters
-    ----------
-    vector :
-        Vector map whose attribute table to update with results of the
-        v.rast.stats call
-
-    raster :
-        Source raster map for statistics
-
-    methods :
-        Descriptive statistics for the `v.rast.stats` call
-
-    column_prefix :
-        Prefix for the names of the columns created by the `v.rast.stats` call
-
-    Returns
-    -------
-        This helper function executes `v.rast.stats`. It does not return any
-        value.
-
-    Examples
-    --------
-    ..
-    """
-    run('v.rast.stats',
-            map=vector,
-            flags='c',
-            raster=raster,
-            method=methods,
-            column_prefix=column_prefix,
-            overwrite=True)
-    # grass.verbose(_("Updating vector map '{v}'".format(v=vector)))
-
-def get_raster_statistics(map_one, map_two, separator, flags):
-    """
-    Parameters
-    ----------
-    map_one :
-        First map as input to `r.stats`
-
-    map_two :
-        Second map as input to `r.stats`
-
-    separator :
-        Character to use as separator in `r.stats`
-
-    flags :
-        Flags for `r.stats`
-
-    Returns
-    -------
-    dictionary :
-        A nested dictionary that holds categorical statistics for both maps
-        'map_one' and 'map_two'.
-
-        - The 'outer_key' is the raster category _and_ label of 'map_one'.
-        - The 'inner_key' is the raster map category of 'map_two'.
-        - The 'inner_value' is the list of statistics for map two, as returned
-          for `r.stats`.
-
-        Example of a nested dictionary:
-
-        {(u'3',
-            u'Region 3'):
-            {u'1': [
-                u'355.747658',
-                u'6000000.000000',
-                u'6',
-                u'6.38%'],
-            u'3': [
-                u'216304.146140',
-                u'46000000.000000',
-                u'46',
-                u'48.94%'],
-            u'2': [
-                u'26627.415787',
-                u'46000000.000000',
-                u'46',
-                u'48.94%']}}
-    """
-
-    statistics = grass.read_command(
-            'r.stats',
-            input=(map_one, map_two),
-            output='-',
-            flags=flags,
-            separator=separator,
-            quiet=True)
-    statistics = statistics.split('\n')[:-1]
-
-    dictionary = dict()
-
-    # build a nested dictionary where:
-    for row in statistics:
-        row = row.split('|')
-        outer_key = ( row[0], row[1])
-        inner_key = row[2]
-        inner_value = row[3:]
-        inner_dictionary = {inner_key: inner_value}
-        try:
-            dictionary[outer_key][inner_key] = inner_value
-        except KeyError:
-            dictionary[outer_key] = { inner_key : inner_value}
-
-    return dictionary
-
-def compile_use_table(supply):
-    """Compile the 'use' table out of a 'supply' table
-
-    Parameters
-    ----------
-    supply :
-        A nested Python dictionary that is compiled when runnning the
-        compute_supply() function
-
-    Returns
-    -------
-
-    Examples
-    --------
-    """
-    uses = {}
-    for outer_key, outer_value in supply.items():
-        dictionaries = statistics_dictionary[outer_key]
-
-        use_values = []
-        for key, value in dictionaries.items():
-            use_value = value[0]
-            use_values.append(use_value)
-
-        use_in_key = sum([float(x)
-            if not math.isnan(float(x))
-            else 0
-            for x in use_values])
-
-        try:
-            uses[outer_key] = use_in_key
-        except KeyError:
-            print "Something went wrong in building the use table"
-
-    return uses
-
-def compute_supply(base,
-        recreation_spectrum,
-        highest_spectrum,
-        base_reclassification_rules,
-        reclassified_base,
-        reclassified_base_title,
-        flow,
-        flow_map_name,
-        aggregation,
-        ns_resolution,
-        ew_resolution,
-        **kwargs):
-    """
-     Algorithmic description of the "Contribution of Ecosysten Types"
-
-     # FIXME
-     '''
-     1   B ← {0, .., m-1}     :  Set of aggregational boundaries
-     2   T ← {0, .., n-1}     :  Set of land cover types
-     3   WE ← 0               :  Set of weighted extents
-     4   R ← 0                :  Set of fractions
-     5   F ← 0
-     6   MASK ← HQR           : High Quality Recreation
-     7   foreach {b} ⊆ B do   : for each aggregational boundary 'b'
-     8      RB ← 0
-     9      foreach {t} ⊆ T do  : for each Land Type
-     10         WEt ← Et * Wt   : Weighted Extent = Extent(t) * Weight(t)
-     11         WE ← WE⋃{WEt}   : Add to set of Weighted Extents
-     12     S ← ∑t∈WEt
-     13     foreach t ← T do
-     14        Rt ← WEt / ∑WE
-     15        R ← R⋃{Rt}
-     16     RB ← RB⋃{R}
-     '''
-     # FIXME
-
-    Parameters
-    ----------
-    recreation_spectrum:
-        Map scoring access to and quality of recreation
-
-    highest_spectrum :
-        Expected is a map of areas with highest recreational value (category 9
-        as per the report ... )
-
-    base :
-        Base land types map for final zonal statistics. Specifically to
-        ESTIMAP's recrceation mapping algorithm
-
-    base_reclassification_rules :
-        Reclassification rules for the input base map
-
-    reclassified_base :
-        Name for the reclassified base cover map
-
-    reclassified_base_title :
-        Title for the reclassified base map
-
-    ecosystem_types :
-
-    flow :
-        Map of visits, derived from the mobility function, depicting the
-        number of people living inside zones 0, 1, 2, 3. Used as a cover map
-        for zonal statistics.
-
-    flow_map_name :
-        A name for the 'flow' map. This is required when the 'flow' input
-        option is not defined by the user, yet some of the requested outputs
-        required first the production of the 'flow' map. An example is the
-        request for a supply table without requesting the 'flow' map itself.
-
-    aggregation :
-
-    ns_resolution :
-
-    ew_resolution :
-
-    statistics_filename :
-
-    **kwargs :
-        Optional keyword arguments, such as: 'csv_prefix'
-
-    ? :
-        Land cover class percentages in ROS9 (this is: relative percentage)
-
-    output :
-        Supply table (distribution of flow for each land cover class)
-
-    Returns
-    -------
-    This function produces a map to base the production of a supply table in
-    form of CSV.
-
-    Examples
-    --------
-    """
-    # Inputs
-    flow_in_base = flow + '_' + base
-    base_scores = base + '.scores'
-
-    # Define lists and dictionaries to hold intermediate data
-    weighted_extents = {}
-    flows = []
-    global statistics_dictionary
-    statistics_dictionary = {}
-
-    # MASK areas of high quality recreation
-    r.mask(raster=highest_spectrum, overwrite=True, quiet=True)
-
-    # Reclassify land cover map to MAES ecosystem types
-    r.reclass(input=base,
-            rules=base_reclassification_rules,
-            output=reclassified_base,
-            quiet=True)
-    # add to "remove_at_exit" after the reclassified maps!
-
-    # Discard areas out of MASK
-    copy_equation = equation.format(result=reclassified_base,
-            expression=reclassified_base)
-    r.mapcalc(copy_equation, overwrite=True)
-    del(copy_equation)
-
-    # Count flow within each land cover category
-    r.stats_zonal(base=base,
-            flags='r',
-            cover=flow_map_name,
-            method='sum',
-            output=flow_in_base,
-            overwrite=True,
-            quiet=True)
-
-    # Set colors for "flow" map
-    r.colors(map=flow_in_base,
-        color=MOBILITY_COLORS,
-        quiet=True)
-
-    # Parse aggregation raster categories and labels
-    categories = grass.parse_command('r.category',
-            map=aggregation,
-            delimiter='\t')
-
-    for category in categories:
-
-        # Intermediate names
-
-        cells = highest_spectrum + '.cells' + '.' + category
-        remove_at_exit.append(cells)
-
-        extent = highest_spectrum + '.extent' + '.' + category
-        remove_at_exit.append(extent)
-
-        weighted = highest_spectrum + '.weighted' + '.' + category
-        remove_at_exit.append(weighted)
-
-        fractions = base + '.fractions' + '.' + category
-        remove_at_exit.append(fractions)
-
-        flow_category = '_flow_' + category
-        flow = base + flow_category
-        remove_at_exit.append(flow)
-
-        flow_in_reclassified_base = reclassified_base + '_flow'
-        flow_in_category = reclassified_base + flow_category
-        del(flow_category)
-        flows.append(flow_in_category)  # add to list for patching
-        remove_at_exit.append(flow_in_category)
-
-        # Output names
-
-        msg = "Processing aggregation raster category: {r}"
-        msg = msg.format(r=category)
-        grass.debug(_(msg))
-        # g.message(_(msg))
-        del(msg)
-
-        # First, set region to extent of the aggregation map
-            # and resolution to the one of the population map
-        # Note the `-a` flag to g.region: ?
-        # To safely modify the region: grass.use_temp_region()  # FIXME
-        g.region(raster=aggregation,
-                nsres=ns_resolution,
-                ewres=ew_resolution,
-                flags='a',
-                quiet=True)
-
-        msg = "|! Computational resolution matched to {raster}"
-        msg = msg.format(raster=aggregation)
-        grass.debug(_(msg))
-        del(msg)
-
-        # Build MASK for current category & high quality recreation areas
-        msg = "Setting category '{c}' of '{a}' as a MASK"
-        grass.verbose(_(msg.format(c=category, a=aggregation)))
-        del(msg)
-
-        masking = 'if( {spectrum} == {highest_quality_category} && '
-        masking += '{aggregation} == {category}, '
-        masking += '1, null() )'
-        masking = masking.format(
-                spectrum=recreation_spectrum,
-                highest_quality_category=HIGHEST_RECREATION_CATEGORY,
-                aggregation=aggregation,
-                category=category)
-
-        masking_equation = equation.format(
-                result='MASK',
-                expression = masking)
-
-        grass.mapcalc(masking_equation, overwrite=True)
-
-        del(masking)
-        del(masking_equation)
-
-        # zoom to MASK
-        g.region(zoom='MASK',
-                nsres=ns_resolution,
-                ewres=ew_resolution,
-                quiet=True)
-
-        # Count number of cells within each land category
-        r.stats_zonal(
-                flags='r',
-                base=base,
-                cover=highest_spectrum,
-                method='count',
-                output=cells,
-                overwrite=True,
-                quiet=True)
-        cells_categories = grass.parse_command('r.category',
-                map=cells,
-                delimiter='\t')
-        grass.debug(_("Cells: {c}".format(c=cells_categories)))
-
-        # Build cell category and label rules for `r.category`
-        cells_rules = '\n'.join(['{0}:{1}'.format(key, value)
-            for key, value
-            in cells_categories.items()])
-        del(cells_categories)
-
-        # Discard areas out of MASK
-        copy_equation = equation.format(result=cells,
-                expression=cells)
-        r.mapcalc(copy_equation, overwrite=True)
-        del(copy_equation)
-
-        # Reassign cell category labels
-        r.category(
-                map=cells,
-                rules='-',
-                stdin=cells_rules,
-                separator=':')
-        del(cells_rules)
-
-        # Compute extent of each land category
-        extent_expression = "@{cells} * area()"
-        extent_expression = extent_expression.format(cells=cells)
-        extent_equation = equation.format(
-                result=extent,
-                expression=extent_expression)
-        r.mapcalc(extent_equation, overwrite=True)
-
-        # Write extent figures as labels
-        r.stats_zonal(
-                flags='r',
-                base=base,
-                cover=extent,
-                method='average',
-                output=extent,
-                overwrite=True,
-                verbose=False,
-                quiet=True)
-
-        # Write land suitability scores as an ASCII file
-        suitability_scores_as_labels = string_to_file(
-                SUITABILITY_SCORES_LABELS,
-                name=reclassified_base)
-        remove_normal_files_at_exit.append(suitability_scores_as_labels)
-
-        # Write scores as raster category labels
-        r.reclass(input=base,
-                output=base_scores,
-                rules=suitability_scores_as_labels,
-                overwrite=True,
-                quiet=True,
-                verbose=False)
-        remove_at_exit.append(base_scores)
-
-        # Compute weighted extents
-        weighted_expression = "@{extent} * float(@{scores})"
-        weighted_expression = weighted_expression.format(
-                extent=extent,
-                scores=base_scores)
-        weighted_equation = equation.format(
-                result=weighted,
-                expression = weighted_expression)
-        r.mapcalc(weighted_equation, overwrite=True)
-
-        # Write weighted extent figures as labels
-        r.stats_zonal(
-                flags='r',
-                base=base,
-                cover=weighted,
-                method='average',
-                output=weighted,
-                overwrite=True,
-                verbose=False,
-                quiet=True)
-
-        # Get weighted extents in a dictionary
-        weighted_extents = grass.parse_command('r.category',
-                map=weighted,
-                delimiter='\t')
-
-        # Compute the sum of all weighted extents and add to dictionary
-        category_sum = sum([float(x)
-            if not math.isnan(float(x))
-            else 0
-            for x
-            in weighted_extents.values()])
-        weighted_extents['sum'] = category_sum
-
-        # Create a map to hold fractions of each weighted extent to the sum
-        # See also:
-        # https://grasswiki.osgeo.org/wiki/LANDSAT#Hint:_Minimal_disk_space_copies
-        r.reclass(
-                input=base,
-                output=fractions,
-                rules='-',
-                stdin='*=*',
-                verbose=False,
-                quiet=True)
-
-        # Compute weighted fractions of land types
-        fraction_category_label = {key: float(value) / weighted_extents['sum']
-                for (key, value)
-                in weighted_extents.iteritems()
-                if key is not 'sum'}
-
-        # Build fraction category and label rules for `r.category`
-        fraction_rules = '\n'.join(['{0}:{1}'.format(key, value)
-            for key, value
-            in fraction_category_label.items()])
-        del(fraction_category_label)
-
-        # Set rules
-        r.category(
-                map=fractions,
-                rules='-',
-                stdin=fraction_rules,
-                separator=':')
-        del(fraction_rules)
-
-        # Assert that sum of fractions is ~1
-        fraction_categories = grass.parse_command('r.category',
-                map=fractions,
-                delimiter='\t')
-
-        fractions_sum = sum([float(x)
-            if not math.isnan(float(x))
-            else 0
-            for x
-            in fraction_categories.values()])
-        msg = "Fractions: {f}".format(f=fraction_categories)
-        grass.debug(_(msg))
-        del(msg)
-
-        # g.message(_("Sum: {:.17g}".format(fractions_sum)))
-        assert abs(fractions_sum -1 ) < 1.e-6, "Sum of fractions is != 1"
-
-        # Compute flow
-        flow_expression = "@{fractions} * @{flow}"
-        flow_expression = flow_expression.format(fractions=fractions,
-                flow=flow_in_base)
-        flow_equation = equation.format(
-                result=flow,
-                expression=flow_expression)
-        r.mapcalc(flow_equation, overwrite=True)
-
-        # Write flow figures as raster category labels
-        r.stats_zonal(base=reclassified_base,
-                flags='r',
-                cover=flow,
-                method='sum',
-                output=flow_in_category,
-                overwrite=True,
-                verbose=False,
-                quiet=True)
-
-        # Parse flow categories and labels
-        flow_categories = grass.parse_command('r.category',
-                map=flow_in_category,
-                delimiter='\t')
-        grass.debug(_("Flow: {c}".format(c=flow_categories)))
-
-        # Build flow category and label rules for `r.category`
-        flow_rules = '\n'.join(['{0}:{1}'.format(key, value)
-            for key, value
-            in flow_categories.items()])
-        del(flow_categories)
-
-        # Discard areas out of MASK
-
-        # Check here again!
-        # Output patch of all flow maps?
-
-        copy_equation = equation.format(result=flow_in_category,
-                expression=flow_in_category)
-        r.mapcalc(copy_equation, overwrite=True)
-        del(copy_equation)
-
-        # Reassign cell category labels
-        r.category(
-                map=flow_in_category,
-                rules='-',
-                stdin=flow_rules,
-                separator=':')
-        del(flow_rules)
-
-        # Update title
-        reclassified_base_title += ' ' + category
-        r.support(flow_in_category,
-                title=reclassified_base_title)
-
-        # if info:
-        #     r.report(flags='hn',
-        #             map=(flow_in_category),
-        #             units=('k','c','p'))
-
-        if print_only:
-            r.stats(input=(flow_in_category),
-                    output='-',
-                    flags='nacpl',
-                    separator=COMMA,
-                    quiet=True)
-
-        if not print_only:
-
-            if 'flow_column_name' in kwargs:
-                flow_column_name = kwargs.get('flow_column_name')
-                flow_column_prefix = flow_column_name + category
-            else:
-                flow_column_name = 'flow'
-                flow_column_prefix = flow_column_name + category
-
-            # Produce vector map(s)
-            if 'vector' in kwargs:
-
-                vector = kwargs.get('vector')
-
-                # The following is wrong
-
-                # update_vector(vector=vector,
-                #         raster=flow_in_category,
-                #         methods=METHODS,
-                #         column_prefix=flow_column_prefix)
-
-                # What can be done?
-
-                # Maybe update columns of an existing map from the columns of
-                # the following vectorised raster map(s)
-                # ?
-
-                r.to_vect(
-                        input=flow_in_category,
-                        output=flow_in_category,
-                        type='area',
-                        quiet=True)
-
-                # Value is the ecosystem type
-                v.db_renamecolumn(
-                        map=flow_in_category,
-                        column=('value', 'ecosystem'))
-
-                # New column for flow values
-                addcolumn_string = flow_column_name + ' double'
-                v.db_addcolumn(
-                        map=flow_in_category,
-                        columns=addcolumn_string)
-
-                # The raster category 'label' is the 'flow'
-                v.db_update(
-                        map=flow_in_category,
-                        column='flow',
-                        query_column='label')
-                v.db_dropcolumn(
-                        map=flow_in_category,
-                        columns='label')
-
-                # Update the aggregation raster categories
-                v.db_addcolumn(
-                        map=flow_in_category,
-                        columns='aggregation_id int')
-                v.db_update(
-                        map=flow_in_category,
-                        column='aggregation_id',
-                        value=category)
-
-                v.colors(map=flow_in_category,
-                        raster=flow_in_category,
-                        quiet=True)
-
-            # get statistics
-            dictionary = get_raster_statistics(
-                    map_one=aggregation,  # reclassified_base
-                    map_two=flow_in_category,
-                    separator='|',
-                    flags='nlcap')
-
-            # merge 'dictionary' with global 'statistics_dictionary'
-            statistics_dictionary = merge_two_dictionaries(
-                    statistics_dictionary,
-                    dictionary)
-            del(dictionary)
-
-        # It is important to remove the MASK!
-        r.mask(flags='r', quiet=True)
-
-
-    # FIXME
-
-    # Add "reclassified_base" map to "remove_at_exit" here, so as to be after
-    # all reclassified maps that derive from it
-
-    # remove the map 'reclassified_base'
-    # g.remove(flags='f', type='raster', name=reclassified_base, quiet=True)
-    # remove_at_exit.append(reclassified_base)
-
-    if not print_only:
-
-        r.patch(flags='',
-                input=flows,
-                output=flow_in_reclassified_base,
-                quiet=True)
-
-        if 'vector' in kwargs:
-            # Patch all flow vector maps in one
-            v.patch(flags='e',
-                    input=flows,
-                    output=flow_in_reclassified_base,
-                    overwrite=True,
-                    quiet=True)
-
-        # export to csv
-        if 'supply_filename' in kwargs:
-
-            supply_filename = kwargs.get('supply_filename')
-            supply_filename += CSV_EXTENSION
-            nested_dictionary_to_csv(supply_filename,
-                    statistics_dictionary)
-            del(supply_filename)
-
-        if 'use_filename' in kwargs:
-
-            use_filename = kwargs.get('use_filename')
-            use_filename += CSV_EXTENSION
-            uses = compile_use_table(statistics_dictionary)
-            dictionary_to_csv(use_filename, uses)
-            del(use_filename)
-            del(statistics_dictionary)
-            del(uses)
-
-    # Maybe return list of flow maps?  Requires unique flow map names
-    return flows
-
-    grass.del_temp_region()  # restoring previous region settings
-    # grass.verbose("Original Region restored")
-
-def main():
-    """
-    Main program
-    """
-
-    '''Flags and Options'''
-
-    global average_filter
-    average_filter = flags['f']
-
-    global info, save_temporary_maps, print_only
-    landuse_extent = flags['e']
-    save_temporary_maps = flags['s']
-    info = flags['i']
-    print_only = flags['p']
-
-    global timestamp
-    timestamp = options['timestamp']
-
-    global remove_at_exit, remove_normal_files_at_exit, rename_at_exit
-    remove_at_exit = []
-    remove_normal_files_at_exit = []
-    rename_at_exit = []
-
-    global metric, units
-    metric = options['metric']
-    units = options['units']
-    if len(units) > 1:
-        units = units.split(',')
-
-    '''names for input, output, output suffix, options'''
-
-    global mask
-    mask = options['mask']
-
-    '''
-    following some hard-coded names -- review and remove!
-    '''
-
-    land = options['land']
-    land_component_map_name = tmp_map_name(name='land_component')
-
-    water = options['water']
-    water_component_map_name = tmp_map_name(name='water_component')
-
-    natural = options['natural']
-    natural_component_map_name = tmp_map_name(name='natural_component')
-
-    urban = options['urban']
-    urban_component_map='urban_component'
-
-    infrastructure = options['infrastructure']
-    infrastructure_component_map_name = tmp_map_name(name='infrastructure_component')
-
-    recreation = options['recreation']
-    recreation_component_map_name = tmp_map_name(name='recreation_component')
-
-    '''Land components'''
-
-    landuse = options['landuse']
-    if landuse:
-        # Check datatype: a land use map should be categorical, i.e. of type CELL
-        landuse_datatype = grass.raster.raster_info(landuse)['datatype']
-        if landuse_datatype != 'CELL':
-            msg = ("The '{landuse}' input map "
-                    "should be a categorical one "
-                    "and of type 'CELL'. "
-                    "Perhaps you meant to use the 'land' input option instead?")
-            grass.fatal(_(msg.format(landuse=landuse)))
-
-    suitability_map_name = tmp_map_name(name='suitability')
-    suitability_scores = options['suitability_scores']
-
-    if landuse and suitability_scores and ':' not in suitability_scores:
-        msg = "Suitability scores from file: {scores}."
-        msg = msg.format(scores = suitability_scores)
-        grass.verbose(_(msg))
-
-    if landuse and not suitability_scores:
-        msg = "Using internal rules to score land use classes in '{map}'"
-        msg = msg.format(map=landuse)
-        grass.warning(_(msg))
-
-        suitability_scores = string_to_file(SUITABILITY_SCORES,
-                name=suitability_map_name)
-        remove_normal_files_at_exit.append(suitability_scores)
-
-    if landuse and suitability_scores and ':' in suitability_scores:
-        msg = "Using provided string of rules to score land use classes in {map}"
-        msg = msg.format(map=landuse)
-        grass.verbose(_(msg))
-        suitability_scores = string_to_file(suitability_scores,
-                name=suitability_map_name)
-        remove_normal_files_at_exit.append(suitability_scores)
-
-
-    # FIXME -----------------------------------------------------------------
-
-    # Use one landcover input if supply is requested
-    # Use one set of land cover reclassification rules
-
-    landcover = options['landcover']
-
-    if not landcover:
-        landcover = landuse
-        msg = "Land cover map 'landcover' not given. "
-        msg += "Attempt to use the '{landuse}' map to derive areal statistics"
-        msg = msg.format(landuse=landuse)
-        grass.verbose(_(msg))
-
-    maes_ecosystem_types = 'maes_ecosystem_types'
-    maes_ecosystem_types_scores = 'maes_ecosystem_types_scores'
-    landcover_reclassification_rules = options['land_classes']
-
-    # if 'land_classes' is a file
-    if (landcover and
-        landcover_reclassification_rules and
-        ':' not in landcover_reclassification_rules):
-        msg = "Land cover reclassification rules from file: {rules}."
-        msg = msg.format(rules = landcover_reclassification_rules)
-        grass.verbose(_(msg))
-
-    # if 'land_classes' not given
-    if landcover and not landcover_reclassification_rules:
-
-        # if 'landcover' is not the MAES land cover,
-        # then use internal reclassification rules
-        # how to test:
-            # 1. landcover is not a "MAES" land cover
-            # 2. landcover is an Urban Atlas one?
-
-        msg = "Using internal rules to reclassify the '{map}' map"
-        msg = msg.format(map=landcover)
-        grass.verbose(_(msg))
-
-        landcover_reclassification_rules = string_to_file(
-                URBAN_ATLAS_TO_MAES_NOMENCLATURE,
-                name=maes_ecosystem_types)
-        remove_normal_files_at_exit.append(landcover_reclassification_rules)
-
-        # if landcover is a "MAES" land cover, no need to reclassify!
-
-    if (landuse and
-        landcover_reclassification_rules and
-        ':' in landcover_reclassification_rules):
-        msg = "Using provided string of rules to reclassify the '{map}' map"
-        msg = msg.format(map=landcover)
-        grass.verbose(_(msg))
-        landcover_reclassification_rules =  string_to_file(
-                landcover_reclassification_rules,
-                name=maes_land_classes)
-        remove_normal_files_at_exit.append(landcover_reclassification_rules)
-
-    # FIXME -----------------------------------------------------------------
-
-    '''Water components'''
-
-    lakes = options['lakes']
-    lakes_coefficients = options['lakes_coefficients']
-    lakes_proximity_map_name = 'lakes_proximity'
-    coastline = options['coastline']
-    coast_proximity_map_name = 'coast_proximity'
-    coast_geomorphology = options['coast_geomorphology']
-    # coast_geomorphology_coefficients = options['geomorphology_coefficients']
-    coast_geomorphology_map_name = 'coast_geomorphology'
-    bathing_water = options['bathing_water']
-    bathing_water_coefficients = options['bathing_coefficients']
-    bathing_water_proximity_map_name = 'bathing_water_proximity'
-
-    '''Natural components'''
-
-    protected = options['protected']
-    protected_scores = options['protected_scores']
-    protected_areas_map_name = 'protected_areas'
-
-    '''Artificial areas'''
-
-    artificial = options['artificial']
-    artificial_proximity_map_name='artificial_proximity'
-    artificial_distance_categories = options['artificial_distances']
-
-    roads = options['roads']
-    roads_proximity_map_name = 'roads_proximity'
-    roads_distance_categories = options['roads_distances']
-
-    artificial_accessibility_map_name='artificial_accessibility'
-
-    '''Devaluation'''
-
-    devaluation = options['devaluation']
-
-    '''Aggregational boundaries'''
-
-    base = options['base']
-    base_vector = options['base_vector']
-    aggregation = options['aggregation']
-
-    '''Population'''
-
-    population = options['population']
-    if population:
-        population_ns_resolution = grass.raster_info(population)['nsres']
-        population_ew_resolution = grass.raster_info(population)['ewres']
-
-    '''Outputs'''
-
-    potential_title = "Recreation potential"
-    recreation_potential = options['potential']  # intermediate / output
-    recreation_potential_map_name = tmp_map_name(name='recreation_potential')
-
-    opportunity_title = "Recreation opportunity"
-    recreation_opportunity=options['opportunity']
-    recreation_opportunity_map_name='recreation_opportunity'
-
-    spectrum_title = "Recreation spectrum"
-    # if options['spectrum']:
-    recreation_spectrum = options['spectrum']  # output
-    # else:
-    #     recreation_spectrum = 'recreation_spectrum'
-    # recreation_spectrum_component_map_name =
-    #       tmp_map_name(name='recreation_spectrum_component_map')
-
-    spectrum_distance_categories = options['spectrum_distances']
-    if ':' in spectrum_distance_categories:
-        spectrum_distance_categories = string_to_file(
-                spectrum_distance_categories,
-                name=recreation_spectrum)
-        # remove_at_exit.append(spectrum_distance_categories) -- Not a map!
-        remove_normal_files_at_exit.append(spectrum_distance_categories)
-
-    highest_spectrum = 'highest_recreation_spectrum'
-    crossmap = 'crossmap'  # REMOVEME
-
-    demand = options['demand']
-    unmet_demand = options['unmet']
-
-    flow = options['flow']
-    flow_map_name = 'flow'
-
-    supply = options['supply']  # use as CSV filename prefix
-    use = options['use']  # use as CSV filename prefix
-
-    """ First, care about the computational region"""
-
-    if mask:
-        msg = "Masking NULL cells based on '{mask}'".format(mask=mask)
-        grass.verbose(_(msg))
-        r.mask(raster=mask, overwrite=True, quiet=True)
-
-    if landuse_extent:
-        grass.use_temp_region()  # to safely modify the region
-        g.region(flags='p', raster=landuse) # Set region to 'mask'
-        msg = "|! Computational resolution matched to {raster}"
-        msg = msg.format(raster=landuse)
-        g.message(_(msg))
-
-    """Land Component
-            or Suitability of Land to Support Recreation Activities (SLSRA)"""
-
-    land_component = []  # a list, use .extend() wherever required
-
-    if land:
-
-        land_component = land.split(',')
-
-    if landuse and suitability_scores:
-
-        msg = "Deriving land suitability from '{landuse}' "
-        msg += "based on rules described in file '{rules}'"
-        grass.verbose(msg.format(landuse=landuse, rules=suitability_scores))
-
-        # suitability is the 'suitability_map_name'
-        recode_map(raster=landuse,
-                rules=suitability_scores,
-                colors=SCORE_COLORS,
-                output=suitability_map_name)
-
-        append_map_to_component(
-                raster=suitability_map_name,
-                component_name='land',
-                component_list=land_component)
-
-    '''Water Component'''
-
-    water_component = []
-    water_components = []
-
-    if water:
-
-        water_component = water.split(',')
-        msg = "Water component includes currently: {component}"
-        msg = msg.format(component=water_component)
-        grass.debug(_(msg))
-        # grass.verbose(_(msg))
-
-    if lakes:
-
-        if lakes_coefficients:
-            metric, constant, kappa, alpha, score = get_coefficients(
-                    lakes_coefficients)
-
-        lakes_proximity = compute_attractiveness(
-                raster = lakes,
-                metric = EUCLIDEAN,
-                constant = constant,
-                kappa = kappa,
-                alpha = alpha,
-                score = score,
-                mask = lakes)
-
-        del(constant)
-        del(kappa)
-        del(alpha)
-        del(score)
-
-        append_map_to_component(
-                raster=lakes_proximity,
-                component_name='water',
-                component_list=water_components)
-
-    if coastline:
-
-        coast_proximity = compute_attractiveness(
-                raster = coastline,
-                metric = EUCLIDEAN,
-                constant = WATER_PROXIMITY_CONSTANT,
-                alpha = WATER_PROXIMITY_ALPHA,
-                kappa = WATER_PROXIMITY_KAPPA,
-                score = WATER_PROXIMITY_SCORE)
-
-        append_map_to_component(
-                raster=coast_proximity,
-                component_name='water',
-                component_list=water_components)
-
-    if coast_geomorphology:
-
-        try:
-
-            if not coastline:
-                msg = "The coastline map is required in order to "
-                msg += "compute attractiveness based on the "
-                msg += "coast geomorphology raster map"
-                msg = msg.format(c=water_component)
-                grass.fatal(_(msg))
-
-        except NameError:
-            grass.fatal(_("No coast proximity"))
-
-        coast_attractiveness = neighborhood_function(
-                raster=coast_geomorphology,
-                method = NEIGHBORHOOD_METHOD,
-                size = NEIGHBORHOOD_SIZE,
-                distance_map=coast_proximity)
-
-        append_map_to_component(
-                raster=coast_attractiveness,
-                component_name='water',
-                component_list=water_components)
-
-    if bathing_water:
-
-        if bathing_water_coefficients:
-            metric, constant, kappa, alpha = get_coefficients(
-                    bathing_water_coefficients)
-
-        bathing_water_proximity = compute_attractiveness(
-                raster = bathing_water,
-                metric = EUCLIDEAN,
-                constant = constant,
-                kappa = kappa,
-                alpha = alpha)
-
-        del(constant)
-        del(kappa)
-        del(alpha)
-
-        append_map_to_component(
-                raster=bathing_water_proximity,
-                component_name='water',
-                component_list=water_components)
-
-    # merge water component related maps in one list
-    water_component += water_components
-
-    '''Natural Component'''
-
-    natural_component = []
-    natural_components = []
-
-    if natural:
-
-        natural_component = natural.split(',')
-
-    if protected:
-        msg = "Scoring protected areas '{protected}' based on '{rules}'"
-        grass.verbose(_(msg.format(protected=protected, rules=protected_scores)))
-
-        protected_areas = protected_areas_map_name
-
-        recode_map(raster=protected,
-                rules=protected_scores,
-                colors=SCORE_COLORS,
-                output=protected_areas)
-
-        append_map_to_component(
-                raster=protected_areas,
-                component_name='natural',
-                component_list=natural_components)
-
-    # merge natural resources component related maps in one list
-    natural_component += natural_components
-
-    """ Normalize land, water, natural inputs
-    and add them to the recreation potential component"""
-
-    recreation_potential_component = []
-
-    if land_component:
-
-        for dummy_index in land_component:
-
-            # remove 'land_map' from 'land_component'
-            # process and add it back afterwards
-            land_map = land_component.pop(0)
-
-            '''
-            This section sets NULL cells to 0.
-            Because `r.null` operates on the complete input raster map,
-            manually subsetting the input map is required.
-            '''
-            suitability_map = tmp_map_name(name=land_map)
-            subset_land = equation.format(result = suitability_map,
-                    expression = land_map)
-            r.mapcalc(subset_land)
-
-            grass.debug(_("Setting NULL cells to 0"))  # REMOVEME ?
-            r.null(map=suitability_map, null=0)  # Set NULLs to 0
-
-            msg = "\nAdding land suitability map '{suitability}' "
-            msg += "to 'Recreation Potential' component\n"
-            msg = msg.format(suitability = suitability_map)
-            grass.verbose(_(msg))
-
-            # add 'suitability_map' to 'land_component'
-            land_component.append(suitability_map)
-
-    if len(land_component) > 1:
-        grass.verbose(_("\nNormalize 'Land' component\n"))
-        zerofy_and_normalise_component(land_component, THRESHHOLD_ZERO,
-                land_component_map_name)
-        recreation_potential_component.extend(land_component)
-    else:
-        recreation_potential_component.extend(land_component)
-
-    if land_component and average_filter:
-        smooth_component(
-                land_component,
-                method='average',
-                size=7)
-
-    # remove_at_exit.extend(land_component)
-
-    if len(water_component) > 1:
-        grass.verbose(_("\nNormalize 'Water' component\n"))
-        zerofy_and_normalise_component(water_component, THRESHHOLD_ZERO,
-                water_component_map_name)
-        recreation_potential_component.append(water_component_map_name)
-    else:
-        recreation_potential_component.extend(water_component)
-
-    # remove_at_exit.append(water_component_map_name)
-
-    if len(natural_component) > 1:
-        grass.verbose(_("\nNormalize 'Natural' component\n"))
-        zerofy_and_normalise_component(
-                components=natural_component,
-                threshhold=THRESHHOLD_ZERO,
-                output_name=natural_component_map_name)
-        recreation_potential_component.append(natural_component_map_name)
-    else:
-        recreation_potential_component.extend(natural_component)
-
-    if natural_component and average_filter:
-        smooth_component(
-                natural_component,
-                method='average',
-                size=7)
-
-    # remove_at_exit.append(natural_component_map_name)
-
-    """ Recreation Potential [Output] """
-
-    tmp_recreation_potential = tmp_map_name(name=recreation_potential_map_name)
-
-    msg = "Computing intermediate 'Recreation Potential' map: '{potential}'"
-    grass.verbose(_(msg.format(potential=tmp_recreation_potential)))
-    grass.debug(_("Maps: {maps}".format(maps=recreation_potential_component)))
-
-    zerofy_and_normalise_component(
-            components = recreation_potential_component,
-            threshhold = THRESHHOLD_ZERO,
-            output_name = tmp_recreation_potential)
-
-    # recode recreation_potential
-    tmp_recreation_potential_categories = tmp_map_name(
-            name=recreation_potential)
-
-    msg = "\nClassifying '{potential}' map"
-    msg = msg.format(potential=tmp_recreation_potential)
-    grass.verbose(_(msg))
-
-    classify_recreation_component(
-            component = tmp_recreation_potential,
-            rules = RECREATION_POTENTIAL_CATEGORIES,
-            output_name = tmp_recreation_potential_categories)
-
-    if recreation_potential:
-
-        # export 'recreation_potential' map and
-        # use 'output_name' for the temporary 'potential' map for spectrum
-        tmp_recreation_potential_categories = export_map(
-                input_name = tmp_recreation_potential_categories,
-                title = potential_title,
-                categories = POTENTIAL_CATEGORY_LABELS,
-                colors = POTENTIAL_COLORS,
-                output_name = recreation_potential)
-
-    # Infrastructure to access recreational facilities, amenities, services
-    # Required for recreation opportunity and successively recreation spectrum
-
-    if infrastructure and not any([recreation_opportunity,
-        recreation_spectrum, demand, flow, supply]):
-        msg = ("Infrastructure is not required "
-        "to derive the 'potential' recreation map.")
-        grass.warning(_(msg))
-
-    if any([recreation_opportunity, recreation_spectrum, demand, flow, supply]):
-
-        infrastructure_component = []
-        infrastructure_components = []
-
-        if infrastructure:
-            infrastructure_component.append(infrastructure)
-
-        '''Artificial surfaces (includung Roads)'''
-
-        if artificial and roads:
-
-            msg = "Roads distance categories: {c}"
-            msg = msg.format(c=roads_distance_categories)
-            grass.debug(_(msg))
-            roads_proximity = compute_artificial_proximity(
-                    raster = roads,
-                    distance_categories = roads_distance_categories,
-                    output_name = roads_proximity_map_name)
-
-            msg = "Artificial distance categories: {c}"
-            msg = msg.format(c=artificial_distance_categories)
-            grass.debug(_(msg))
-            artificial_proximity = compute_artificial_proximity(
-                    raster = artificial,
-                    distance_categories = artificial_distance_categories,
-                    output_name = artificial_proximity_map_name)
-
-            artificial_accessibility = compute_artificial_accessibility(
-                    artificial_proximity,
-                    roads_proximity,
-                    output_name = artificial_accessibility_map_name)
-
-            infrastructure_components.append(artificial_accessibility)
-
-        # merge infrastructure component related maps in one list
-        infrastructure_component += infrastructure_components
-
-    # # Recreational facilities, amenities, services
-
-    # recreation_component = []
-    # recreation_components = []
-
-    # if recreation:
-    #     recreation_component.append(recreation)
-
-    # # merge recreation component related maps in one list
-    # recreation_component += recreation_components
-
-    """ Recreation Spectrum """
-
-    if any([recreation_spectrum, demand, flow, supply]):
-
-        recreation_opportunity_component = []
-
-        # input
-        zerofy_and_normalise_component(
-                components = infrastructure_component,
-                threshhold = THRESHHOLD_ZERO,
-                output_name = infrastructure_component_map_name)
-
-        recreation_opportunity_component.append(
-                infrastructure_component_map_name)
-
-        # # input
-        # zerofy_and_normalise_component(recreation_component,
-        #         THRESHHOLD_0001, recreation_component_map_name)
-        # recreation_opportunity_component.append(recreation_component_map_name)
-        # remove_at_exit.append(recreation_component_map_name)
-
-        # intermediate
-
-        # REVIEW --------------------------------------------------------------
-        tmp_recreation_opportunity = tmp_map_name(
-                name=recreation_opportunity_map_name)
-        msg = "Computing intermediate opportunity map '{opportunity}'"
-        grass.debug(_(msg.format(opportunity=tmp_recreation_opportunity)))
-
-        grass.verbose(_("\nNormalize 'Recreation Opportunity' component\n"))
-        grass.debug(_("Maps: {maps}".format(maps=recreation_opportunity_component)))
-
-        zerofy_and_normalise_component(
-                components = recreation_opportunity_component,
-                threshhold = THRESHHOLD_0001,
-                output_name = tmp_recreation_opportunity)
-
-        # Why threshhold 0.0003? How and why it differs from 0.0001?
-        # -------------------------------------------------------------- REVIEW
-
-        msg = "Classifying '{opportunity}' map"
-        grass.verbose(msg.format(opportunity=tmp_recreation_opportunity))
-        del(msg)
-
-        # recode opportunity_component
-        tmp_recreation_opportunity_categories = tmp_map_name(
-                name=recreation_opportunity)
-
-        classify_recreation_component(
-                component = tmp_recreation_opportunity,
-                rules = RECREATION_OPPORTUNITY_CATEGORIES,
-                output_name = tmp_recreation_opportunity_categories)
-
-        ''' Recreation Opportunity [Output]'''
-
-        if recreation_opportunity:
-
-            # export 'recreation_opportunity' map and
-            # use 'output_name' for the temporary 'potential' map for spectrum
-            tmp_recreation_opportunity_categories = export_map(
-                    input_name = tmp_recreation_opportunity_categories,
-                    title = opportunity_title,
-                    categories = OPPORTUNITY_CATEGORY_LABELS,
-                    colors = OPPORTUNITY_COLORS,
-                    output_name = recreation_opportunity)
-
-        # Recreation Spectrum: Potential + Opportunity [Output]
-
-        if not recreation_spectrum and any([demand, flow, supply]):
-            recreation_spectrum = tmp_map_name(name='recreation_spectrum')
-            remove_at_exit.append(recreation_spectrum)
-
-        recreation_spectrum = compute_recreation_spectrum(
-                potential = tmp_recreation_potential_categories,
-                opportunity = tmp_recreation_opportunity_categories,
-                spectrum = recreation_spectrum)
-
-        msg = "Writing '{spectrum}' map"
-        msg = msg.format(spectrum=recreation_spectrum)
-        grass.verbose(_(msg))
-        del(msg)
-        get_univariate_statistics(recreation_spectrum)
-
-        # get category labels
-        spectrum_categories = 'categories_of_'
-        spectrum_categories += recreation_spectrum
-        spectrum_category_labels = string_to_file(
-                SPECTRUM_CATEGORY_LABELS,
-                name=spectrum_categories)
-
-        # add to list for removal
-        remove_normal_files_at_exit.append(spectrum_category_labels)
-
-        # update category labels, meta and colors
-        spectrum_categories = 'categories_of_'
-
-        r.category(map=recreation_spectrum,
-                rules=spectrum_category_labels,
-                separator=':')
-
-        update_meta(recreation_spectrum, spectrum_title)
-
-        r.colors(map=recreation_spectrum, rules='-', stdin = SPECTRUM_COLORS,
-                quiet=True)
-
-        if base_vector:
-            update_vector(vector=base_vector,
-                    raster=recreation_spectrum,
-                    methods=METHODS,
-                    column_prefix='spectrum')
-
-    """Valuation Tables"""
-
-    if any([demand, flow, supply, aggregation]):
-
-        '''Highest Recreation Spectrum == 9'''
-
-        expression = "if({spectrum} == {highest_recreation_category}, {spectrum}, null())"
-        highest_spectrum_expression = expression.format(
-                spectrum=recreation_spectrum,
-                highest_recreation_category=HIGHEST_RECREATION_CATEGORY)
-        highest_spectrum_equation = equation.format(result=highest_spectrum,
-                expression=highest_spectrum_expression)
-        r.mapcalc(highest_spectrum_equation, overwrite=True)
-
-        '''Distance map'''
-
-        distance_to_highest_spectrum = tmp_map_name(name=highest_spectrum)
-        r.grow_distance(input=highest_spectrum,
-                distance=distance_to_highest_spectrum,
-                metric=metric,
-                quiet=True,
-                overwrite=True)
-
-        '''Distance categories'''
-
-        distance_categories_to_highest_spectrum = 'categories_of_'
-        distance_categories_to_highest_spectrum += distance_to_highest_spectrum
-        remove_at_exit.append(distance_categories_to_highest_spectrum)  # FIXME
-
-        recode_map(raster=distance_to_highest_spectrum,
-                rules=spectrum_distance_categories,
-                colors=SCORE_COLORS,
-                output=distance_categories_to_highest_spectrum)
-
-        spectrum_distance_category_labels = string_to_file(
-                SPECTRUM_DISTANCE_CATEGORY_LABELS,
-                name=distance_categories_to_highest_spectrum)
-        remove_normal_files_at_exit.append(spectrum_distance_category_labels)
-
-        r.category(map=distance_categories_to_highest_spectrum,
-                rules=spectrum_distance_category_labels,
-                separator=':')
-
-        '''Combine Base map and Distance Categories'''
-
-        tmp_crossmap = tmp_map_name(name=crossmap)
-        r.cross(input=(distance_categories_to_highest_spectrum, base),
-                flags='z',
-                output=tmp_crossmap,
-                quiet=True)
-
-        grass.use_temp_region()  # to safely modify the region
-        g.region(nsres=population_ns_resolution,
-                 ewres=population_ew_resolution,
-                 flags='a')  # Resolution should match 'population' FIXME
-        msg = "|! Computational extent & resolution matched to {raster}"
-        msg = msg.format(raster=landuse)
-        grass.verbose(_(msg))
-
-        # if info:
-        #     population_statistics = get_univariate_statistics(population)
-        #     population_total = population_statistics['sum']
-        #     msg = "|i Population statistics: {s}".format(s=population_total)
-        #     g.message(_(msg))
-
-        '''Demand Distribution'''
-
-        if any([flow, supply, aggregation]) and not demand:
-            demand = tmp_map_name(name='demand')
-
-        r.stats_zonal(base=tmp_crossmap,
-                flags='r',
-                cover=population,
-                method='sum',
-                output=demand,
-                overwrite=True,
-                quiet=True)
-
-        # ------------------------------------------- REMOVEME
-        # if info:
-        #     r.report(map=demand, units=('k','c','p'))
-        # ------------------------------------------- REMOVEME
-
-        # copy 'reclassed' as 'normal' map (r.mapcalc)
-            # so as to enable removal of it and its 'base' map
-        demand_copy = demand + '_copy'
-        copy_expression = "{input_raster}"
-        copy_expression = copy_expression.format(input_raster=demand)
-        copy_equation = equation.format(result=demand_copy,
-                expression=copy_expression)
-        r.mapcalc(copy_equation, overwrite=True)
-
-        # remove the reclassed map 'demand'
-        g.remove(flags='f', type='raster', name=demand, quiet=True)
-
-        # rename back to 'demand'
-        g.rename(raster=(demand_copy,demand), quiet=True)
-
-        if demand and base_vector:
-            update_vector(vector=base_vector,
-                    raster=demand,
-                    methods=METHODS,
-                    column_prefix='demand')
-
-        '''Unmet Demand'''
-
-        if unmet_demand:
-
-            # compute unmet demand
-
-            unmet_demand_expression = compute_unmet_demand(
-                    distance=distance_categories_to_highest_spectrum,
-                    constant=MOBILITY_CONSTANT,
-                    coefficients=MOBILITY_COEFFICIENTS[4],
-                    population=demand,
-                    score=MOBILITY_SCORE)
-                    # suitability=suitability)  # Not used.
-                    # Maybe it can, though, after successfully testing its
-                    # integration to build_distance_function().
-
-            grass.debug(_("Unmet demand function: {f}".format(f=unmet_demand_expression)))
-
-            unmet_demand_equation = equation.format(result=unmet_demand,
-                    expression=unmet_demand_expression)
-            r.mapcalc(unmet_demand_equation, overwrite=True)
-
-            if base_vector:
-                update_vector(vector=base_vector,
-                        raster=unmet_demand,
-                        methods=METHODS,
-                        column_prefix='unmet')
-
-        '''Mobility function'''
-
-        if not flow and any([supply, aggregation]):
-
-            flow = flow_map_name
-            remove_at_exit.append(flow)
-
-        if flow or any ([supply, aggregation]):
-
-            mobility_expression = mobility_function(
-                    distance=distance_categories_to_highest_spectrum,
-                    constant=MOBILITY_CONSTANT,
-                    coefficients=MOBILITY_COEFFICIENTS,
-                    population=demand,
-                    score=MOBILITY_SCORE)
-                    # suitability=suitability)  # Not used.
-                    # Maybe it can, though, after successfully testing its
-                    # integration to build_distance_function().
-
-            msg = "Mobility function: {f}"
-            grass.debug(_(msg.format(f=mobility_expression)))
-            del(msg)
-
-            '''Flow map'''
-
-            mobility_equation = equation.format(result=flow,
-                    expression=mobility_expression)
-            r.mapcalc(mobility_equation, overwrite=True)
-
-            if base_vector:
-                update_vector(vector=base_vector,
-                        raster=flow_map_name,
-                        methods=METHODS,
-                        column_prefix='flow')
-
-    '''Supply Table'''
-
-    if aggregation:
-
-        supply_parameters = {}
-
-        if supply:
-            supply_parameters.update({'supply_filename': supply})
-
-        if use:
-            supply_parameters.update({'use_filename': use})
-
-        if base_vector:
-            supply_parameters.update({'vector': base_vector})
-
-        compute_supply(
-                base = landcover,
-                recreation_spectrum = recreation_spectrum,
-                highest_spectrum = highest_spectrum,
-                base_reclassification_rules = landcover_reclassification_rules,
-                reclassified_base = maes_ecosystem_types,
-                reclassified_base_title = 'MAES ecosystem types',
-                flow = flow,
-                flow_map_name = flow_map_name,
-                aggregation = aggregation,
-                ns_resolution = population_ns_resolution,
-                ew_resolution = population_ew_resolution,
-                **supply_parameters)
-
-    # restore region
-    if landuse_extent:
-        grass.del_temp_region()  # restoring previous region settings
-        grass.verbose("Original Region restored")
-
-    # print citation
-    if info:
-        citation = 'Citation: ' + citation_recreation_potential
-        g.message(citation)
-
-if __name__ == "__main__":
-    options, flags = grass.parser()
-    atexit.register(cleanup)
-    sys.exit(main())

Added: grass-addons/grass7/raster/r.estimap.recreation/requirements-dev.txt
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/requirements-dev.txt	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/requirements-dev.txt	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1 @@
+pytest >= 4.0

Added: grass-addons/grass7/raster/r.estimap.recreation/setup.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/setup.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/setup.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+import os
+import sys
+
+from setuptools import setup, find_packages
+
+os.chdir(os.path.dirname(sys.argv[0]) or ".")
+
+with open("README.md", "r") as fh:
+    long_description = fh.read()
+
+setup(
+    name="r.estimap.recreation",
+    version="3",
+    description="Implementation of ESTIMAP recreation as a GRASS GIS add-on",
+    long_description=long_description,
+    url="https://gitlab.com/natcapes/r.estimap.recreation",
+    author="Nikos Alexandris",
+    author_email="nik at nikosalexandris.net",
+    # list of valid classifiers
+    # https://pypi.python.org/pypi?%3Aaction=list_classifiers
+    classifiers=[
+        "Development Status :: 3 - Beta",
+        "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 3",
+        "License :: EUPL v 1.2 :: GNU General Public License v3 or later (GPLv3+)",
+    ],
+    packages=find_packages(),
+)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/spectrum.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/spectrum_high_provision_near.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/test.r.estimap.recreation.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/test.r.estimap.recreation.py	2019-02-28 20:51:39 UTC (rev 74142)
+++ grass-addons/grass7/raster/r.estimap.recreation/test.r.estimap.recreation.py	2019-03-01 11:47:49 UTC (rev 74143)
@@ -1,205 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-"""
-Name       Tests on ...
-Purpose    Test for no difference between the input and output raster maps
-after recoding based on a set of pre-defind rules
-
-License    (C) 2018 by the GRASS Development Team
-           This program is free software under the GNU General Public License
-           (>=v2). Read the file COPYING that comes with GRASS for details.
-
-:authors: Nikos Alexandris
-"""
-
-"""Libraries"""
-
-from grass.gunittest.case import TestCase
-from grass.gunittest.gmodules import SimpleModule
-import grass.script as g
-
-import restimap as restimap
-import tempfile
-
-"""Globals"""
-
-# PRECISION=[0.1, 0.01, 0.001]
-
-CORINE_LAND_COVER_CLASSES="""
-north:                    1
-south:                    0
-east:                     45
-west:                     0
-rows:                     1
-cols:                     45
-null:                     -1
-type: int
-multiplier: 1
-
-1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45"""
-# print "CORINE land cover classes:", CORINE_LAND_COVER_CLASSES
-corine_input='corine_input'
-
-CORINE_LAND_COVER_CLASSES_MINIMUM=1
-# print "Minimum category integer for CORINE land cover class :", CORINE_LAND_COVER_CLASSES_MINIMUM
-
-CORINE_LAND_COVER_CLASSES_MAXIMUM=48
-# print "Maximum category integer for CORINE land cover class:", CORINE_LAND_COVER_CLASSES_MAXIMUM
-
-CORINE_LAND_COVER_CLASSES_SUITABILITY_SCORES="""
-north:                    1
-south:                    0
-east:                     45
-west:                     0
-rows:                     1
-cols:                     45
-null:                     -1
-type: dcell
-multiplier: 1
-
-0 0.1 0 0 0 0 0 0 0 1 0.1 0.3 0.3 0.4 0.5 0.5 0.5 0.6 0.3 0.3 0.6 0.6 1 0.8 1 0.8 0.8 0.8 0.8 1 0.8 0.7 0 0.8 1 0.8 1 0.8 1 1 1 1 0.8 1 0.3"""
-# print "Suitability scores map:", CORINE_LAND_COVER_CLASSES_SUITABILITY_SCORES
-
-suitability_map='suitability_map'
-corine_recoded='corine_recoded'
-
-"""Test Case Class"""
-
-# def recode_map(raster, rules, colors, output):
-
-class TestCORINE(TestCase):
-
-    gisenv = SimpleModule('g.gisenv', get='MAPSET')
-    TestCase.runModule(gisenv, expecting_stdout=True)
-    print "Mapset: ", gisenv.outputs.stdout.strip()
-
-    # TODO: replace by unified handing of maps
-    to_remove = []
-
-    # glist = SimpleModule('g.list',
-    #                 type='raster',
-    #                 mapset='.',
-    #                 flags='p')
-
-    # TestCase.runModule(glist, expecting_stdout=True)
-    # print glist.outputs.stdout.strip()
-
-    filename = tempfile.TemporaryFile(mode='w+b',
-                                      suffix='',
-                                      prefix='tmp',
-                                      dir=None)
-    print "Filename:", filename
-
-    corine_input = 'corine_land_cover_classes.ascii'
-    suitability_map = 'suitability_scores_for_corine.ascii'
-    suitability_rules = 'suitability_of_corine_land_cover_classes.scores'
-
-    @classmethod
-    def setUpClass(cls):
-        """
-        """
-
-        try:
-            print "Trying to open file and write content"
-            ascii_file = open(cls.filename, "w")
-            ascii_file.write("Purchase Amount")
-        except IOError as e:
-            print "IOError:", e
-            return
-        finally:
-            print "Success -- Closing file"
-            ascii_file.close()
-
-        # use a temporary region
-        print "Use a temporary region"
-        cls.use_temp_region()
-
-        # # create input raster maps
-        print "Import CORINE land cover class nomenclature"
-        print "Input map name:", cls.corine_input
-        cls.runModule('r.in.ascii',
-                      input=cls.corine_input,
-                      output=corine_input,
-                      overwrite=True,
-                      verbose=True)
-
-        print "Import suitability scores map"
-        cls.runModule('r.in.ascii',
-                      input=cls.suitability_map,
-                      output=suitability_map,
-                      overwrite=True,
-                      verbose=True)
-
-        # append them in list "to_remove"
-        print "Add imported maps in list to remove"
-        cls.to_remove.append(corine_input)
-        cls.to_remove.append(suitability_map)
-
-        # set region to map(s)
-        cls.runModule('g.region', raster=corine_input, flags='p')
-
-    def test_recode_map(self):
-        """
-        """
-        restimap.recode_map(raster=corine_input,
-                rules=cls.suitability_rules,
-                output=corine_recoded)
-
-    @classmethod
-    def tearDownClass(cls):
-
-        """
-        Remove temporary region and test raster maps
-        """
-        cls.del_temp_region()
-        print
-        print "Removing test raster maps:\n"
-        print ', '.join(cls.to_remove)
-        if cls.to_remove:
-            cls.runModule('g.remove', flags='f', type='raster',
-                name=','.join(cls.to_remove), verbose=True)
-
-    def tearDown(self):
-        """
-        ...
-        """
-        pass
-
-    def test_corine_range(self):
-        """
-        Test for range of Hue, Intensity and Saturation
-        """
-        self.assertRasterMinMax(map=corine,
-                refmin=CORINE_MINIMUM,
-                refmax=CORINE_MAXIMUM,
-                msg=None)
-
-    def test_difference(self):
-        """
-        Test for no or minimal differences between
-        """
-        # assertRastersNoDifference(actual, reference, precision, statistics=None, msg=None)
-
-        self.assertRastersNoDifference(actual=corine_recoded,
-                reference=corine_input,
-                precision=precision)
-
-        info = SimpleModule('r.info', flags='r', map=corine_input)
-        TestCase.runModule(info, expecting_stdout=True)
-        corine_input_line = [corine_input] + info.outputs.stdout.splitlines()
-
-        info = SimpleModule('r.info', flags='r', map=corine_recoded)
-        TestCase.runModule(info, expecting_stdout=True)
-        corine_recoded_line = [corine_recoded] + info.outputs.stdout.splitlines()
-
-        # inform
-
-        for row in corine_input_line, corine_recoded_line:
-            print("{: >20} {: >25} {: >20}".format(*row))
-
-        print
-
-if __name__ == '__main__':
-    from grass.gunittest.main import test
-    test()

Added: grass-addons/grass7/raster/r.estimap.recreation/test_integration.sh
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/test_integration.sh	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/test_integration.sh	2019-03-01 11:47:49 UTC (rev 74143)
@@ -0,0 +1,97 @@
+#!/bin/bash -
+#===============================================================================
+#
+#          FILE: test_integration_x.sh
+#
+#         USAGE: ./test_integration_x.sh
+#
+#   DESCRIPTION:
+#
+#       OPTIONS: ---
+#  REQUIREMENTS: ---
+#          BUGS: ---
+#         NOTES: First implementation by pmav99
+#        AUTHOR: Nikos Alexandris (), nik at nikosalexandris.net
+#  ORGANIZATION:
+#       CREATED: 02/08/2019 12:48
+#      REVISION:  ---
+#===============================================================================
+
+# set -o nounset                              # Treat unset variables as an error
+set -xeuo pipefail
+
+# Map and file names of interest
+map_names=( demand flow spectrum unmet_demand potential opportunity flow_corine_land_cover_2006 maes_ecosystem_types maes_ecosystem_types_flow )
+csv_names=( supply.csv use.csv )
+
+# ------------------- FIXME: check if unnecessarily querying any input maps ---
+# # Query existing maps
+# for name in "${map_names[@]}" ;do
+#   mv -f master."${name}" /tmp
+#   r.univar "${name}" > master."${name}"
+# done
+
+# csv_names=( supply.csv use.csv )
+# for name in "${csv_names[@]}" ;do
+#   echo "${name}"
+#   mv -f master."${name}" /tmp
+#   cp "${name}" master."${name}"
+# done
+# -----------------------------------------------------------------------------
+
+# First grassy things first
+g.region raster=area_of_interest -p
+
+# Clean output maps from previous run(s)
+g.remove \
+    -f -b \
+    type=raster \
+    name=demand,flow,maes_ecosystem_types,maes_ecosystem_types_flow,flow_corine_land_cover_2006,opportunity,potential,potential_1,potential_2,potential_3,potential_4,recreation_opportunity,spectrum,highest_recreation_spectrum,demand,unmet,unmet_demand,mobility,crossmap
+
+# Re-run the module
+$GRASS_PYTHON r.estimap.recreation/r.estimap.recreation.py \
+  --verbose \
+  --overwrite \
+  mask=area_of_interest \
+  land=land_suitability \
+  water=water_resources,bathing_water_quality \
+  natural=protected_areas \
+  infrastructure=distance_to_infrastructure \
+  potential=potential \
+  opportunity=opportunity \
+  spectrum=spectrum \
+  demand=demand \
+  unmet=unmet_demand \
+  flow=flow \
+  population=population_2015 \
+  base=local_administrative_units \
+  landcover=corine_land_cover_2006 \
+  aggregation=regions \
+  land_classes=corine_accounting_to_maes_land_classes.rules \
+  supply=supply \
+  use=use
+
+# compare 'master' (old) with 'current' (new) maps
+for name in "${map_names[@]}" ;do
+    # remove 'old'
+    touch current."${name}"
+    mv -f current."${name}" /tmp
+    # create 'new'
+    echo "${name}"
+    r.univar "${name}" > current."${name}"
+    # compare
+    diff master."${name}" current."${name}"
+    echo
+done
+
+for name in "${csv_names[@]}" ;do
+    # remove 'old'
+    touch current."${name}"
+    mv -f current."${name}" /tmp
+    # create 'new' from the module's last run
+    echo "${name}"
+    mv "${name}" current."${name}"
+    # compare
+    diff master."${name}" current."${name}"
+    echo
+done


Property changes on: grass-addons/grass7/raster/r.estimap.recreation/test_integration.sh
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Deleted: grass-addons/grass7/raster/r.estimap.recreation/unmet_demand.png
===================================================================
(Binary files differ)

Deleted: grass-addons/grass7/raster/r.estimap.recreation/water_resources.png
===================================================================
(Binary files differ)



More information about the grass-commit mailing list