Skip to content

Commit

Permalink
Merge pull request #37 from svencc/feature/forest-cell-renderer-and-s…
Browse files Browse the repository at this point in the history
…pacial-index

Feature/forest cell renderer and spacial index
  • Loading branch information
svencc authored Mar 10, 2024
2 parents c8fd1ee + 97a86e3 commit f03a09b
Show file tree
Hide file tree
Showing 32 changed files with 615 additions and 155 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ To create more intelligence from gameMap data the following features are planned
* ![mergedmap2-preview.png](docs%2Fmd-media%2Fmergedmap2-preview.png)
* Forest detection / forest heatmap
* ![forestmap-preview.png](docs%2Fmd-media%2Fforestmap-preview.png)
* Structure detection
* ![structuremap-preview.png](docs%2Fmd-media%2Fstructuremap-preview.png)
* Terrain heightmap
* ![topographic-scanner-example.png](docs%2Fmd-media%2Ftopographic-scanner-example.png)
* Map shading / lightmap
Expand Down
13 changes: 7 additions & 6 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
# TODO LIST

# 1
* add forest heatmap
* support scaling!
* extract forest colors to MapScheme

* extract threshold forest/structure to configuration
* make step size an int (minimum 1 meter); so no float and rounding will be used anymore in code > in client as well
*
* /api/v1/configuration/map-tools/resources/forest?mapName= tooks way too long; does it already make use of the resource_name, class_name and prefab_name tables?
* cache forest and/or spatialForestMap
* cache forest and/or spatialForestMap > eclipseStore


* add village heatmap
* add names to villages
* add general map names

* Save Aspect in rad value for smoother shading!


* integrate pipeline in commander!
* add DynamicLayerProperties (configuration for RecomMapEntity) (/)


