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

svn_grass at osgeo.org svn_grass at osgeo.org
Thu Jan 10 02:08:16 PST 2019


Author: nikosa
Date: 2019-01-10 02:08:15 -0800 (Thu, 10 Jan 2019)
New Revision: 73922

Added:
   grass-addons/grass7/raster/r.estimap.recreation/
   grass-addons/grass7/raster/r.estimap.recreation/C_2018_8970_F1_COMMISSION_DECISION_EN_V3_P1_1006093.PDF
   grass-addons/grass7/raster/r.estimap.recreation/LICENSE
   grass-addons/grass7/raster/r.estimap.recreation/Makefile
   grass-addons/grass7/raster/r.estimap.recreation/README.md
   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.html
   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
Log:
Add r.estimap.recreation

Added: grass-addons/grass7/raster/r.estimap.recreation/C_2018_8970_F1_COMMISSION_DECISION_EN_V3_P1_1006093.PDF
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/C_2018_8970_F1_COMMISSION_DECISION_EN_V3_P1_1006093.PDF
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/C_2018_8970_F1_COMMISSION_DECISION_EN_V3_P1_1006093.PDF	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/C_2018_8970_F1_COMMISSION_DECISION_EN_V3_P1_1006093.PDF	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/C_2018_8970_F1_COMMISSION_DECISION_EN_V3_P1_1006093.PDF
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Added: grass-addons/grass7/raster/r.estimap.recreation/LICENSE
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/LICENSE	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/LICENSE	2019-01-10 10:08:15 UTC (rev 73922)
@@ -0,0 +1,274 @@
+European Union Public Licence
+V. 1.2
+
+EUPL © the European Union 2007, 2016
+
+This European Union Public Licence (the ‘EUPL’) applies to the Work (as
+defined below) which is provided under the terms of this Licence. Any use of
+the Work, other than as authorised under this Licence is prohibited (to the
+extent such use is covered by a right of the copyright holder of the Work).
+
+The Work is provided under the terms of this Licence when the Licensor (as
+defined below) has placed the following notice immediately following the
+copyright notice for the Work: “Licensed under the EUPL”, or has expressed by
+any other means his willingness to license under the EUPL.
+
+1. Definitions
+
+In this Licence, the following terms have the following meaning:
+— ‘The Licence’: this Licence.
+— ‘The Original Work’: the work or software distributed or communicated by the
+  ‘Licensor under this Licence, available as Source Code and also as
+  ‘Executable Code as the case may be.
+— ‘Derivative Works’: the works or software that could be created by the
+  ‘Licensee, based upon the Original Work or modifications thereof. This
+  ‘Licence does not define the extent of modification or dependence on the
+  ‘Original Work required in order to classify a work as a Derivative Work;
+  ‘this extent is determined by copyright law applicable in the country
+  ‘mentioned in Article 15.
+— ‘The Work’: the Original Work or its Derivative Works.
+— ‘The Source Code’: the human-readable form of the Work which is the most
+  convenient for people to study and modify.
+
+— ‘The Executable Code’: any code which has generally been compiled and which
+  is meant to be interpreted by a computer as a program.
+— ‘The Licensor’: the natural or legal person that distributes or communicates
+  the Work under the Licence.
+— ‘Contributor(s)’: any natural or legal person who modifies the Work under
+  the Licence, or otherwise contributes to the creation of a Derivative Work.
+— ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of
+  the Work under the terms of the Licence.
+— ‘Distribution’ or ‘Communication’: any act of selling, giving, lending,
+  renting, distributing, communicating, transmitting, or otherwise making
+  available, online or offline, copies of the Work or providing access to its
+  essential functionalities at the disposal of any other natural or legal
+  person.
+
+2. Scope of the rights granted by the Licence
+
+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
+sublicensable licence to do the following, for the duration of copyright
+vested in the Original Work:
+
+— use the Work in any circumstance and for all usage,
+— reproduce the Work,
+— modify the Work, and make Derivative Works based upon the Work,
+— communicate to the public, including the right to make available or display
+  the Work or copies thereof to the public and perform publicly, as the case
+  may be, the Work,
+— distribute the Work or copies thereof,
+— lend and rent the Work or copies thereof,
+— sublicense rights in the Work or copies thereof.
+
+Those rights can be exercised on any media, supports and formats, whether now
+known or later invented, as far as the applicable law permits so.
+
+In the countries where moral rights apply, the Licensor waives his right to
+exercise his moral right to the extent allowed by law in order to make
+effective the licence of the economic rights here above listed.
+
+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights
+to any patents held by the Licensor, to the extent necessary to make use of
+the rights granted on the Work under this Licence.
+
+3. Communication of the Source Code
+
+The Licensor may provide the Work either in its Source Code form, or as
+Executable Code. If the Work is provided as Executable Code, the Licensor
+provides in addition a machine-readable copy of the Source Code of the Work
+along with each copy of the Work that the Licensor distributes or indicates,
+in a notice following the copyright notice attached to the Work, a repository
+where the Source Code is easily and freely accessible for as long as the
+Licensor continues to distribute or communicate the Work.
+
+4. Limitations on copyright
+
+Nothing in this Licence is intended to deprive the Licensee of the benefits
+from any exception or limitation to the exclusive rights of the rights owners
+in the Work, of the exhaustion of those rights or of other applicable
+limitations thereto.
+
+5. Obligations of the Licensee
+
+The grant of the rights mentioned above is subject to some restrictions and
+obligations imposed on the Licensee. Those obligations are the following:
+
+Attribution right: The Licensee shall keep intact all copyright, patent or
+trademarks notices and all notices that refer to the Licence and to the
+disclaimer of warranties. The Licensee must include a copy of such notices and
+a copy of the Licence with every copy of the Work he/she distributes or
+communicates. The Licensee must cause any Derivative Work to carry prominent
+notices stating that the Work has been modified and the date of modification.
+
+Copyleft clause: If the Licensee distributes or communicates copies of the
+Original Works or Derivative Works, this Distribution or Communication will be
+done under the terms of this Licence or of a later version of this Licence
+unless the Original Work is expressly distributed only under this version of
+the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee
+(becoming Licensor) cannot offer or impose any additional terms or conditions
+on the Work or Derivative Work that alter or restrict the terms of the
+Licence.
+
+Compatibility clause: If the Licensee Distributes or Communicates Derivative
+Works or copies thereof based upon both the Work and another work licensed
+under a Compatible Licence, this Distribution or Communication can be done
+under the terms of this Compatible Licence. For the sake of this clause,
+‘Compatible Licence’ refers to the licences listed in the appendix attached to
+this Licence. Should the Licensee's obligations under the Compatible Licence
+conflict with his/her obligations under this Licence, the obligations of the
+Compatible Licence shall prevail.
+
+Provision of Source Code: When distributing or communicating copies of the
+Work, the Licensee will provide a machine-readable copy of the Source Code or
+indicate a repository where this Source will be easily and freely available
+for as long as the Licensee continues to distribute or communicate the Work.
+
+Legal Protection: This Licence does not grant permission to use the trade
+names, trademarks, service marks, or names of the Licensor, except as required
+for reasonable and customary use in describing the origin of the Work and
+reproducing the content of the copyright notice.
+
+6. Chain of Authorship
+
+The original Licensor warrants that the copyright in the Original Work granted
+hereunder is owned by him/her or licensed to him/her and that he/she has the
+power and authority to grant the Licence.
+
+Each Contributor warrants that the copyright in the modifications he/she
+brings to the Work are owned by him/her or licensed to him/her and that he/she
+has the power and authority to grant the Licence.
+
+Each time You accept the Licence, the original Licensor and subsequent
+Contributors grant You a licence to their contributions to the Work, under the
+terms of this Licence.
+
+7. Disclaimer of Warranty
+
+The Work is a work in progress, which is continuously improved by numerous
+Contributors. It is not a finished work and may therefore contain defects or
+‘bugs’ inherent to this type of development.
+
+For the above reason, the Work is provided under the Licence on an ‘as is’
+basis and without warranties of any kind concerning the Work, including
+without limitation merchantability, fitness for a particular purpose, absence
+of defects or errors, accuracy, non-infringement of intellectual property
+rights other than copyright as stated in Article 6 of this Licence.
+
+This disclaimer of warranty is an essential part of the Licence and a
+condition for the grant of any rights to the Work.
+
+8. Disclaimer of Liability
+
+Except in the cases of wilful misconduct or damages directly caused to natural
+persons, the Licensor will in no event be liable for any direct or indirect,
+material or moral, damages of any kind, arising out of the Licence or of the
+use of the Work, including without limitation, damages for loss of goodwill,
+work stoppage, computer failure or malfunction, loss of data or any commercial
+damage, even if the Licensor has been advised of the possibility of such
+damage. However, the Licensor will be liable under statutory product liability
+laws as far such laws apply to the Work.
+
+9. Additional agreements
+
+While distributing the Work, You may choose to conclude an additional
+agreement, defining obligations or services consistent with this Licence.
+However, if accepting obligations, You may act only on your own behalf and on
+your sole responsibility, not on behalf of the original Licensor or any other
+Contributor, and only if You agree to indemnify, defend, and hold each
+Contributor harmless for any liability incurred by, or claims asserted against
+such Contributor by the fact You have accepted any warranty or additional
+liability.
+
+10. Acceptance of the Licence
+
+The provisions of this Licence can be accepted by clicking on an icon ‘I
+agree’ placed under the bottom of a window displaying the text of this Licence
+or by affirming consent in any other similar way, in accordance with the rules
+of applicable law. Clicking on that icon indicates your clear and irrevocable
+acceptance of this Licence and all of its terms and conditions.
+
+Similarly, you irrevocably accept this Licence and all of its terms and
+conditions by exercising any rights granted to You by Article 2 of this
+Licence, such as the use of the Work, the creation by You of a Derivative Work
+or the Distribution or Communication by You of the Work or copies thereof.
+
+11. Information to the public
+
+In case of any Distribution or Communication of the Work by means of
+electronic communication by You (for example, by offering to download the Work
+from a remote location) the distribution channel or media (for example, a
+website) must at least provide to the public the information requested by the
+applicable law regarding the Licensor, the Licence and the way it may be
+accessible, concluded, stored and reproduced by the Licensee.
+
+12. Termination of the Licence
+
+The Licence and the rights granted hereunder will terminate automatically upon
+any breach by the Licensee of the terms of the Licence. Such a termination
+will not terminate the licences of any person who has received the Work from
+the Licensee under the Licence, provided such persons remain in full
+compliance with the Licence.
+
+13. Miscellaneous
+
+Without prejudice of Article 9 above, the Licence represents the complete
+agreement between the Parties as to the Work.
+
+If any provision of the Licence is invalid or unenforceable under applicable
+law, this will not affect the validity or enforceability of the Licence as a
+whole. Such provision will be construed or reformed so as necessary to make it
+valid and enforceable.
+
+The European Commission may publish other linguistic versions or new versions
+of this Licence or updated versions of the Appendix, so far this is required
+and reasonable, without reducing the scope of the rights granted by the
+Licence. New versions of the Licence will be published with a unique version
+number.
+
+All linguistic versions of this Licence, approved by the European Commission,
+have identical value. Parties can take advantage of the linguistic version of
+their choice.
+
+14. Jurisdiction
+
+Without prejudice to specific agreement between parties,
+— any litigation resulting from the interpretation of this License, arising
+  between the European Union institutions, bodies, offices or agencies, as a
+  Licensor, and any Licensee, will be subject to the jurisdiction of the Court
+  of Justice of the European Union, as laid down in article 272 of the Treaty
+  on the Functioning of the European Union,
+— any litigation arising between other parties and resulting from the
+  interpretation of this License, will be subject to the exclusive
+  jurisdiction of the competent court where the Licensor resides or conducts
+  its primary business.
+
+15. Applicable Law
+
+Without prejudice to specific agreement between parties,
+— this Licence shall be governed by the law of the European Union Member State
+  where the Licensor has his seat, resides or has his registered office,
+— this licence shall be governed by Belgian law if the Licensor has no seat,
+  residence or registered office inside a European Union Member State.
+
+Appendix
+
+‘Compatible Licences’ according to Article 5 EUPL are:
+— GNU General Public License (GPL) v. 2, v. 3
+— GNU Affero General Public License (AGPL) v. 3
+— Open Software License (OSL) v. 2.1, v. 3.0
+— Eclipse Public License (EPL) v. 1.0
+— CeCILL v. 2.0, v. 2.1
+— Mozilla Public Licence (MPL) v. 2
+— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
+— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
+  works other than software
+— European Union Public Licence (EUPL) v. 1.1, v. 1.2
+— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or
+  Strong Reciprocity (LiLiQ-R+)
+
+— The European Commission may update this Appendix to later versions of the
+  above licences without producing a new version of the EUPL, as long as they
+  provide the rights granted in Article 2 of this Licence and protect the
+  covered Source Code from exclusive appropriation.
+— All other changes or additions to this Appendix require the production of a
+  new EUPL version.
\ No newline at end of file

