Skip to content

Conversation

SoftwareMechanic
Copy link
Contributor

added geojson support as overlay and tilesplugin

@SoftwareMechanic SoftwareMechanic changed the title Feature/geojson Add GeoJSON format support Sep 5, 2025
@gkjohnson gkjohnson linked an issue Sep 5, 2025 that may be closed by this pull request
@gkjohnson gkjohnson added this to the v0.4.16 milestone Sep 9, 2025
Copy link
Contributor

@gkjohnson gkjohnson left a comment

Choose a reason for hiding this comment

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

This is great to see - and a really nice approach to overlaying and generating it.

Can you explain your use case a bit? Are you planning to have multiple polygons with multiple colors? One potential issue I see is that multiple colored geojson data sets will require separate layers of images which could cause a bit of an explosion of memory.

One thought I had is that the geojson could be converted to SVG and then you'd have all the flexibility of SVG scalability and CSS styling. This geojson2svg package supports Geojson > SVG conversion. Who know's how the performance may be affected, though 🤔 Something to explore in the future.

edit And sorry for the commit rework. I wanted to remove the dependency on #1302 so we can get this merged separately if needed.

'https://www.gstatic.com/draco/v1/decoders/',
);

const geojson = {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the source for this polygon data?

Copy link
Contributor Author

@SoftwareMechanic SoftwareMechanic Sep 9, 2025

Choose a reason for hiding this comment

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

bacino_varenna_aggregato4326.json
I have this local file geojson (in order to load it here on Github, I had to change the extension from .geojson to .json), I wanted to try to have both an url or a geojson object.
the reasons for geojson object was because I wanted to start thinking about a "drawPlugin",
but also because maybe the front end developer could handle a conversion of it to the accepted EPSG,
in any case if just URL is provided, we should make sure that the geojson is defined in supported EPSG codes looking at 'crs.properties.name' of the object.

what do you think about this?
we can just keep the url if you prefer, to make it coherent with the library


const geojsonOverlay = new GeoJSONTilesOverlay( {

levels: 14, // choose max zoom to rasterize overlay ( 12- 14 typical )
Copy link
Contributor

@gkjohnson gkjohnson Sep 9, 2025

Choose a reason for hiding this comment

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

This is an interesting new case - this is a good solution for now. It may be worth adjusting overlays so that they can load up to whatever maximum resolution the underlying tile set is at in order to more easily support these kinds of dynamically generated / flexible types of data sets.

edit created #1310

Copy link
Contributor Author

Choose a reason for hiding this comment

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

absolutely agree, I think that somehow we should maybe have access to the levels defined for the plugin the geojson is onto in order to manage that dynamically.
or hardcode an high number of levels if the prop is not provided?

@SoftwareMechanic
Copy link
Contributor Author

SoftwareMechanic commented Sep 9, 2025

This is great to see - and a really nice approach to overlaying and generating it.
thank you so much, I appreciate.

Can you explain your use case a bit?

In my use case, for the project I'm working on, I'd like the user to upload any geojson for a specific project (in supported EPSG, of course; the platform I'm working on could eventually convert them to the desired EPSG in the future), and these geojsons should be overlaid on a globe/basemap (photorealistic tiles, cesium globe, or whatever "base layer" the user wishes). One of the purposes of the geojson is to highlight, for example, the valley affected by a specific infrastructure (such as a bridge), so that the user can not only better see the area for whatever reason, but also understand, for example, whether a heavy rainfall (provided by a WMS) is within the area of ​​interest or not, perhaps causing a flood, and so on.

Are you planning to have multiple polygons with multiple colors? One potential issue I see is that multiple colored geojson data sets will require separate layers of images which could cause a bit of an explosion of memory.

that would be possible, I didn't think about it honestly, we could have colors for each feature, similarly to what is possible to do with your geojson library for three.js.

about having multiple datasets, yes I understand that drawing on multiple canvas can be heavy if multiple data sets are there, in that case, I would consider, if possible, to have only one data set with multiple datasets, in order to create and draw using only one canvas per time, maybe having an Api from GeoJSONTilesOverlay/Plugin, to add a dataset and manage the "merge" of datasets behind the scene, or I would consider to test your suggestion about SVG.
if the SVG solution would work and is more performant, I'd keep that solution as the definitive, and even more simple maybe.

honestly I already made some tests with SVG but I have to dig more, I will consider that converter you linked me, or drawing them directly as I did in my last commit.

I think that the advantage you see in using SVG is to don't have to worry about levels and redrawing images, in order to gain performance, am I right? I agree with that eventually 🚀 , but with current implementation svgs are created whenever it would create other images format, eventually we should keep track of the memory and cpu usage to see what is the best approach, and how to optimize it.

@gkjohnson
Copy link
Contributor

in supported EPSG, of course; the platform I'm working on could eventually convert them to the desired EPSG in the future

As far as I understand, GeoJSON does not support custom CRS. The values should always be in latitude / longitude which will be universal. To that end the "GeoJSONImageSource" shouldn't need a custom CRS other than to adjust the implicit tiling scheme. Though I think that's probably not needed for this.

that would be possible, I didn't think about it honestly, we could have colors for each feature

For reference it looks like Mapbox provides a custom styling system for things like this but it would be nice to avoid adding an overly complex styling system.

I think that the advantage you see in using SVG is to don't have to worry about levels and redrawing images, in order to gain performance, am I right?

Unfortunately in order to render SVG in WebGL (or WebGPU afaik) you still have to rasterize the SVG data to a texture, so you will still have to deal with levels and the texture generation. But SVG is a good standard for these kinds of vector images and supports declarative styling out of the box rather than requiring a custom one. Once concern I have is that this will be slower, though, because of the complexity of the styles, but it will require some testing.

Merge branch 'feature/geojson' of https://github.com/SoftwareMechanic/3DTilesRendererJS into feature/geojson

It looks like you've pulled and merged the cherry picked and rebased version of the branch I made into your local version, which has brought back all the styling changes I fixed. I can force push my version of the branch again but you'll need to pull it and overwrite your local version. Alternatively we can wait until #1302 is merged and you can fix up the styling issues in this branch then. The diff is otherwise too complex to understand now, unfortunately. Let me know what you'd like to do!

@SoftwareMechanic
Copy link
Contributor Author

As far as I understand, GeoJSON does not support custom CRS. The values should always be in latitude / longitude which will be universal. To that end the "GeoJSONImageSource" shouldn't need a custom CRS other than to adjust the implicit tiling scheme. Though I think that's probably not needed for this.

Once it happened that I received a .geojson file in EPSG:3003, but that was in fact an exception, and not even the Cesium Viewer can render it by default, so I agree to treat them as latitude longitude coordinates, maybe we can just write a line in the docs or in a comment in the example about this

For reference it looks like Mapbox provides a custom styling system for things like this but it would be nice to avoid adding an overly complex styling system.

I agree, I would not add another 'rendering' layer, not for now at least. the more is simple the better it is imo.

Unfortunately in order to render SVG in WebGL (or WebGPU afaik) you still have to rasterize the SVG data to a texture, so you will still have to deal with levels and the texture generation. But SVG is a good standard for these kinds of vector images and supports declarative styling out of the box rather than requiring a custom one. Once concern I have is that this will be slower, though, because of the complexity of the styles, but it will require some testing.

that was in fact my doubt about webGL, but I agree that using SVG is better for drawing and styling, and can lead us to have a good and modular management of the styling eventually.

It looks like you've pulled and merged the cherry picked and rebased version of the branch I made into your local version, which has brought back all the styling changes I fixed. I can force push my version of the branch again but you'll need to pull it and overwrite your local version. Alternatively we can wait until #1302 is merged and you can fix up the styling issues in this branch then. The diff is otherwise too complex to understand now, unfortunately. Let me know what you'd like to do!

🙈 I am sorry I messed up with GH desktop.
you can forse push and I will overwrite my local version, my bad!

Copy link
Contributor

@gkjohnson gkjohnson left a comment

Choose a reason for hiding this comment

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

Added some initial comments. I'll take a look at the drawing code later this week.

geojson = null,
url = null, // URL or GeoJson object can be provided
tileDimension = 256,
bounds = [ - 180, - 90, 180, 90 ],
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of passing "bounds" into the image source it would be best to calculate the bounds of the geojson object so it's a tight bounds (including some margin to account for stroke thickness, etc.

Copy link
Contributor Author

@SoftwareMechanic SoftwareMechanic Sep 10, 2025

Choose a reason for hiding this comment

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

for the moment in this new commit I will set the bounds like for other image sources using the crs bounds, but if using the bounds limited to the entity can optimize the process, I would like to implement this, even in #1302 , a lot of WMS services like geojson use only a small portion of the map to display data in there.

Copy link
Contributor

Choose a reason for hiding this comment

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

if using the bounds limited to the entity can optimize the process, I would like to implement this

Calling TilingScheme.setContentBounds tells the tiling instance that any tiles that do not include that content bounds in its range is non existent and should not be loaded. This prevents unnecessary data loading and prevents render targets for overlays from being generated - saving memory and performance.

even in #1302 , a lot of WMS services like geojson use only a small portion of the map to display data in there.

I'd added this in some of the WMSImageSource updates - the constructor takes a contentBounds field (which can be derived from the EX_GeographicBoundingBox in the WMS capabilities XML) and is used to set the content bounds of tiling here (with the full CRS range used as a fallback).

Comment on lines +57 to +68
const res = await this.fetchData( this.url );
// fetchData should return a Response-like object
// allow both Response and plain object (in case overlay fetch returns parsed JSON)
if ( res && typeof res.json === 'function' ) {

this.geojson = await res.json();

} else if ( res ) {

this.geojson = res;

}
Copy link
Contributor

Choose a reason for hiding this comment

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

What case are you imagining where fetchData will return pre-parsed JSON? I think it should be okay to assume this is performing a typical fetch in this case.

); // [ minx,miny,maxx,maxy ] normalized

// compute tile extent in projection units ( degrees for 4326, meters for 3857 )
const tileInfo = this._tileExtentFromNormalized( boundsNormalized );
Copy link
Contributor

Choose a reason for hiding this comment

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

You can get the lat / lon for the tile using the getTileBounds and passing normalized = false.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried doing that, but logging the result I get different values from getTileBounds not normalized and from _tileExtentFromNormalized, and the drawing will not work anymore.

Copy link
Contributor

Choose a reason for hiding this comment

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

Lets start by setting all the CRSs to something that's easier to follow. Right now the "ProjectionScheme" is being set to EPSG:3857, which isn't right since the GeoJSON data just specifies WGS84 lat / lon values and these are just being linearly interpolated to convert to canvas coordinates. These interpretations of the data need to be the same.

EPSG:3857 is the "Web Mercator" CRS, which has a nonlinear projection distortion along the latitude axis. That means that while latitude and longitude will correspond to the same spots on a globe they will be at different points on the map - meaning the "normalized values" bounding the tile sections will be different EPSG:3857 and EPSG:4326. It's possible to use the web mercator projection here but generally it's a bit easier to work with EPSG:4326 and either way the important part is being consistent.


// find features that ( quick bbox ) intersect tile
const features =
this.geojson && this.geojson.features ? this.geojson.features : [ ];
Copy link
Contributor

Choose a reason for hiding this comment

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

There's no guarantee that a geojson object has a root "features" object unless it's a "FeatureCollection". The GeoJSON root can otherwise be any of the GeoJSON geometry or feature types so we'll need to handle this more generically, as well.

Comment on lines +37 to +48
for ( let i = 0; i < this.levels; i ++ ) {

const tilesX = 2 ** i;
const tilesY = 2 ** i;
this.tiling.setLevel( i, {
tilePixelWidth: this.tileDimension,
tilePixelHeight: this.tileDimension,
tileCountX: tilesX,
tileCountY: tilesY,
} );

}
Copy link
Contributor

Choose a reason for hiding this comment

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

We can just use the generateLevels function here, which will generate a basic set of quad-split tiles down to the desired level:

tiling.generateLevels(
  levels,
  projection.tileCountX,
  projection.tileCountY,
  {
    tilePixelWidth: tileDimension,
    tilePixelHeight: tileDimension,
  },
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Support WMS Tiled Image Format and GeoJSON
2 participants