* known issues or optimizations:
* prebuffering of zoom levels (for faster zooming) or find out what is the bottleneck
* (add pre-click-event (preclick, click, doublecklic) events)
Expand Down
Binary file modified docs/md-media/mergedmap2-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/md-media/structuremap-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
public class CoordinateConverter {

public double threeDeeZToTwoDeeY(
final double y,
final int demHeight,
final double stepSize
final double yCoordinate,
final double demHeightInMeter
) {
return (demHeight * stepSize) - y;
return (demHeightInMeter) - yCoordinate;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import com.recom.commons.calculator.ARGBCalculator;
import com.recom.commons.map.rasterizer.mapdesignscheme.MapDesignScheme;
import com.recom.commons.model.DEMDescriptor;
import com.recom.commons.model.maprendererpipeline.dataprovider.SpacialIndex;
import com.recom.commons.model.maprendererpipeline.dataprovider.forest.ForestItem;
import com.recom.commons.model.maprendererpipeline.dataprovider.forest.SpacialIndex;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

Expand All @@ -16,7 +16,7 @@ public class D8AlgorithmForForestMap {

@NonNull
private final ARGBCalculator colorCalculator = new ARGBCalculator();
private final double cellSize;
private final int forestCellSizeInMeter;


@NonNull
Expand All @@ -29,10 +29,9 @@ public int[][] generateForestMap(
final int demHeight = demDescriptor.getDemHeight();

final int[][] forestMap = new int[demWidth][demHeight];

for (int x = 0; x < demWidth; x++) {
for (int y = 0; y < demHeight; y++) {
forestMap[x][y] = calculateForestFragment(demDescriptor, spacialIndex, mapScheme, x, y);
for (int demX = 0; demX < demWidth; demX++) {
for (int demY = 0; demY < demHeight; demY++) {
forestMap[demX][demY] = calculateForestFragment(demDescriptor, spacialIndex, mapScheme, demX, demY);
}
}

Expand All @@ -44,40 +43,47 @@ private int calculateForestFragment(
@NonNull final DEMDescriptor demDescriptor,
@NonNull final SpacialIndex<ForestItem> spacialIndex,
@NonNull final MapDesignScheme mapScheme,
final int x,
final int y
final int demX,
final int demY
) {
final List<ForestItem> forestItemsInSpace = spacialIndex.get(x, y);
final double treeDensity = forestItemsInSpace.size() / cellSize * cellSize;
int surroundingForestNeighbourSpaces = 0;
final int forestCellSizeSquared = forestCellSizeInMeter * forestCellSizeInMeter;
double forestDensityThreshold = 1F / 100; // @TODO extract to conf

// 0.15 Trees per 25m²
float forestDensityThreshold = 0.15f; // @TODO extract to conf
final int spacialX = demX * demDescriptor.getStepSize();
final int spacialY = demY * demDescriptor.getStepSize();

final int demWidth = demDescriptor.getDemWidth();
final int demHeight = demDescriptor.getDemHeight();
final List<ForestItem> forestItemsInSpace = spacialIndex.getInSpace(spacialX, spacialY);
final double treeDensity = forestItemsInSpace.size() / (double) forestCellSizeSquared;

int surroundingForestNeighbourSpaces = 0;
for (int direction = 0; direction < 8; direction++) {
final int adjacentNeighborX = x + D8AspectMatrix.directionXComponentMatrix[direction]; // Calculate the X-coordinate of the adjacent neighbor.
final int adjacentNeighborY = y + D8AspectMatrix.directionYComponentMatrix[direction]; // Calculate the Y-coordinate of the adjacent neighbor.
final double adjacentNeighborSpatialX = spacialX + (D8AspectMatrix.directionXComponentMatrix[direction] * demDescriptor.getStepSize()); // Calculate the X-coordinate of the adjacent neighbor.
final double adjacentNeighborSpatialY = spacialY + (D8AspectMatrix.directionYComponentMatrix[direction] * demDescriptor.getStepSize()); // Calculate the Y-coordinate of the adjacent neighbor.

if (adjacentNeighborSpatialX < 0 || adjacentNeighborSpatialX > demDescriptor.getMapWidthInMeter() || adjacentNeighborSpatialY < 0 || adjacentNeighborSpatialY > demDescriptor.getMapHeightInMeter()) {
continue;
} else {
final List<ForestItem> forestItemsInNeighborCell = spacialIndex.getInSpace(adjacentNeighborSpatialX, adjacentNeighborSpatialY);

double neighbourForestDensity;
if (!forestItemsInNeighborCell.isEmpty()) {
neighbourForestDensity = forestItemsInNeighborCell.size() / (double) forestCellSizeSquared;
} else {
neighbourForestDensity = 0;
}

if (adjacentNeighborX >= 0 && adjacentNeighborY >= 0 && adjacentNeighborX < demWidth && adjacentNeighborY < demHeight) {
final List<ForestItem> forestItemsInNeighborSpace = spacialIndex.get(adjacentNeighborX, adjacentNeighborY);
final double neighbourForestDensity = forestItemsInNeighborSpace.size() / cellSize * cellSize;
if (neighbourForestDensity > forestDensityThreshold) {
if (neighbourForestDensity >= forestDensityThreshold) {
surroundingForestNeighbourSpaces++;
}
}
}

final int baseColorForest = mapScheme.getBaseColorForest();

if (treeDensity < forestDensityThreshold) {
return mapScheme.getBaseColorContourBackground(); // @TODO extract to new variable baseColorOfForestBackground!!!
} else if (treeDensity > forestDensityThreshold && surroundingForestNeighbourSpaces >= 5) {
return baseColorForest;
return mapScheme.getBaseColorForestBackground();
} else if (treeDensity >= forestDensityThreshold && surroundingForestNeighbourSpaces >= 5) {
return mapScheme.getBaseColorForest();
} else {
return colorCalculator.modifyTransparency(baseColorForest, 0.5);
return colorCalculator.modifyTransparency(mapScheme.getBaseColorForest(), 0.5);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public int[][] generateShadedMap(
final int mapHeight = slopeAndAspectMap[0].length;
final int[][] shadingMap = new int[mapWidth][mapHeight];

for (int x = 0; x < mapWidth; x++) {
for (int y = 0; y < mapHeight; y++) {
shadingMap[x][y] = calculateShading(slopeAndAspectMap[x][y], shadowingScheme);
for (int demX = 0; demX < mapWidth; demX++) {
for (int demY = 0; demY < mapHeight; demY++) {
shadingMap[demX][demY] = calculateShading(slopeAndAspectMap[demX][demY], shadowingScheme);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public SlopeAndAspect[][] generateSlopeAndAspectMap(@NonNull final float[][] dem
final SlopeAndAspect[][] slopeAndAspects = new SlopeAndAspect[demWidth][demHeight];

// Iterate through each cell in the DEM to calculate its slope and aspect.
for (int x = 0; x < demWidth; x++) {
for (int y = 0; y < demHeight; y++) {
slopeAndAspects[x][y] = calculateSlopeAndAspect(dem, x, y);
for (int demX = 0; demX < demWidth; demX++) {
for (int demY = 0; demY < demHeight; demY++) {
slopeAndAspects[demX][demY] = calculateSlopeAndAspect(dem, demX, demY);
}
}

Expand All @@ -41,15 +41,15 @@ public SlopeAndAspect[][] generateSlopeAndAspectMap(@NonNull final float[][] dem
* It identifies the steepest slope among the 8 possible descent directions.
*
* @param dem The digital elevation model (DEM) as a 2D array of elevation values.
* @param x The X-coordinate (column) of the cell in the DEM.
* @param y The Y-coordinate (row) of the cell in the DEM.
* @param demX The X-coordinate (column) of the cell in the DEM.
* @param demY The Y-coordinate (row) of the cell in the DEM.
* @return The maximum slope from the given cell.
*/
@NonNull
private SlopeAndAspect calculateSlopeAndAspect(
final float[][] dem,
final int x,
final int y
final int demX,
final int demY
) {
final int demWidth = dem.length;
final int demHeight = dem[0].length;
Expand All @@ -59,13 +59,13 @@ private SlopeAndAspect calculateSlopeAndAspect(

for (int direction = 0; direction < 8; direction++) {
final Aspect currentAspect = D8AspectMatrix.aspects[direction];
final int adjacentNeighborX = x + D8AspectMatrix.directionXComponentMatrix[direction]; // Calculate the X-coordinate of the adjacent neighbor.
final int adjacentNeighborY = y + D8AspectMatrix.directionYComponentMatrix[direction]; // Calculate the Y-coordinate of the adjacent neighbor.
final int adjacentNeighborX = demX + D8AspectMatrix.directionXComponentMatrix[direction]; // Calculate the X-coordinate of the adjacent neighbor.
final int adjacentNeighborY = demY + D8AspectMatrix.directionYComponentMatrix[direction]; // Calculate the Y-coordinate of the adjacent neighbor.

// Prüfen, ob der neue Punkt innerhalb der Grenzen liegt
if (adjacentNeighborX >= 0 && adjacentNeighborY >= 0 && adjacentNeighborX < demWidth) {
if (adjacentNeighborY < demHeight) {
final double relativeDifference = dem[x][y] - dem[adjacentNeighborX][adjacentNeighborY];
final double relativeDifference = dem[demX][demY] - dem[adjacentNeighborX][adjacentNeighborY];
final int differenceSign = Sign.of(relativeDifference);
final double absoluteDifference = Math.abs(relativeDifference);
// Elevation difference to the neighbor.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.recom.commons.calculator.d8algorithm;


import com.recom.commons.calculator.ARGBCalculator;
import com.recom.commons.map.rasterizer.mapdesignscheme.MapDesignScheme;
import com.recom.commons.model.DEMDescriptor;
import com.recom.commons.model.maprendererpipeline.dataprovider.SpacialIndex;
import com.recom.commons.model.maprendererpipeline.dataprovider.village.StructureItem;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.util.List;

@RequiredArgsConstructor
public class D8AlgorithmForStructureMap {

@NonNull
private final ARGBCalculator colorCalculator = new ARGBCalculator();
private final int structureCellSizeInMeter;


@NonNull
public int[][] generateStructureMap(
@NonNull final DEMDescriptor demDescriptor,
@NonNull final SpacialIndex<StructureItem> spacialIndex,
@NonNull final MapDesignScheme mapScheme
) {
final int demWidth = demDescriptor.getDemWidth();
final int demHeight = demDescriptor.getDemHeight();

final int[][] structureMap = new int[demWidth][demHeight];
for (int demX = 0; demX < demWidth; demX++) {
for (int demY = 0; demY < demHeight; demY++) {
structureMap[demX][demY] = calculateStructureFragment(demDescriptor, spacialIndex, mapScheme, demX, demY);
}
}

return structureMap;
}

@NonNull
private int calculateStructureFragment(
@NonNull final DEMDescriptor demDescriptor,
@NonNull final SpacialIndex<StructureItem> spacialIndex,
@NonNull final MapDesignScheme mapScheme,
final int demX,
final int demY
) {
final int structureCellSizeSquared = structureCellSizeInMeter * structureCellSizeInMeter;
double structureDensityThreshold = 1F / 100; // @TODO extract to conf

final int spacialX = demX * demDescriptor.getStepSize();
final int spacialY = demY * demDescriptor.getStepSize();

final List<StructureItem> structureItemsInSpace = spacialIndex.getInSpace(spacialX, spacialY);
final double StructureDensity = structureItemsInSpace.size() / (double) structureCellSizeSquared;

int surroundingStructureNeighbourSpaces = 0;
for (int direction = 0; direction < 8; direction++) {
final double adjacentNeighborSpatialX = spacialX + (D8AspectMatrix.directionXComponentMatrix[direction] * demDescriptor.getStepSize()); // Calculate the X-coordinate of the adjacent neighbor.
final double adjacentNeighborSpatialY = spacialY + (D8AspectMatrix.directionYComponentMatrix[direction] * demDescriptor.getStepSize()); // Calculate the Y-coordinate of the adjacent neighbor.

if (adjacentNeighborSpatialX < 0 || adjacentNeighborSpatialX > demDescriptor.getMapWidthInMeter() || adjacentNeighborSpatialY < 0 || adjacentNeighborSpatialY > demDescriptor.getMapHeightInMeter()) {
continue;
} else {
final List<StructureItem> structureItemsInNeighborCell = spacialIndex.getInSpace(adjacentNeighborSpatialX, adjacentNeighborSpatialY);

double neighbourStructureDensity;
if (!structureItemsInNeighborCell.isEmpty()) {
neighbourStructureDensity = structureItemsInNeighborCell.size() / (double) structureCellSizeSquared;
} else {
neighbourStructureDensity = 0;
}

if (neighbourStructureDensity >= structureDensityThreshold) {
surroundingStructureNeighbourSpaces++;
}
}
}

if (StructureDensity < structureDensityThreshold) {
return mapScheme.getBaseColorStructureBackground();
} else if (StructureDensity >= structureDensityThreshold && surroundingStructureNeighbourSpaces >= 5) {
return mapScheme.getBaseColorStructure();
} else {
return colorCalculator.modifyTransparency(mapScheme.getBaseColorStructure(), 0.5);
}
}

}
11 changes: 11 additions & 0 deletions libs/commons/src/main/java/com/recom/commons/map/MapComposer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.recom.commons.map.rasterizer.configuration.MapLayerRasterizer;
import com.recom.commons.model.maprendererpipeline.MapComposerWorkPackage;
import com.recom.commons.model.maprendererpipeline.dataprovider.forest.ForestProvidable;
import com.recom.commons.model.maprendererpipeline.dataprovider.village.StructureProvidable;
import com.recom.commons.model.maprendererpipeline.report.PipelineLogMessage;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down Expand Up @@ -32,12 +33,19 @@ public class MapComposer {
@Getter
@NonNull
private Optional<ForestProvidable> forestProvider = Optional.empty();
@Getter
@NonNull
private Optional<StructureProvidable> structureProvider = Optional.empty();


public void registerForestProvider(@NonNull final ForestProvidable forestProvider) {
this.forestProvider = Optional.of(forestProvider);
}

public void registerVillageProvider(@NonNull final StructureProvidable villageProvider) {
this.structureProvider = Optional.of(villageProvider);
}

@NonNull
public static MapComposer withDefaultConfiguration() {
final MapComposer mapComposer = new MapComposer();
Expand All @@ -48,6 +56,7 @@ public static MapComposer withDefaultConfiguration() {
mapComposer.registerRenderer(new HeightMapRasterizer());
mapComposer.registerRenderer(new BaseMapRasterizer());
mapComposer.registerRenderer(new ShadowedMapRasterizer());
mapComposer.registerRenderer(new StructureMapRasterizer(mapComposer));
mapComposer.registerRenderer(new ForestMapRasterizer(mapComposer));
mapComposer.registerRenderer(new ContourLineMapRasterizer());

Expand Down Expand Up @@ -89,6 +98,7 @@ private void renderDataInParallel(@NonNull final MapComposerWorkPackage workPack
try {
renderer.render(workPackage);
} catch (final Throwable t) {
log.error("Failed to render data!", t);
workPackage.getReport().logException(t);
}

Expand All @@ -110,6 +120,7 @@ private void renderCoreDataInSequence(@NonNull final MapComposerWorkPackage work
try {
renderer.render(workPackage);
} catch (final IOException e) {
log.error("Failed to render data!", e);
workPackage.getReport().logException(e);
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public int[] rasterizeBaseMap(
final float heightRange = demDescriptor.getMaxHeight() - demDescriptor.getSeaLevel();
final float depthRange = demDescriptor.getMaxWaterDepth() - demDescriptor.getSeaLevel();

for (int x = 0; x < width; x++) {
for (int z = 0; z < height; z++) {
final float heightValue = demDescriptor.getDem()[x][z];
for (int demX = 0; demX < width; demX++) {
for (int demY = 0; demY < height; demY++) {
final float heightValue = demDescriptor.getDem()[demX][demY];
int color;

if (heightValue > demDescriptor.getSeaLevel()) {
Expand All @@ -59,7 +59,7 @@ public int[] rasterizeBaseMap(
color = argbCalculator.modifyBrightness(mapScheme.getBaseColorWater(), Math.abs(1 - dynamicDepthUnit));
}

imageBuffer[x + z * width] = color;
imageBuffer[demX + demY * width] = color;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ public int[] rasterizeContourMap(
final int height = DEMDescriptor.getDemHeight();

final int[] pixelBuffer = new int[width * height];
for (int x = 0; x < width; x++) {
for (int z = 0; z < height; z++) {
pixelBuffer[x + z * width] = contourMap[x][z];
for (int demX = 0; demX < width; demX++) {
for (int demY = 0; demY < height; demY++) {
pixelBuffer[demX + demY * width] = contourMap[demX][demY];
}
}

Expand Down
Loading

0 comments on commit f03a09b

Please sign in to comment.