Added: grass-addons/grass7/raster/r.estimap.recreation/Makefile
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/Makefile	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/Makefile	2019-01-10 10:08:15 UTC (rev 73922)
@@ -0,0 +1,8 @@
+MODULE_TOPDIR = ../..
+
+PGM = r.estimap.recreation
+
+include $(MODULE_TOPDIR)/include/Make/Script.make
+include $(MODULE_TOPDIR)/include/Make/Python.make
+
+default: script

Added: grass-addons/grass7/raster/r.estimap.recreation/README.md
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/README.md	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/README.md	2019-01-10 10:08:15 UTC (rev 73922)
@@ -0,0 +1,549 @@
+Description
+-----------
+
+*r.estimap* is an implementation of the ESTIMAP recreation algorithm to
+support mapping and modelling of ecosystem services (Zulian, 2014).
+
+Examples
+--------
+
+For the sake of demonstrating the usage of the module, we use the
+following "component" maps
+
+-   area_of_interest
+-   land_suitability
+-   water_resources
+-   protected_areas
+
+(available to download at: ...) to derive a recreation *potential* map.
+
+<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)
+
+</div>
+
+Before anything, we need to define the extent of interest using
+
+<div class="code">
+
+    g.region  raster=area_of_interest
+
+</div>
+
+### Using pre-processed maps
+
+The first four input options of the module are designed to receive
+pre-processed input maps that classify as either `land`, `natural`,
+`water` and `infrastructure` resources.
+
+#### Potential
+
+To compute a *potential* output map, the simplest possible command call
+requires the user to define the input map option `land` and define a
+name for the output map option `potential`. Using a pre-processed map
+that depicts the suitability of different land types to support for
+recreation (here the map named
+`land_suitability) the command to execute is: `
+
+<div class="code">
+
+    r.estimap  land=land_suitability  potential=potential
+
+</div>
+
+![Example of a recreation potential output map](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
+GRASS GIS.
+
+To exclude certain areas from the computations, we may use a raster map
+as a mask and feed it to the input map option `mask`:
+
+<div class="code">
+
+    r.estimap  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)
+
+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
+assigned the NULL value). Successively, these areas will be empty in the
+output map `potential_1`. Actually, the same effect can be achieved by
+using GRASS GIS' native mask creation module `r.mask` and feed it with a
+raster map of interest. The result will be a raster map named **MASK**
+whose presence acts as a filter. In the following examples, it becomes
+obvious that if a single input map features such **No Data** areas, they
+will be propagated in the output map.
+
+Nonetheless, it is good practice to use a `MASK` when one needs to
+ensure the exclusion of undesired areas from any computations. Also,
+note the `--o` flag: it is required to overwrite the already existing
+map named `potential_1`.
+
+Next, we add a water component, a map named `water_resources`, modify
+the output map name to `potential_2` and execute again, without a mask:
+
+<div class="code">
+
+    r.estimap  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)
+
+At this point it becomes clear that all `NULL` cells present in the
+"water" map, are proagated in the output map `potential_2`.
+
+Following, we provide a map of protected areas named `protected_areas`,
+modify the output map name to `potential_3` and repeat the command
+execution:
+
+<div class="code">
+
+    r.estimap  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)
+
+While the `land` option accepts only one map as an input, both the
+`water` and the `natural` options accept multiple maps as inputs. In
+example, we add a second map named `bathing_water_quality` to the water
+component and modify the output map name to `potential_4`:
+
+<div class="code">
+
+    r.estimap  land=land_suitability  water=water_resources,bathing_water_quality  natural=protected_areas  potential=potential_4
+
+</div>
+
+In general, arbitrary number of maps, separated by comma, may be added
+to options that accept multiple inputs.
+
+![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)
+
+This example, features also a title and a legend, so as to make sense of
+the map.
+
+<div class="code">
+
+    d.rast  potential_4
+    d.legend  -c  -b  potential_4  at=0,15,0,1  border_color=white
+    d.text  text="Potential"  bgcolor=white
+
+</div>
+
+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 `potential_1`, `potential_2`, `potential_3` and
+`potential_4` range within \[0,3\]. Yet, they differ in the distribution
+of values due to the different set of input maps.
+
+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
+*raw*, unprocessed maps, read section **Using unprocessed maps**.
+
+#### Spectrum
+
+To derive a map with the recreation (opportunity) `spectrum`, we need in
+addition an `infrastructure` component. In this example a map that
+scores distance to infrastructure (such as the road network) named
+`distance_to_infrastructure` is defined as an input:
+
+![Example of an input map showing distances to
+infrastructure](distance_to_infrastructure.png)
+
+Naturally, we need to define the output map option `spectrum` too:
+
+<div class="code">
+
+    r.estimap  \
+      land=land_suitability \
+      water=water_resources,bathing_water_quality \
+      natural=protected_areas \
+      infrastructure=distance_to_infrastructure
+      spectrum=spectrum  \
+
+</div>
+
+or, the same command in a copy-paste friendly way:
+
+<div class="code">
+
+    r.estimap  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)
+
+Missing to define the `infrastructure` map, the command will abort and
+inform about.
+
+The image above, was produced via the following native GRASS GIS
+commands
+
+<div class="code">
+
+    d.rast  spectrum
+    d.legend  -c  -b  spectrum  at=0,30,0,1  border_color=white
+    d.text  text="Spectrum"  bgcolor=white
+
+</div>
+
+##### Opportunity
+
+The `opportunity` map is actually an intermediate step of the algorithm.
+The option to output this map `opportunity` is meant for expert users
+who want to explore the fundamentals of the processing steps. Hence, it
+requires to define the output option `spectrum` map as well. Building
+upon the previous command, we add the `opportunity` output option:
+
+<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
+
+</div>
+
+or, the same command in a copy-paste friendly way:
+
+<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
+
+</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)
+
+#### More input maps
+
+To derive the outputs met `demand` distributiom, `unmet` demand
+distributiom and the actual `flow`, additional requirements are a
+`population` map and one of boundaries, as an input to the option
+`base`, 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
+`demand`, `unmet` and `flow` to the spatial resolution of the
+`population` input map.
+
+Population
+
+![Fragment of a population map (GHSL, 2015)](population_2015.png)
+
+In this example, the population map named `population_2015` is of
+1000m\^2.
+
+Local administrative units
+
+![Fragment of a local administrative units input
+map](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
+map.
+
+#### Demand
+
+<div class="code">
+
+    r.estimap --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
+
+</div>
+
+![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)
+
+#### Unmet Demand
+
+<div class="code">
+
+    r.estimap --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
+
+</div>
+
+![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)
+
+#### Mobility
+
+The *mobility* bases upon the same function used to quantify the
+attractiveness of locations for their recreational value. It includes an
+extra *score* term.
+
+The computation involves a *distance* 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">
+
+  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
+
+</div>
+
+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
+
+<div class="code">
+
+    r.estimap --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
+
+</div>
+
+![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)
+
+#### All in one call
+
+Of course it is possible to derive all output maps with one call:
+
+<div class="code">
+
+    r.estimap --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'
+
+</div>
+
+Note the use of the `timestamp` parameter! This concerns the `spectrum`
+map. If plans include working with GRASS GIS' temporal framework on
+time-series, this will be useful.
+
+### Using unprocessed input maps
+
+The module offers a pre-processing functionality for all of the
+following input components:
+
+-   landuse
+-   suitability\_scores
+
+<!-- -->
+
+-   landcover
+-   land\_classes
+
+<!-- -->
+
+-   lakes
+-   lakes\_coefficients
+-   default is set to: euclidean,1,30,0.008,1
+
+<!-- -->
+
+-   coastline
+-   coastline\_coefficients
+-   default is set to: euclidean,1,30,0.008,1
+-   coast\_geomorphology
+
+<!-- -->
+
+-   bathing\_water
+-   bathing\_coefficients
+-   default is set to: euclidean,1,5,0.01101
+
+<!-- -->
+
+-   protected
+-   protected\_scores
+-   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
+
+<!-- -->
+
+-   anthropic
+-   anthropic\_distances
+-   0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:\*:5
+
+<!-- -->
+
+-   roads
+-   roads\_distances
+-   0:500:1,500.000001:1000:2,1000.000001:5000:3,5000.000001:10000:4,10000.00001:\*:5
+
+A first look on how this works, is to experiment with the `landuse` and
+`suitability_scores` input options.
+
+Let's return to the first example, and use a fragment from the
+unprocessed CORINE land data set, instead of the `land_suitability` map.
+This requires a set of "score" rules, that correspond to the CORINE
+nomenclature, to translate the land cover types into recreation
+potential.
+
+<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)
+
+</div>
+
+In this case, the rules are a simple ASCII file (for example named
+`corine_suitability.scores` that contains the following
+
+<div class="code">
+
+    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
+
+</div>
+
+This file is provided in the `suitability_scores` option:
+
+<div class="code">
+
+    r.estimap  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)
+
+The same can be achieved with a long one-line string too:
+
+<div class="code">
+
+    r.estimap \
+      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
+
+</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 `suitability_scores` option:
+
+<div class="code">
+
+    r.estimap  landuse=corine_land_cover_2006  suitability_scores=corine_suitability.scores  potential=potential_1 --o
+
+</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
+
+-   provide a new "rules" file with the desired set of scoring rules
+-   provide a string to the `suitability_scores` option
+
+Author
+------
+
+Nikos Alexandris
+
+Licence
+-------
+
+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.

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

