Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions docs/usage/parameters/PostProcessing.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Each post-processor has a field `type` which is used to identify it.
* [Metadata parse](#metadata-parse)
* [Metadata remap](#metadata-remap)
* [Metadata truncate](#metadata-truncate)
* [Metadata conversion](#metadata-conversion)
* [Geometry post-processors](#geometry-post-processors)
* [Geometry buffer](#geometry-buffer)

Expand Down Expand Up @@ -224,6 +225,36 @@ Avaliable policies:
| `ignore` | Metadata is not modified. |
| `error` | An error occurs, and the generation stops. |

### Metadata conversion

Post-processor that converts metadata distances/altitude into voxel units.
This does not perform unit conversion. It assumes that the model uses the same unit as the chosen coordinate reference system used for projection.

**Type**: `conversion`

**Extra parameters**
- `metadata` (required): Name of the metadata to apply conversion.
- `convertAs` (required): The type of conversion to perform.
Since it is possible to provide two different scales (a vertical and a horizontal), the conversion of distance may not be the same whether it is a vertical distance (height for example)
or a horizontal distance (road width for example). As for the conversion of altitude it differs from a vertical distance as they might be an offset.
| convertAs | Explanation |
|:---------------------|:-----------------------------------------|
| `altitude` | Converts altitude into voxel. |
| `horizontalDistance` | Converts horizontal distance into voxel. |
| `verticalDistance` | Converts vertical distance into voxel. |

- `ifMissing` (optional, default `error`): Policy to apply if metadata is missing (see below).
- `ifConversionFail` (optional, default `error`): Policy to apply if conversion fails (see below).
Available policies:

| Policy | Explanation |
|:-----------------|:-----------------------------------------------------|
| `discardModel` | The model is discarded. |
| `removeMetadata` | The metadata is removed (no effect for `ifMissing`). |
| `ignore` | Metadata is not modified. |
| `error` | An error occurs, and the generation stops. |


## Geometry post-processors

These post-processors can be applied to models resulting from the [`geoToolsVector` processor](FetchDataTask.md#geotoolsvector-geotools-vector-processor), and they only modifies the model, without changing its type.
Expand Down
4 changes: 2 additions & 2 deletions docs/usage/parameters/TileTasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ If the heightmap value is lower than or equal to the altitude, the placeable use

- `models` (required, models must be voxelizable in 2d): [Selection of models](ModelSelection.md) to use.
- `heightmap` (required): [Heightmap](Heightmaps.md) to use.
- `altitudeMetadata` (required): Name of the model metadata containing the altitude value.
- `altitudeMetadata` (required): Name of the model metadata containing the altitude value. Must be expressed in voxel units.
- `placeAbove` (optional, default [`Nothing`](Placeables.md#nothing)): [Placeable](Placeables.md) placed above the altitude value.
- `placeBelow` (optional, default [`Nothing`](Placeables.md#nothing)): [Placeable](Placeables.md) placed below the altitude value.

Expand Down Expand Up @@ -138,7 +138,7 @@ Building height is given by `height` metadata (This task does nothing if the val
- `window`: [Placeable](Placeables.md) used to render windows.

#### Required model metadata

All the below metadata must be expressed in voxel unit.
- `height`: Height of the building to render (must be a positive integer). The height is defined as the distance between the minimum ground altitude and the gutter altitude.
- `minimum-ground-altitude`: Minimum altitude inside the shape of the model.
- `ground-floor-altitude`: Ground floor altitude of the model.
Expand Down
4 changes: 4 additions & 0 deletions examples/processes/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ forEachTile:
metadata: height
method: round
ifMissing: ignore
- type: conversion
metadata: height
convertAs: verticalDistance
ifMissing: ignore
Comment thread
zhak5388 marked this conversation as resolved.
- type: default
metadata: height
value: 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.ignfab.minalac.generator.parameters.processors.post.DiscardPostProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.post.IdentityPostProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.post.JTSGeometryBufferPostProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.post.MetadataConvertPostProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.post.MetadataCopyPostProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.post.MetadataDefaultPostProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.post.MetadataParsePostProcessorParams;
Expand Down Expand Up @@ -129,6 +130,7 @@ public static void main(String[] args) throws FactoryException, InterruptedExcep
parser.registerParams("truncate", MetadataTruncatePostProcessorParams.class);
parser.registerParams("geometryBuffer", JTSGeometryBufferPostProcessorParams.class);
parser.registerParams("remap", MetadataValueMappingPostProcessorParams.class);
parser.registerParams("conversion", MetadataConvertPostProcessorParams.class);

Generation generation = parser.parse(parameters).create(maxTileSize);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@

import java.util.Collection;

import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.crs.CoordinateReferenceSystem;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.util.AffineTransformation;

import com.ignfab.minalac.generator.exceptions.TransformException;
import com.ignfab.minalac.generator.generation.heightmaps.HeightmapDeclarationStore;
Expand All @@ -33,12 +30,13 @@ public class Generation {
// Target coordinate reference system (CRS used for voxel world rendering)
private final CoordinateReferenceSystem crs;

// Vertical size of voxel in target CRS units
private final double centerX;
private final double centerY;
// Horizontal size of voxel in target CRS units (map unit / voxel)
private final double horizontalScale;
// Vertical size of voxel in target CRS units (map unit / voxel)
private final double verticalScale;

// Transformations from and to target CRS
private final AffineTransformation crsToVoxel;
private final AffineTransformation voxelToCrs;
private final double angle;

private final VoxelWorld world;
private final HeightmapDeclarationStore heightmaps = new HeightmapDeclarationStore();
Expand Down Expand Up @@ -78,8 +76,6 @@ public Generation(
double angle,
int maxTileSize) {

this.seed = seed;

WorldBBox3d maximumLimits = world.maxLimits();
world.setLimits(new WorldBBox3d(
-extentX / 2,
Expand All @@ -90,25 +86,16 @@ public Generation(
maximumLimits.sizeZ()
));

this.seed = seed;
this.crs = crs;
this.centerX = centerX;
this.centerY = centerY;
this.horizontalScale = horizontalScale;
this.verticalScale = verticalScale;
this.angle = angle;
this.world = world;

this.maxTileSize = maxTileSize;
tiles = world.tiles(maxTileSize);

this.crs = crs;
this.verticalScale = verticalScale;

// CRS to Voxel transformation (basically, translates, rotates and scale)
crsToVoxel = new AffineTransformation();
crsToVoxel.translate(-centerX, -centerY);
crsToVoxel.scale(1.0 / horizontalScale, 1.0 / horizontalScale);
crsToVoxel.rotate(-angle);

Comment thread
zhak5388 marked this conversation as resolved.
// Voxel to CRS transformation (reverse of crsToVoxel transformation)
voxelToCrs = new AffineTransformation();
voxelToCrs.rotate(angle);
voxelToCrs.scale(horizontalScale, horizontalScale);
voxelToCrs.translate(centerX, centerY);
}

/**
Expand Down Expand Up @@ -143,41 +130,34 @@ public Scheduler<GenerationTile> scheduler() {
* @param bbox Bounding box to get envelope for.
* @return ReferencedEnvelope covering given bounding box in CRS.
*/
public ReferencedEnvelope getEnvelopeForCRS(CoordinateReferenceSystem crs, WorldBBox3d bbox) throws FactoryException, TransformException {
public ReferencedEnvelope getEnvelopeForCRS(CoordinateReferenceSystem crs, WorldBBox3d bbox) throws TransformException {
WorldToMapConverter converter = makeCoordsConverter(crs).inverse();

int minX = bbox.minX();
int minY = bbox.minY();
int maxX = bbox.maxX() + 1;
int maxY = bbox.maxY() + 1;
Geometry geom = new GeometryFactory().createLinearRing(new Coordinate[] {
new Coordinate(minX, minY),
new Coordinate(maxX, minY),
new Coordinate(maxX, maxY),
new Coordinate(minX, maxY),
new Coordinate(minX, minY)
converter.convert(new Coordinate(minX, minY)),
converter.convert(new Coordinate(maxX, minY)),
converter.convert(new Coordinate(maxX, maxY)),
converter.convert(new Coordinate(minX, maxY)),
converter.convert(new Coordinate(minX, minY))
});

return new ReferencedEnvelope(converter.convert(geom).getEnvelopeInternal(), crs);
return new ReferencedEnvelope(geom.getEnvelopeInternal(), crs);
}

/**
* Creates a converter for a given CRS.
*
* @param sourceCrs CRS from which convert map coordinates.
* @return A converter to be used to convert any map coordinates into generated world coordinates.
* @throws FactoryException If not suitable transformation found for conversion.
*/
public MapToWorldConverter makeCoordsConverter(CoordinateReferenceSystem sourceCrs) throws FactoryException {
return new MapToWorldConverter(CRS.findMathTransform(sourceCrs, crs), crsToVoxel);
}

/**
* {@return the vertical scale}
*/
// To be removed when vertical is used by this class. (Renderers will probably contain that value)
public double getVerticalScale() {
return verticalScale;
public MapToWorldConverter makeCoordsConverter(CoordinateReferenceSystem sourceCrs) {
// altitudeOffset will be possibly be used to store offset between different CRS.
// If that is the case, the world CRS should be a CRS containing an altitude of reference .
return new MapToWorldConverter(sourceCrs, crs, -centerX, -centerY, 1.0 / horizontalScale, 1.0 / verticalScale, -angle, -0.0);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.ignfab.minalac.generator.exceptions.TransformException;
import com.ignfab.minalac.generator.inputs.FloatGeographicDataMatrix2d;
import com.ignfab.minalac.generator.utils.coordinates.MapCoordinates;
import com.ignfab.minalac.generator.utils.coordinates.MapCoordinates2d;
import com.ignfab.minalac.generator.utils.coordinates.MapToWorldConverter;
import com.ignfab.minalac.generator.utils.coordinates.WorldToMapConverter;
import com.ignfab.minalac.generator.utils.world2d.WorldBBox2d;
Expand All @@ -11,24 +11,25 @@

/**
* A model based on a matrix of floats (usually a heightmap model).
* The coordinates used to access its values are world coordinates.
* Beware its values are not converted, it is up to the caller to make the necessary changes (For example convert altitude in world unit)
*/
public class FloatMatrixModel extends ModelImpl implements Matrix2d<Float> {
private FloatGeographicDataMatrix2d data;
private MapToWorldConverter mapToWorld;
private WorldToMapConverter worldToMap;
private WorldBBox2d bbox;
private final FloatGeographicDataMatrix2d data;
private final WorldToMapConverter worldToMap;
private final WorldBBox2d bbox;

/**
* Creates a new {@code FloatMatrixModel}.
*
* @param data underlying geographic data
* @param converter coordinates converter from matrix to world
* @throws TransformException
* @throws TransformException if converter is not invertible
*/
public FloatMatrixModel(FloatGeographicDataMatrix2d data, MapToWorldConverter converter) throws TransformException {
mapToWorld = converter;
super(converter);
try {
worldToMap = mapToWorld.inverse();
worldToMap = converter.inverse();
} catch (TransformException e) {
throw new IllegalArgumentException("converter must be invertible");
}
Expand All @@ -39,10 +40,10 @@ public FloatMatrixModel(FloatGeographicDataMatrix2d data, MapToWorldConverter co
double maxY = data.offsetY() + data.sizeY() * data.cellSizeY() - 1.0;

this.bbox = new WorldBBox2d(
mapToWorld.convert(new MapCoordinates(data.offsetX(), data.offsetY())),
mapToWorld.convert(new MapCoordinates(data.offsetX(), maxY)),
mapToWorld.convert(new MapCoordinates(maxX, data.offsetY())),
mapToWorld.convert(new MapCoordinates(maxX, maxY))
converter.convert(new MapCoordinates2d(data.offsetX(), data.offsetY())),
converter.convert(new MapCoordinates2d(data.offsetX(), maxY)),
converter.convert(new MapCoordinates2d(maxX, data.offsetY())),
converter.convert(new MapCoordinates2d(maxX, maxY))
);
}

Expand All @@ -55,7 +56,7 @@ public WorldBBox2d bbox() {
public Float get(WorldCoords2d coords) {
// Here we perform coordinates conversion and value interpolation

MapCoordinates coordinates;
MapCoordinates2d coordinates;
try {
coordinates = worldToMap.convert(coords);
} catch (TransformException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ public class JTSGeometryModel extends ModelImpl implements ShapesVoxelizable2d,
* @param converter Converter from geometry CRS to world coordinates
*/
public JTSGeometryModel(Geometry geom, MapToWorldConverter converter) throws TransformException {
super();
// Until there is no need of it we don't keep original geometry.
// Geometry is stored transformed into world coordinates
super(converter);
this.geom = converter.convert(geom);
}

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/ignfab/minalac/generator/models/Model.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ignfab.minalac.generator.models;

import com.ignfab.minalac.generator.utils.coordinates.MapToWorldConverter;
import com.ignfab.minalac.generator.utils.random.Salting;

/**
Expand Down Expand Up @@ -45,4 +46,10 @@ public interface Model extends Salting {
* See docs/development/RandomNumbers.md for more detailed information.
*/
String salt();

/**
* {@return the {@link MapToWorldConverter } of this model.}
* It allows to convert the coordinates/distances of this model into world (game) coordinates/distances.
*/
MapToWorldConverter converter();
}
20 changes: 20 additions & 0 deletions src/main/java/com/ignfab/minalac/generator/models/ModelImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import java.util.HashMap;
import java.util.Map;

import com.ignfab.minalac.generator.utils.coordinates.MapToWorldConverter;

/**
* Models are objects that can be rendered.
* They optionally can have some metadata attached to them.
Expand All @@ -12,6 +14,19 @@ public abstract class ModelImpl implements Model {
* Model metadata.
*/
private final Map<String, Object> metadata = new HashMap<>();
/**
* Converter allowing to convert the coordinates/distances of this model into world (game) coordinates/distances.
*/
private final MapToWorldConverter converter;

/**
* Base constructor for models.
*
* @param converter the {@link MapToWorldConverter} to use.
*/
protected ModelImpl(MapToWorldConverter converter) {
this.converter = converter;
}

@Override
public boolean hasMetadata(String name) {
Expand All @@ -31,4 +46,9 @@ public void setMetadata(String name, Object value) {
else
metadata.put(name, value);
}

@Override
public MapToWorldConverter converter() {
return this.converter;
}
}
Loading