Skip to content

XYZ Tiles provider - #209

Open
naulan-chrzaszcz wants to merge 1 commit into
mainfrom
naulan/terrarium-provider
Open

naulan-chrzaszcz wants to merge 1 commit into
mainfrom
naulan/terrarium-provider

Conversation

@naulan-chrzaszcz

@naulan-chrzaszcz naulan-chrzaszcz commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Changes

Add a new provider which handle a new data source which provide images given the elevations with special encoding and with XYZ Tiles endpoint.

Technical changes

Add a new provider XYZTilesProvider with her parameters, which download images and store it into a new FloatBufferedImageMatrix model which can handle many format to get elevations.

This model can only supported (for now) the terrarium format handle by TerrariumElevationDecoder class.

Showcase

image

Reason

For Albanian and world wide DTM rendering.

TODOs

  • Documentation
  • Unit tests

Self-checks

  • The code has unit tests associated
  • The code has Javadoc Comments associated
  • Complex / Unexpected code is explained / justified with a small comment
  • Relevant documentation inside the /docs folder has been updated
  • All examples in examples/ work the same (or have been adapted if subject to changes in this PR)
  • Git history is clean (each commit accomplish a single task and describe it accordingly)
  • The texts have been proofread (documentation, error messages, logs, comments...)

@naulan-chrzaszcz

Copy link
Copy Markdown
Member Author

This feature is pretty raw; it was a prototype I quickly put together while I was in Albania, using the most powerful Claude Code model.
I need to make it less obscure and understand what it actually generated for me.

@naulan-chrzaszcz naulan-chrzaszcz added type: enhancement New feature or request size: medium Neither small nor big change, will require some time to review On hold Todo later labels Sep 24, 2026
@naulan-chrzaszcz naulan-chrzaszcz self-assigned this Sep 24, 2026
@indyteo

indyteo commented Sep 24, 2026

Copy link
Copy Markdown
Member

I did not read the code in details, but just from a quick overview, it seems like you can quite drastically simplify it, especially by removing the re-projection.

In the WMS provider, we have a CRS in the params because we need to tell to the GeoServer in which CRS we want our data. It is intended that the user inputs either the native data CRS (so the server don't reproject and our MapToWorldConverter does), or the generated map CRS (so the server reprojects and our converter don't).
In the Overpass provider, we don't have a CRS in the params because OSM data is ALWAYS in EPSG:4326. Our MapToWorldConverter will handle the re-projection as long as the correct CRS is indicated in the Provider.Result#crs() method.

In your case, it seems like the data will always be in EPSG:4326. So, just like the Overpass provider, don't worry about any re-projection yourself, and let the MapToWorldConverter do it. I guess you started from the WMS provider, which explains why you had this CRS and thus struggled to integrate it.

Additionally, keep in mind that it is completely OK for a provider to return multiple results. Even with matrices, it looks like you could return one matrix per tile instead of having to handle the merging yourself. Claude might be good at writing working code, but it's up to you to integrate it properly into the codebase (because it doesn't know about everything that is already handled by another component). If you were to write such code by hand, would you have really implemented re-projection and tile merging yourself? Probably not, because you would have known that it was tedious to do and also unnecessary ;)

@pyrollo

pyrollo commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

This feature is interesting.

You should not limit it to Terrarium, many other data are provided using XYZ tiles.

And as said Theo, this could be very simplified and made more Voxatile-like:

  • separate Provider logic from data storage logic (keep data in BufferedImage);
  • remove useless (and memory consuming) conversion to FloatGeographicDataMatrix2d, prefer keeping data in BufferedImage rather (this is the policy we had from the beginning of the project);
  • avoid useless CRS transformations

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

[Maven Build Status]

📑 Commit: ca8e6b28643bc24de63f254b1d086f2b88bb7cd8
⌚️ Date: 2026-10-09T17:11:48 (CEST)
🛠️ Status: ✅ Success

📦 Download artifact: voxatile.jar

