flsgen
is a neutral landscape generator that allows users to set targets on landscape indices. It first relies on Choco-solver to identify landscape structure satisfying user targets, then uses a stochastic algorithm to produce landscape rasters.
Java 8+ must be installed in your system to run flsgen. For the command-line interface, only a JRE is necessary, whereas the Java API needs a full JDK and Maven. Download and installation instructions for Java are available here: https://www.oracle.com/java/technologies/javase-downloads.html, or here: https://openjdk.java.net/install/. Download and installation instructions for Maven are available here: https://maven.apache.org/.
The most straightforward way to use fslgen is the command-line interface (CLI). Once Java is installed on your system, the only thing to do is to download the CLI jar file here. To test that everything is working properly, from a terminal navigate to the folder where you downloaded the jar file and use the following command:
java -jar flsgen-1.2.0.jar
You should get the following output:
Usage: flsgen [-hV] [COMMAND]
A fragmented neutral landscape generator
-h, --help Show this help message and exit.
-V, --version Print version information and exit.
Commands:
structure Generate a landscape structure satisfying a set of targets.
generate Generate landscapes from given structures. To produce more
realistic landscape, the algorithm relies on a terrain either
given as input or automatically generated as a fractal terrain.
terrain Generate a fractal terrain using the Diamond-Square algorithm.
To avoid typing java -jar flsgen-1.2.0.jar
each time you need to use flsgen, you can create the following alias: alias flsgen='java -jar flsgen-1.2.0.jar'
in your .bashrc
or .profile
file (in Linux). The following examples assume that this step has been done. If you did not create the alias, simply replace flsgen
by java -jar flsgen-1.2.0.jar
.
To use the Java API, you can either download flsgen jar file here and add it to your classpath, or clone this Github repository and install flsgen as a local Maven dependency using the following command:
mvn clean install -DskipTests
Using the CLI, you can easily generate a fractal terrain raster using the flsgen terrain
CLI command:
Usage: flsgen terrain [-hV] -H=<nbRows> [-r=<resolution>]
[-R=<roughnessFactor>] [-s=<srs>] [-t=<template>]
-W=<nbCols> [-x=<x>] [-y=<y>] <output>
Generate a fractal terrain using the Diamond-Square algorithm
<output> Path to the raster to generate as output
-h, --help Show this help message and exit.
-H, --height=<nbRows> Height (in pixels) of the terrain to generate
-r, --resolution=<resolution>
Spatial resolution of the output raster (in CRS unit,
default 0.0001)
-R, --roughness=<roughnessFactor>
Roughness parameter (also called H), between 0 and 1.
Lower values produce rougher terrain (0.5 by
default)
-s, -srs, --spatial-reference-system=<srs>
Spatial reference system of the output raster
(default: EPSG:4326)
-t, -ot, --output-template=<template>
Raster template to use for output raster metadata
-V, --version Print version information and exit.
-W, --width=<nbCols> Width (in pixels) of the terrain to generate
-x=<x> Top left x coordinate of the output raster (default 0)
-y=<y> Top left y coordinate of the output raster (default 0)
For example, if you want to generate a 200x200 fractal terrain with a roughness factor of 0.4 and default parameters, use the following command:
flsgen terrain -H 200 -W 200 -R 0.4 terrain.tif
You will get a fractal tif raster, e.g. :
To achieve the same result with the Java API:
import org.flsgen.RegularSquareGrid;
import org.flsgen.Terrain;
import org.flsgen.RasterUtils;
import org.opengis.referencing.FactoryException;
import java.io.IOException;
public class FractalTerrainTest {
public static void main(String[] args) throws IOException, FactoryException {
RegularSquareGrid grid = new RegularSquareGrid(200, 200);
Terrain t = new Terrain(grid);
t.generateDiamondSquare(0.4);
RasterUtils.exportDoubleRaster(
t.getData(), t.getGrid(),
0, 0, 0.01, "EPSG:4326", "terrain.tif"
);
}
}
Say that we want to generate a landscape structure for a 200x200 landscape containing three landscape classes (plus a background class), with the following user targets:
Class | NP | AREA | CA | MESH | PLAND |
---|---|---|---|---|---|
0 | [1, 10] | [300, 4000] | [1000, 5000] | 225 | - |
1 | [2, 8] | [200, 4000] | - | - | 40% |
2 | [5, 7] | [800, 1200] | - | - | - |
The first step is to create a JSON file (e.g. target.json
) describing these targets:
{
"nbRows" : 200,
"nbCols" : 200,
"classes" : [
{
"name" : "Class A",
"NP" : [1, 10],
"AREA" : [300, 4000],
"CA" : [1000, 5000],
"MESH" : [225, 225]
},
{
"name" : "Class B",
"NP" : [2, 8],
"AREA" : [200, 4000],
"PLAND" : [40, 40]
},
{
"name" : "Class C",
"NP" : [5, 7],
"AREA" : [800, 1200]
}
]
}
Using the CLI, you can generate a non-spatially-explicit landscape structure using the flsgen structure
command:
Usage: flsgen structure [-hV] [-n=<nbSolutions>] [-s=<search>] <outputPrefix>
[<jsonPaths>...]
Generate a landscape structure satisfying a set of targets
<outputPrefix> JSON output file (or prefix for multiple structure
generation) for solution -- Use "-" to write to
STDOUT (only possible with one structure as input
and single-solution generation)
[<jsonPaths>...] JSON input file(s) describing landscape targets -- Use
"-" to read from STDIN (only possible with one
structure as input) -- Use multiple space-separated
paths to generate landscapes with different
structures.
-h, --help Show this help message and exit.
-n, --nb-solutions=<nbSolutions>
Number of solutions to generate, if greater than one,
use a prefix for JSON output file (default: 1).
-s, --search-strategy=<search>
Search strategy to use in the Choco org.flsgen.solver
(possible values: DEFAULT, RANDOM, DOM_OVER_W_DEG,
DOM_OVER_W_DEG_REF, ACTIVITY_BASED,
CONFLICT_HISTORY, MIN_DOM_UB, MIN_DOM_LB).
-V, --version Print version information and exit.
For example, from the previous targets file:
flsgen structure struct target.json
If there exist a landscape structure satisfying these targets, the program will write the output structure into the struct_target.json
file.
To achieve the same result with the Java API:
import org.flsgen.LandscapeStructureSolver;
import org.flsgen.LandscapeStructure;
import org.flsgen.FlsgenException;
import org.flsgen.solver.*;
import com.github.cliftonlabs.json_simple.JsonException;
import org.apache.commons.io.IOUtils;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class StructureTest {
public static void main(String[] args) throws IOException, JsonException, FlsgenException {
LandscapeStructureSolver ls = LandscapeStructureSolverFactory.readFromJSON(
IOUtils.toString(new FileReader("target.json"))
);
ls.build();
LandscapeStructure struct = ls.findSolution();
FileWriter writer = new FileWriter("struct_target.json");
writer.write(struct.toJSON());
writer.close();
}
}
Now, let's generate a landscape raster from the previously generated structure.
Using the CLI, we use the flsgen generate
command to generate a landscape raster from a landscape structure:
Usage: flsgen generate [-hV] [-c=<connectivity>] [-D=<minDistance>]
[-e=<terrainOutput>] [-l=<terrainInput>] [-m=<maxTry>]
[-n=<nbLandscapes>] [-p=<maxTryPatch>] [-r=<resolution>]
[-R=<roughnessFactor>] [-s=<srs>] [-t=<template>]
[-T=<terrainDependency>] [-x=<x>] [-y=<y>]
<outputPrefix> [<jsonPaths>...]
Generate landscapes from given structures. To produce more realistic landscape,
the algorithm relies on a terrain either given as input or automatically
generated as a fractal terrain.
<outputPrefix> Output raster prefix path for generated landscape(s)
[<jsonPaths>...] JSON input file describing landscape structure -- Use
"-" to read from STDIN (only possible with one
structure as input) -- Use multiple space-separated
paths to generate landscapes with different
structures.
-c, --connectivity=<connectivity>
Connectivity definition in the regular square org.
flsgen.grid - '4' (4-connected) or '8' (8-connected)
(default: 4).
-D, --distance-between-patches=<minDistance>
Minimum distance (in number of cells) between patches
from a same class (default: 2).
-e, -et, --export-terrain=<terrainOutput>
Set an output raster path to export the terrain used
to generate the landscape
-h, --help Show this help message and exit.
-l, -lt, --load-terrain=<terrainInput>
Load the terrain used by the algorithm from a raster
instead of generating it
-m, -mt, --max-try=<maxTry>
Maximum number or trials to generate the whole
landscape (default: 100).
-n, --nb-landscapes=<nbLandscapes>
Number of landscapes to generate (default: 1).
-p, -mtp, --max-try-patch=<maxTryPatch>
Maximum number of trials to generate a patch (default:
100).
-r, --resolution=<resolution>
Spatial resolution of the output raster (in CRS unit,
default: 0.0001)
-R, --roughness=<roughnessFactor>
Roughness parameter (also called H), between 0 and 1
for fractal terrain generation. Lower values produce
rougher terrain (default: 0.5)
-s, -srs, --spatial-reference-system=<srs>
Spatial reference system of the output raster
(default: EPSG:4326)
-t, -ot, --output-template=<template>
Raster template to use for output raster metadata
-T, --terrain-dependency=<terrainDependency>
Terrain dependency of the patch generation algorithm,
between 0 and 1. 0 means no dependency to the
terrain, and 1 mean that patch generation is
entirely guided by the terrain (default: 0.5)
-V, --version Print version information and exit.
-x=<x> Top left x coordinate of the output raster (default: 0)
-y=<y> Top left y coordinate of the output raster (default: 0)
From the previous terrain raster and the previous structure JSON file, we can generate a landscape raster file with the following command:
flsgen generate -l terrain.tif landscape struct_target.json
This will generate the landscape_struct_target.tif
raster file with default generation parameters. As the generation algorithm is randomized, you will get a different raster at each call, but always with the same patch sizes distribution. Below are three examples of generated landscapes with the previous command.
To achieve the same with the Java API:
import com.github.cliftonlabs.json_simple.JsonException;
import ors.flsgen.RasterUtils;
import org.flsgen.FlsgenException;
import org.flsgen.INeighborhood;
import org.flsgen.Neighborhoods;
import org.flsgen.RegularSquareGrid;
import org.flsgen.LandscapeStructure;
import org.flsgen.LandscapeGenerator;
import org.flsgen.Terrain;
import org.opengis.referencing.FactoryException;
import org.apache.commons.io.IOUtils;
import java.io.FileReader;
import java.io.IOException;
public class GenerateTest {
public static void main(String[] args) throws IOException, JsonException, FlsgenException, FactoryException {
LandscapeStructure structure = LandscapeStructureSolverFactory.fromJSON(
IOUtils.toString(new FileReader("struct_target.json"))
);
RegularSquareGrid grid = new RegularSquareGrid(200, 200);
Terrain terrain = new Terrain(grid);
double[] terrainData = RasterUtils.loadDoubleDataFromRaster("terrain.tif", terrain.getGrid());
terrain.loadFromData(terrainData);
INeighborhood neighborhood = Neighborhoods.FOUR_CONNECTED;
INeighborhood distance = Neighborhoods.TWO_WIDE_FOUR_CONNECTED;
LandscapeGenerator generator = new LandscapeGenerator(structure, neighborhood, distance, terrain);
generator.generate(0.5, 10,10);
int[] rasterData = landscapeGenerator.getRasterData(noDataValue);
generator.exportRaster(rasterData, generator.getGrid(), 0, 0, 0.001, "EPSG:4326", "landscape_struct_target.tif");
}
}
It is possible to use a mask raster, whose NO_DATA cell will be unavailable for both focal and non-focal classes. To do so, instead of specifying the number of rows and columns in the targets, specify the mask raster in targets with the maskRasterPath
key:
{
"maskRasterPath": "mask.tif",
"classes" : [
{
"name" : "Class A",
"NP" : [2, 30],
"AREA" : [200, 4000],
"PLAND" : [40, 40]
}
]
}
or:
Instead of generating landscape structure from targets, it is also possible to extract existing structures from real landscapes and use them to recreate real composition patterns. To do so, simply use the flsgen extract_structure
command:
Usage: flsgen extract_structure [-hV] [-c=<connectivity>] <inputRaster>
<outputFile> [<focalClasses>...]
Extracts a landscape structure from an existing raster.
<inputRaster> Raster (.tif) input file to extract the landscape
structure from
<outputFile> JSON output file -- Use "-" to write to STDOUT
[<focalClasses>...] Raster values of the focal classes
-c, --connectivity=<connectivity>
Connectivity definition in the regular grid - '4'
(4-connected) or '8' (8-connected) (default: 4).
-h, --help Show this help message and exit.
-V, --version Print version information and exit.