public static Map<Integer,ComparableGrid2D<Double>> create(final ComparableGrid2D<Double> heightmap, final ComparableGrid2D<Double> soilmap, final ForestAttributes attributes)
	{
		final Map<Integer,ComparableGrid2D<Double>> result = new HashMap<Integer,ComparableGrid2D<Double>>();
		final Random rand = new Random(attributes.seed);
		
		// generate a 2D grid for each level, initialised with 0.0
		for(Entry<Integer, ForestLevels> level : attributes.levels.entrySet())
		{
			GridAttributes gridAttributes = new GridAttributes(heightmap.rows(), heightmap.cols(), attributes.seed);
			result.put(level.getKey(), GridFactory.create2D(GridType.DOUBLE_2D, gridAttributes));
		}
		
		// place forest seeds for each level
		heightmap.iterate(new Grid2DIterator<Double>()
		{
			@Override
			public void step(int row, int col, GridCell<Double> gridCell, Grid2D<Double> localgrid)
			{
				double height = heightmap.getDataAt(row, col).getData();
				for(Entry<Integer, ForestLevels> level : attributes.levels.entrySet())
				{
					if(level.getValue().lower <= height && level.getValue().upper >= height)
					{
						ComparableGrid2D<Double> grid = result.get(level.getKey());

						// TODO: modify tree probability at this position, currently based on level.
						double heightprob = (height-level.getValue().lower)/(level.getValue().upper - level.getValue().lower);
						double probability = -Math.abs(heightprob-0.5)+1;
						
						// TODO: modify forest seeding probability.
						// currently, prob * overall density * level density
						if(rand.nextDouble() < probability * attributes.density * level.getValue().density)
						{
							grid.setDataAt(row, col, probability);
						}
					}
				}
			}
		});
		
		// grow forests for some steps
		for(Entry<Integer, ForestLevels> level : attributes.levels.entrySet())
		{
			ComparableGrid2D<Double> initialGrid = result.get(level.getKey());
			CellularAutomata<Double> automata = new CellularAutomata<Double>(initialGrid, new CAStep<Double>() {

				@Override
				public ComparableGrid2D<Double> work(ComparableGrid2D<Double> grid)
				{
					// iterate over the forest probabilities and grow
					grid.iterate(new Grid2DIterator<Double>()
					{
						private final int[][] dirs = new int[][]{ new int[]{-1,0},  new int[]{1,0},  new int[]{0,-1},  new int[]{0,1}};

						@Override
						public void step(int row, int col, GridCell<Double> gridCell, Grid2D<Double> localgrid)
						{
							if(gridCell.getData() > 0)
							{
								int dir = rand.nextInt(4);
								int newrow = row + dirs [dir][0];
								int newcol = col + dirs[dir][1];
								if(!heightmap.invalid(newrow, newcol) && heightmap.getDataAt(newrow, newcol).getData() > 0)
								{
									localgrid.setDataAt(newrow, newcol, gridCell.getData());
								}
							}
						}
					});
					return grid;
				}
			});
			
			// some growing steps for the forest
			automata.step(3);
			// store the modified grid back in the result map
			result.put(level.getKey(), automata.result());
		}
		
		return result;
	}