@naulan-chrzaszcz
naulan-chrzaszcz force-pushed the naulan/terrarium-provider branch 3 times, most recently from f0860db to 95bae7f Compare October 8, 2026 15:23
@naulan-chrzaszcz naulan-chrzaszcz changed the title Terrarium tiles provider XYZ Tiles provider Oct 8, 2026
@naulan-chrzaszcz
naulan-chrzaszcz force-pushed the naulan/terrarium-provider branch 2 times, most recently from 5f62b48 to ec221a7 Compare October 8, 2026 15:28
Comment thread src/main/java/fr/ign/voxatile/core/inputs/FloatBufferedImageMatrix.java Outdated
Comment thread src/main/java/fr/ign/voxatile/core/inputs/FloatBufferedImageMatrix.java Outdated
double maxY = data.offsetY() + data.sizeY() * data.cellSizeY() - 1.0;
double maxX = data.offsetX() + (data.sizeX() - 1) * data.cellSizeX();
double maxY = data.offsetY() + (data.sizeY() - 1) * data.cellSizeY();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hotfix for WG84 CRS

Comment on lines +66 to +67
float x = (float) ((coordinates.x() - data.offsetX()) / data.cellSizeX());
float y = (float) ((coordinates.y() - data.offsetY()) / data.cellSizeY());

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hotfix for WG84 CRS

Comment thread pom.xml
Comment on lines +203 to +208
<!-- TwelveMonkeys library for WebP support in ImageIO -->
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-webp</artifactId>
<version>3.9.4</version>
</dependency>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new endpoint of Mapterhorn use WebP.

@naulan-chrzaszcz naulan-chrzaszcz removed the On hold Todo later label Oct 8, 2026
@naulan-chrzaszcz
naulan-chrzaszcz marked this pull request as ready for review October 8, 2026 15:43
@naulan-chrzaszcz

Copy link
Copy Markdown
Member Author

Feel free to give some reviews if you'd like, @ignfab/minalac
But the code can change fastly.

/**
* Interface for decoding RGB values to float elevation.
*/
public interface ElevationDecoder {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pourquoi pas plus généralement "FloatDecoder" ?

Et puis pourquoi ne pas utiliser une IntToDoubleFunction ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... et surtout pourquoi répondre aux questions par du code ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 Qu'est-ce que je peux dire ? Oui, je suis d'accord.

Ça fait une interface en moins... Je ne peux pas défendre ma version s'il y a évidemment mieux.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu aurais pu répondre "parce que je veux du Float et pas du Double"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Oui, je suis d'accord.
Mais, c'est plus générique ainsi.

@naulan-chrzaszcz
naulan-chrzaszcz force-pushed the naulan/terrarium-provider branch from ec221a7 to e2c12fe Compare October 9, 2026 13:41
@naulan-chrzaszcz

naulan-chrzaszcz commented Oct 9, 2026 •

Copy link
Copy Markdown
Member Author
image Est-ce que les platistes auraient raison, finalement ? P'tit problème de rendu...

Comment thread src/main/java/fr/ign/voxatile/core/inputs/XYZTilesProvider.java Outdated
@naulan-chrzaszcz
naulan-chrzaszcz force-pushed the naulan/terrarium-provider branch from e2c12fe to 67fdc42 Compare October 9, 2026 13:53
Comment on lines +22 to +25
/**
* Zoom level for the tiles (required).
*/
public int zoom;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have any usecase for that "zoom" parameter ?
In what case would we need to set a zoom value that does not fit our voxel size ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated 🚀

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Zoom field is only used to defined details of the data. I explained in my JavaDoc

@pyrollo pyrollo Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That explains what "zoom" is (which is quite obvious) but not why would I choose a zoom value that does not fit voxel size.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je te la refais en français:

Pourquoi pourrait on choisir le niveau de zoom ? Dans quel cas cela peut être utile de choisir un autre niveau de zoom que celui qui est adapté pour le rendu ?

Je ne vois pas de cas d'usage.

Comment thread src/main/java/fr/ign/voxatile/core/inputs/XYZTilesProvider.java Outdated
@naulan-chrzaszcz
naulan-chrzaszcz force-pushed the naulan/terrarium-provider branch from 67fdc42 to ca8e6b2 Compare October 9, 2026 15:11

**Extra parameters**:
- `url` (required): URL containing the `{z}`, `{x}`, and `{y}` placeholders. Example : `https://tiles.mapterhorn.com/{z}/{x}/{y}.webp`
- `zoom` (required): Zoom level for the tiles (must be a non-negative integer)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we have to specify that ? What zoom value should I put ?

Voxatile should know better than me what zoom to use.

@indyteo

indyteo commented Oct 12, 2026

Copy link
Copy Markdown
Member

Okay, this is promising. Let's see some high-level considerations first.

So, after reading your code, here is how I see it:
You have a provider dedicated to XYZ tiles querying, crafting requests from a user-defined URL that implies the tiles format (PNG, JPEG, WebP...). This format does not matter, as long as Java can read it into a standard buffered image (which is nice, because it was designed to be extensible, meaning that additional formats can be supported, as shown with WebP).
Because each tile needs to be independent, you must wrap that buffered image into a geographic data matrix. Because you are working with altitude data, you made a new implementation of float geographic data matrix, based on a buffered image. But a buffered image typically embeds integers, not floats, so you added an integer to float function to convert from one into another.
Then, you added a single implementation based on the Terrarium encoding algorithm.
At this point, you are done since those float geographic data matrices can be processed by the already existing float matrix processor.

First of all, is that correct? Let me know if I missed something important.

Now, when I think about this topic, I see a different approach:
We make an XYZ tiles provider similar to yours, but instead providing integer geographic data matrices (new implementation, will probably be required anyway at some point to handle color images as well), thus without any conversion needed.
Then, we create a new processor, dedicated to Terrarium, which create float matrix models from those integer geographic data matrices (with the good implementation, no data copy nor extensive code duplication is needed).
It would then work the same because that processor would've created float matrix models.

So, now I have a few questions for you:

  1. Do you understand the approach I'm talking about?
  2. What do you think of your current approach? What are the differences with this one?
  3. What are the pros and cons of the two, and ultimately, which one do you think would be the best?

Please note that I'm asking you to write a message with your answers, not to code anything right now!
And obviously, I'm asking you to think about it, as a "design exercise", I'm not prompting Claude to do it 😉

the formulas below are based on the Slippy map tilenames convention
https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Common_programming_languages
*/
private double tile2lon(int x, int z) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private double tile2lon(int x, int z) {
private double xToLongitude(int x, int z) {

return x * 360.0 / (1 << z) - 180.0;
}

private int lon2tileX(double lon, int z) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private int lon2tileX(double lon, int z) {
private int longitudeToX(double lon, int z) {

/**
* Creates a new {@code XYZTilesProvider}.
*
* @param url URL with {@code {z}}, {@code {x}}, and {@code {y}} placeholders

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param url URL with {@code {z}}, {@code {x}}, and {@code {y}} placeholders
* @param url URL with {@code {x}}, {@code {y}}, and {@code {z}} placeholders

*/
public class XYZTilesProviderParams extends ProviderParams {
/**
* URL carrying the {@code {z}}, {@code {x}} and {@code {y}} placeholders (required).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* URL carrying the {@code {z}}, {@code {x}} and {@code {y}} placeholders (required).
* URL carrying the {@code {x}}, {@code {y}} and {@code {z}} placeholders (required).

This wacky order is everywhere ...

/**
* Constructs a new {@link XYZTilesProviderParams} with mandatory fields.
*
* @param url URL carrying the {@code {z}}, {@code {x}} and {@code {y}} placeholders

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order

@pyrollo

pyrollo commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Please note that I'm asking you to write a message with your answers, not to code anything right now!

That's important to say :D

Your proposal is interesting (and promising). But before that, I think there is a serious issue with zoom that should be fixed.

the formulas below are based on the Slippy map tilenames convention
https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Common_programming_languages
*/
private double tile2lon(int x, int z) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tile2lon, lon2tileX, tile2lat, lat2tileY are actually static. But that's too bad.
If you pay attention at z value passed to them, you will discover that you can remove this argument (and keep these methods non static).


List<FloatGeographicDataMatrix2d> tiles = new ArrayList<>();
for (int y = minY; y <= maxY; y++)
for (int x = minX; x <= maxX; x++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will your code behave in Fidji Islands ?
What will occur around 180° longitude ?

@indyteo

indyteo commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

But before that, I think there is a serious issue with zoom that should be fixed.

Indeed, this is completely unrelated. Whatever the global design is, the same issues about the actual XYZ providing logic will need to be considered, such as the zoom parameter you mentioned, or the tiles streaming instead of buffering... but those can be handled separately, either before, in parallel, or after.

Considering I (only) asked for critical thinking and analysis, I think it can be performed in parallel to addressing initial code reviews, yes!

@indyteo

indyteo commented Oct 13, 2026

Copy link
Copy Markdown
Member

Just a side note regarding the analysis I asked you, if it's easier for you, I don't mind if you do it in French. Doing it in English would be a good exercise as well, but I prefer you focus on quality of content rather than language.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: medium Neither small nor big change, will require some time to review type: enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants