Skip to content
Draft
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
35 changes: 35 additions & 0 deletions examples/processes/mapterhorn.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
heightmaps:
ground:
default: 0

forEachTile:

altitude:
type: sequence
do:
- type: fetchData
modelType: altitude
provider:
type: terrarium
url: https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png
zoom: 14
#resolution: 30
- type: populateHeightmap
models:
type: altitude
heightmap: ground
- type: setSpawn
heightmap:
sum: [ground, 1]
x: 0
y: 0
- type: renderHeightmap
at: ground
place:
structure:
- place: *voxel-grass
at: [0, 0, 0]
- place: *voxel-dirt
at: [0, 0, -3..-1]
- place: *voxel-stone
at: [0, 0, -20..-4]
2 changes: 2 additions & 0 deletions src/main/java/com/ignfab/minalac/generator/Voxatile.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.ignfab.minalac.generator.parameters.providers.GeoTiffProviderParams;
import com.ignfab.minalac.generator.parameters.providers.OverpassProviderParams;
import com.ignfab.minalac.generator.parameters.providers.ShapefileProviderParams;
import com.ignfab.minalac.generator.parameters.providers.TerrariumTilesProviderParams;
import com.ignfab.minalac.generator.parameters.providers.WFSProviderParams;
import com.ignfab.minalac.generator.parameters.providers.WMSFloatBilProviderParams;
import com.ignfab.minalac.generator.parameters.tasks.CopyHeightmapTaskParams;
Expand Down Expand Up @@ -135,6 +136,7 @@ public static void main(String[] args) throws FactoryException, InterruptedExcep
parser.registerParams("renderPoints2d", RenderPoints2dTaskParams.class);
parser.registerParams("setSpawn", SetSpawnTaskParams.class);