Index: grass-addons/grass7/raster/r.estimap.recreation/area_of_interest.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/area_of_interest.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/area_of_interest.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/bathing_water_quality.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/bathing_water_quality.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/bathing_water_quality.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/bathing_water_quality.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/corine_land_cover_2006.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/corine_land_cover_2006_suitability.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006_suitability.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006_suitability.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_2006_suitability.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/corine_land_cover_legend.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_legend.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_legend.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/corine_land_cover_legend.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/demand.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/demand.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/demand.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/demand.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/distance_to_infrastructure.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/distance_to_infrastructure.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/distance_to_infrastructure.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/distance_to_infrastructure.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/land_suitability.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/land_suitability.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/land_suitability.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/land_suitability.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/local_administrative_units.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/local_administrative_units.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/local_administrative_units.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/local_administrative_units.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/mobility.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/mobility.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/mobility.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/mobility.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/opportunity.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/opportunity.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/opportunity.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/opportunity.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/population_2015.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/population_2015.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/population_2015.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/population_2015.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_1.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_1.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_1.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_1.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_2.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_2.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_2.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_2.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_3.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_3.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_3.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_3.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_4.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_4.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_4.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_4.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_based_on_corine_land_cover_2006.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_based_on_corine_land_cover_2006.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_based_on_corine_land_cover_2006.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_based_on_corine_land_cover_2006.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_corine.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_corine.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_corine.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_corine.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/potential_masked.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/potential_masked.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/potential_masked.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/potential_masked.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/protected_areas.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/protected_areas.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/protected_areas.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/protected_areas.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/r.estimap.recreation.html
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.html	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.html	2019-01-10 10:08:15 UTC (rev 73922)
@@ -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="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">
+</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="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="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="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="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="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="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="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="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="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="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="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="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="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="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">
+  </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="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.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/r.estimap.recreation.py	2019-01-10 10:08:15 UTC (rev 73922)
@@ -0,0 +1,4108 @@
+#!/usr/bin/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: d
+#%  description: Draw maps in terminology (developper's version)
+#%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())


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

