Heatmaps in Cesium

Hey Guys,

I’ve been looking for a library that adds heatmaps to Cesium and found that there aren’t any. So I decided to create one myself: CesiumHeatmap on GitHub.

I decided to use heatmap.js as it’s easy to use and fairly lightweight. A heatmap is created by calling CesiumHeatmap.create() and adding data to the returned instance.

Take a look and improve it if you want because it’s far from perfect. Currently the main issue I have with it is that the resulting heatmaps on the globe don’t fit well when the area (bounding box) is very big (for example the entire EU).

Regards

Manuel

Does it look fine when you are viewing the heatmap in 2D mode? I’m wondering if it’s a projection issue.

No, it looks the same.

can you post some code samples to show what the problem is exactly

Take a look at the GitHub Repository, it’s not that much code and it’s not very complicated. This is what it looks like:

The heatmap should be positioned on top of the circles, but for some reason it’s positioned way south of them (the lines connect the positions that should be lined up). The further north or south you go the closer the heatmap is to the expected position (the circles). This leads me to believe something is wrong with the way Cesium streches it out over the rectangle and/or the way I draw it.

This heatmap spans The Netherlands, Belgium, Germany, Luxembourg and France with this bounding box: {north: 54.86127, south: 42.3316, east: 14.99133, west: -4.579688}. The area you are looking at in the image is the westernmost part of France.

Hi,

SingleTimeImageryProvider uses the GeographicTilingScheme by default, not WebMercatorTilingScheme. Either pass an instance of WebMercatorTilingScheme with appropriate “rectangleSouthwestInMeters” and “rectangleNortheastInMeters” properties to the SingleTileImageryProvider constructor, or change your heatmap generator to use the geographic projection.

Kevin

Hey,

Changing the Tiling Scheme to WebMercatorTilingScheme seems like it would be the correct way to do this, but looking at the documentation for this class there doesn’t seem to be a way for this to be passed on to the SingleTimeImageryProvider. It just states "The single image is assumed to use a GeographicTilingScheme". Do you know of a way I should do this?

Manuel

You’re right, SingleTimeImageryProvider doesn’t seem to support that. I don’t see any good reason for that, though. It should be easy to create a fork of it that does the right thing, or perhaps even monkey patch it by overwriting the _tilingScheme property after it’s constructed.

This works perfectly, thank you so much! :slight_smile:

Now to find out a way to use the heatmap as a Entity’s material as mramato suggested in the GitHub issues thread. If you have any insight please do tell.

I’m still not quite happy with the performance, but it works well enough for anyone interested to check it out on GitHub.

Have you added any profiling code to figure out where the bottleneck is?

I have checked and 95-99% of the time is spent on the setData call (line 2 of CHInstance.prototype.setData) and the adding of the layer itself (line 6 of CHInstance.prototype.updateLayer). Both of these calls are handled by outside code (setData in heatmap.js and updateLayer in Cesium.js).

For the update layer does the browser loading timeline show where the bottleneck is? The Cesium imagery providers are generally very fast at getting the provider bootstrapped and ready to request tiles.

It looks like a lot of time is spent on this: instance._heatmap.getDataURL() (line 2 of CesiumHeatmap._getImageryProvider), which is the heatmapInstance.getDataURL() call from the heatmap library.

You shouldn’t need to call getDataURL, just pass the canvas directly.

When I do that I get the following error:

Cesium.js:21202 : An error occurred in “”: Failed to load image [object HTMLCanvasElement].

I’ve tried:

url: instance._container.childNodes[0]

url: instance._container.children[0]
url: instance._heatmap._renderer.canvas

url: instance._heatmap._renderer.ctx

The bottom one points to the CanvasRenderingContext2D the rest points to the canvas on which the heatmap is drawn. This canvas is hidden by way of setting ‘display: none’, but removing that doesn’t seem to make a difference.

I’m not quite sure how to pass on a canvas in stead of a url to this function.

Is that error when using the Rectangle.material? Or a SingleTileImageryProvider? I expect it to work with the former, I’m not sure about the latter.

SingleTileImageryProvider needs a URL - not a Canvas - but imagery providers in general can return a Canvas from requestImage. You’re probably getting to the point where you should write your own ImageryProvider instead of trying to use SingleTileImageryProvider. If you want to continue monkey patching, though, do this:

  1. Construct the SingleTileImageryProvider with an invalid URL.

  2. Set its _image property to your Canvas. Also set _tileWidth and _tileHeight to the dimensions of your canvas.

Hacktastic, but it should work.

Writing an imagery provider is pretty easy. If this works for you, you should do that.

I don’t want to discourage you from using a Rectangle.material as Matt has been suggesting, though. It might be a better choice.

Kevin

That error is from using it with SingleTileImageryProvider.url.

It kind of works when I use Rectangle.material, but there are some issues:

  • I’m back to using the WGS84 view instead of Mercator (that’s what I set on line 12 of CesiumHeatmap._getImageryProvider) which distorts the heatmap to not fit (see my third message in this thread).

  • The Entity also seems to only use the first heatmap canvas it can find, in stead of using the one I supply to it. Maybe it’s getting the image by doing a getElementsByClassName() and using the first one it finds?

I’ve actually been thinking about creating my own ImageryProvider. I’ll look into this if the Entity option doesn’t pan out.