parser.registerParams("terrarium", TerrariumTilesProviderParams.class);
parser.registerParams("wfs", WFSProviderParams.class);
parser.registerParams("gpkg", GeoPackageProviderParams.class);
parser.registerParams("shapefile", ShapefileProviderParams.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.ignfab.minalac.generator.inputs;

import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;

import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.crs.DefaultGeographicCRS;

import com.ignfab.minalac.generator.exceptions.GenerationFailedException;
import com.ignfab.minalac.generator.exceptions.RetryableException;
import com.ignfab.minalac.generator.utils.coordinates.EnvelopeProvider;
import com.ignfab.minalac.generator.utils.coordinates.WebMercatorUtils;
import com.ignfab.minalac.generator.utils.world3d.WorldBBox3d;

/**
* Data provider reading images from generic XYZ tile endpoints.
*/
public class XYZTilesProvider implements Provider<BufferedImage> {
private static final int CONNECT_TIMEOUT_MS = 10_000;
private static final int READ_TIMEOUT_MS = 60_000;

private final String urlTemplate;
private final EnvelopeProvider envelopeProvider;
private final int zoom;

/**
* Creates a new {@code XYZTilesProvider} instance.
*
* @param urlTemplate the URL template for the XYZ tiles, with placeholders {z}, {x}, and {y}
* @param zoom the zoom level for the tiles
* @param envelopeProvider the provider used to compute the envelope for the given CRS and bounding box
*/
public XYZTilesProvider(
String urlTemplate,
int zoom,
EnvelopeProvider envelopeProvider
) {
this.urlTemplate = urlTemplate;
this.zoom = zoom;
this.envelopeProvider = envelopeProvider;
}

@Override
public Class<BufferedImage> providedType() {
return BufferedImage.class;
}

@Override
public Result<BufferedImage> provide(WorldBBox3d bbox) throws GenerationFailedException, RetryableException {
ReferencedEnvelope wgs84Envelope;
try {
wgs84Envelope = envelopeProvider.computeForCRS(DefaultGeographicCRS.WGS84, bbox);
} catch (Exception e) {
throw new GenerationFailedException("Cannot compute WGS84 envelope", e);
}

int minTileX = WebMercatorUtils.clamp((int) Math.floor(WebMercatorUtils.fractionalX(wgs84Envelope.getMinX(), zoom)), zoom);
int maxTileX = WebMercatorUtils.clamp((int) Math.floor(WebMercatorUtils.fractionalX(wgs84Envelope.getMaxX(), zoom)), zoom);
int minTileY = WebMercatorUtils.clamp((int) Math.floor(WebMercatorUtils.fractionalY(wgs84Envelope.getMaxY(), zoom)), zoom);

Check failure on line 66 in src/main/java/com/ignfab/minalac/generator/inputs/XYZTilesProvider.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/inputs/XYZTilesProvider.java#L66 <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck>

Line has trailing spaces.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/inputs/XYZTilesProvider.java:66:0: error: Line has trailing spaces. (com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck)
int maxTileY = WebMercatorUtils.clamp((int) Math.floor(WebMercatorUtils.fractionalY(wgs84Envelope.getMinY(), zoom)), zoom);

List<BufferedImage> tiles = new ArrayList<>();
for (int x = minTileX; x <= maxTileX; x++)
for (int y = minTileY; y <= maxTileY; y++) {
BufferedImage image = download(x, y);
if (image != null) tiles.add(image);
}

return new SimpleResult<>(DefaultGeographicCRS.WGS84, tiles.iterator());
}

// Downloads a single tile image for the given x and y coordinates at the configured zoom level.
private BufferedImage download(int x, int y) throws GenerationFailedException, RetryableException {
String url = urlTemplate.replace("{z}", String.valueOf(zoom))
.replace("{x}", String.valueOf(x))
.replace("{y}", String.valueOf(y));

try {
URLConnection connection = URI.create(url).toURL().openConnection();
connection.setConnectTimeout(CONNECT_TIMEOUT_MS);
connection.setReadTimeout(READ_TIMEOUT_MS);

if (connection instanceof HttpURLConnection http) {
int status = http.getResponseCode();
if (status == HttpURLConnection.HTTP_NOT_FOUND) return null;
if (status != HttpURLConnection.HTTP_OK)
throw new RetryableException("Tile " + url + " returned HTTP " + status);
}

try (InputStream stream = connection.getInputStream()) {
BufferedImage image = ImageIO.read(stream);
if (image == null)
throw new GenerationFailedException("No image reader for " + url);
return image;
}
} catch (IOException | IllegalArgumentException e) {
throw new RetryableException("Error or invalid URL reading tile " + url, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

@Override
public Float get(WorldCoords2d coords) {
System.out.println(coords);
// Here we perform coordinates conversion and value interpolation

MapCoordinates coordinates;
Expand All @@ -71,13 +72,17 @@
int yf = (int) Math.floor(y);
int xc = (int) Math.ceil(x);
int yc = (int) Math.ceil(y);
System.out.println("aaa");
if (xf < 0 || yf < 0 || xc >= data.sizeX() || yc >= data.sizeY())
return null;
System.out.println("aaa");

// Basic bilinear interpolation
float fx = x - xf;
float fy = y - yf;

System.out.println("Interpolating at local coordinates: x=" + x + ", y=" + y + " with floor: xf=" + xf + ", yf=" + yf + " and ceil: xc=" + xc + ", yc=" + yc);
System.out.println( (1 - fy) * ((1 - fx) * data.getFloat(xf, yf) + fx * data.getFloat(xc, yf))

Check failure on line 84 in src/main/java/com/ignfab/minalac/generator/models/FloatMatrixModel.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/models/FloatMatrixModel.java#L84 <com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck>

'(' is followed by whitespace.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/models/FloatMatrixModel.java:84:27: error: '(' is followed by whitespace. (com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck)
+ fy * ((1 - fx) * data.getFloat(xf, yc) + fx * data.getFloat(xc, yc)));
return (1 - fy) * ((1 - fx) * data.getFloat(xf, yf) + fx * data.getFloat(xc, yf))
+ fy * ((1 - fx) * data.getFloat(xf, yc) + fx * data.getFloat(xc, yc));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ignfab.minalac.generator.parameters.processors;

import java.awt.image.BufferedImage;
import com.ignfab.minalac.generator.generation.Generation;

Check failure on line 4 in src/main/java/com/ignfab/minalac/generator/parameters/processors/TerrariumImageProcessorParams.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/parameters/processors/TerrariumImageProcessorParams.java#L4 <com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck>

'com.ignfab.minalac.generator.generation.Generation' should be separated from previous imports.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/parameters/processors/TerrariumImageProcessorParams.java:4:1: error: 'com.ignfab.minalac.generator.generation.Generation' should be separated from previous imports. (com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck)
import com.ignfab.minalac.generator.models.FloatMatrixModel;
import com.ignfab.minalac.generator.processors.Processor;
import com.ignfab.minalac.generator.processors.TerrariumImageProcessor;

/**
* Parameters for Terrarium image processors.
*/
public class TerrariumImageProcessorParams extends ProcessorParams {
@Override
public Processor<BufferedImage, FloatMatrixModel> create(Generation generation) {
return new TerrariumImageProcessor(generation::makeCoordsConverter);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.ignfab.minalac.generator.parameters.providers;

import java.awt.image.BufferedImage;
import java.beans.ConstructorProperties;

import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.Nulls;

import com.ignfab.minalac.generator.generation.Generation;
import com.ignfab.minalac.generator.inputs.Provider;
import com.ignfab.minalac.generator.inputs.XYZTilesProvider;
import com.ignfab.minalac.generator.parameters.processors.ProcessorParams;
import com.ignfab.minalac.generator.parameters.processors.TerrariumImageProcessorParams;

/**
* Parameters for generic XYZ tiles providers.
*/
public class TerrariumTilesProviderParams extends ProviderParams {
/**
* Tile URL carrying the {@code {z}}, {@code {x}} and {@code {y}} placeholders
* (required).
*/
public String url;

/**
* Zoom level to read (optional, default: 12).
*/
@JsonSetter(nulls = Nulls.SKIP)
public int zoom = 12;

/**
* Creates a new {@code TerrariumTilesProviderParams} with mandatory fields.
*
* @param url Tile URL with the {@code {z}}, {@code {x}} and {@code {y}} placeholders
*/
@ConstructorProperties({"url"})
public TerrariumTilesProviderParams(String url) {
this.url = url;
}

@Override
public Provider<BufferedImage> create(Generation generation) {
if (url == null || url.isBlank())
throw new IllegalArgumentException("An XYZ tiles provider needs a url");
if (!url.contains("{z}") || !url.contains("{x}") || !url.contains("{y}"))
throw new IllegalArgumentException("The url must carry the {z}, {x} and {y} placeholders, got \"%s\"".formatted(url));

return new XYZTilesProvider(url, zoom, generation::getEnvelopeForCRS);
}

@Override
public ProcessorParams defaultProcessor() {
return new TerrariumImageProcessorParams();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.ignfab.minalac.generator.processors;

import java.awt.image.BufferedImage;

import com.ignfab.minalac.generator.exceptions.GenerationFailedException;
import com.ignfab.minalac.generator.exceptions.IgnorableException;
import com.ignfab.minalac.generator.inputs.FloatArrayGeographicDataMatrix2d;
import com.ignfab.minalac.generator.models.FloatMatrixModel;
import com.ignfab.minalac.generator.utils.coordinates.CoordsConverterProvider;

/**
* Processor transforming raw Terrarium images into a queryable elevation model.
*/
public class TerrariumImageProcessor extends ConvertingProcessor<BufferedImage, FloatMatrixModel> {
// private MapToWorldConverter converter;

public TerrariumImageProcessor(CoordsConverterProvider converterFactory) {

Check failure on line 17 in src/main/java/com/ignfab/minalac/generator/processors/TerrariumImageProcessor.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/processors/TerrariumImageProcessor.java#L17 <com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck>

Missing a Javadoc comment.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/processors/TerrariumImageProcessor.java:17:5: error: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
super(converterFactory);
}

@Override
public Class<BufferedImage> acceptedType() {
return BufferedImage.class;
}

@Override
public Class<FloatMatrixModel> modelType() {
return FloatMatrixModel.class;
}

@Override
public FloatMatrixModel process(BufferedImage image) throws GenerationFailedException, IgnorableException {
if (image == null)
throw new IgnorableException("Image is null, ignoring this element");

int width = image.getWidth();
int height = image.getHeight();

FloatArrayGeographicDataMatrix2d matrix = new FloatArrayGeographicDataMatrix2d(
width, height, 0, 0, 1.0, 1.0
);

Check failure on line 42 in src/main/java/com/ignfab/minalac/generator/processors/TerrariumImageProcessor.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/processors/TerrariumImageProcessor.java#L42 <com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck>

Line has trailing spaces.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/processors/TerrariumImageProcessor.java:42:0: error: Line has trailing spaces. (com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck)
float[] data = matrix.data();
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++) {
int rgb = image.getRGB(x, y);
float elevation = ((rgb >> 16) & 0xFF) * 256f + ((rgb >> 8) & 0xFF) + (rgb & 0xFF) / 256f - 32768f;
data[y * width + x] = elevation;
}

try {
return new FloatMatrixModel(matrix, converter);
} catch (Exception e) {
throw new GenerationFailedException("Failed to create FloatMatrixModel", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@ public PopulateHeightmapTask(ModelSelection selection, WritableHeightmapSpec hei
protected void run(FloatMatrixModel model, GenerationTile tile) {
WritableHeightmap heightmap = tile.heightmap(heightmapSpec);
WorldBBox2d intersection = tile.limits().to2d().intersection(heightmap.bbox());
System.out.println(model);
// Iterate over matrix and fill heightmap altitude
for (Matrix2d.Value<Float> value : model) {
System.out.println("Processing value at coordinates: " + value.coords() + " with elevation: " + value.value());
WorldCoords2d c = value.coords();
if (intersection.contains(c))
heightmap.set(c, (int) Math.round(value.value() / verticalScale));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ignfab.minalac.generator.utils.coordinates;

public final class WebMercatorUtils {

Check failure on line 3 in src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java#L3 <com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocTypeCheck>

Missing a Javadoc comment.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java:3:1: error: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocTypeCheck)
private static final double MAX_LATITUDE = 85.0511287798066;

private WebMercatorUtils() {}

public static double fractionalX(double longitude, int zoom) {

Check failure on line 8 in src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java#L8 <com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck>

Missing a Javadoc comment.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java:8:5: error: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
return (longitude + 180.0) / 360.0 * (1 << zoom);
}

public static double fractionalY(double latitude, int zoom) {

Check failure on line 12 in src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java#L12 <com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck>

Missing a Javadoc comment.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java:12:5: error: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
double radians = Math.toRadians(Math.max(-MAX_LATITUDE, Math.min(MAX_LATITUDE, latitude)));
return (1.0 - Math.log(Math.tan(Math.PI / 4.0 + radians / 2.0)) / Math.PI) / 2.0 * (1 << zoom);
}

public static int clamp(int index, int zoom) {

Check failure on line 17 in src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java

View workflow job for this annotation

GitHub Actions / checkstyle

[checkstyle] src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java#L17 <com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck>

Missing a Javadoc comment.
Raw output
/github/workspace/./src/main/java/com/ignfab/minalac/generator/utils/coordinates/WebMercatorUtils.java:17:5: error: Missing a Javadoc comment. (com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck)
return Math.min((1 << zoom) - 1, Math.max(0, index));
}
}
Loading