Index: grass-addons/grass7/raster/r.estimap.recreation/spectrum.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/spectrum.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/spectrum.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/spectrum_high_provision_near.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/spectrum_high_provision_near.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/spectrum_high_provision_near.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/spectrum_high_provision_near.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/test.r.estimap.recreation.py
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/test.r.estimap.recreation.py	                        (rev 0)
+++ grass-addons/grass7/raster/r.estimap.recreation/test.r.estimap.recreation.py	2019-01-10 10:08:15 UTC (rev 73922)
@@ -0,0 +1,205 @@
+#!/usr/bin/python\<nl>\
+# -*- 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()


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

Index: grass-addons/grass7/raster/r.estimap.recreation/unmet_demand.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/unmet_demand.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/unmet_demand.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/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/water_resources.png
===================================================================
(Binary files differ)

Index: grass-addons/grass7/raster/r.estimap.recreation/water_resources.png
===================================================================
--- grass-addons/grass7/raster/r.estimap.recreation/water_resources.png	2019-01-07 20:57:59 UTC (rev 73921)
+++ grass-addons/grass7/raster/r.estimap.recreation/water_resources.png	2019-01-10 10:08:15 UTC (rev 73922)

Property changes on: grass-addons/grass7/raster/r.estimap.recreation/water_resources.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+image/png
\ No newline at end of property


More information about the grass-commit mailing list