Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents

Intro

By default, Cesium cannot generate heatmaps, so that third-party solutions must be used to implement this functionality in the viewer.

There is a library called heatmap.js that calculates a represents heatmaps from different input types, such as mouse position on the screen, where you click or even coordinates in the browser's own canvas.Image Removed

...

From this library, and from the idea of representing points in the canvas, a different library called CesiumHeatmap.js was developed. This one allows you to generate heatmaps in the viewer itself, from point coordinates and a value representing a point's weight.

At the end of the tutorial, using an ontology of points, you can generate a heatmap like this one:

Iframe
srchttps://www.onesaitplatform.online/web/wp_cesium_heatmap_example/index.html
width600
frameborderhide
styledisplay: block; margin-left: auto; margin-right: auto;
alignmiddle
titleEjemplo de mapa de calor en Cesium
height450
Info

Eh! This is no photo, but an interactive map.  Try to browse through it.


Library installation

The first step will be download and uncompressing the library, or cloning the repository (https://github.com/manuelnas/CesiumHeatmap.git) in the project folder containing the viewer.Image Removed

...

The downloaded/cloned file has the following files:

  • CesiumHeatmap.js: the library to aggregate heatmaps using heatmap.js (included) and the functions Cesium.Entity.Rectangle or Cesium.SingleTileImageryProvider, the later will be used in this tutorial.

  • HeatmapImageryProvider.js: another library to aggregate heatmaps, also using heatmap.js (also including it) through customization of the Cesium.ImageryProvider function, which will not be studied for the moment.

  • LICENSE: a generic document with the libraries's copyright.

  • package.json: file with the repository's address.

  • README.md: file with the libraries0 description, along with an example of use of CesiumHeatmap.js.

To use the library in the Cesium viewer, you must insert the CesiumHeatmap.js library after the Cesium.js library call, as shown next:

...

...

Insert CesiumHeatmap.js library
Code Block
linenumberslanguagetruexml
<script src="https://www.onesaitplatform.online/web/Cesium160/Cesium.js"></script>
<script src="js/heatmap/CesiumHeatmap.js"></script>
<style>
  @import url(https://www.onesaitplatform.online/web/Cesium160/Widgets/widgets.css);

Having done this, you can start generating heatmaps.

Functioning philosophy

To generate a heatmap, you need a series of point-type entities (It does not work with lines or polygons), with defined geospace coordinates (usually longitude and latitude), and a field including each point's value or weight respect to the other points.

For this tutorial, we created a series of points, each of them with a value. These points, represented on a map along with their values, are seen in the following way:Image Removed

...

The coordinates and values of all these points, which total 30, are stored in an ontology already loaded in the platform and served as a REST API. Each entity has the following structure:

Ontology entity example structure
Code Block
title
languagexml
themeEclipse
firstline1
Ontology entity example structure
{
  "type": "Feature",
  "properties": {
    "id": 2,
    "value": 22
  },
  "geometry": {
    "type": "Point",
    coordinates": [
      -15.432677385995223,
      28.138219879738312
    ]
  }
}

In this structure, we have defined fields such as longitude (geometry.coordinates[0]), latitude (geometry.coordinates[1]) and the value assigned to the point (properties.value).

Tip

...

heatmapSampleData

What? You don't know yet how you can generate an ontology and serve it as a REST API service? You're lucky, we explain it right here. And, if you want to try and create your own REST API for this example, here you have the JSON file: heatmapSampleData.json

Besides the points with values, the heatmap generation also requires you to define the study area, understanding it as a rectangle delimiting the points to be considered. Thus, for the point series in the tutorial, a possible bounding rectangle can be this one:Image Removed

...

Info

...

Bounding rectangle

This is the rectangle referenced when we say that this library uses the Cesium.Entity.Rectangle function.

We are not interested in the rectangle's geometries, but in the upper, lower, left and right limit coordinates. This, while easy to get if you use a GIS program, can be harder if you have to do it manually. We suggest you to take the "higher" point, the increase the value of its latitude coordinate (geometry.coordinates[1]), so the limit is a bit to its north. Repeat it for each of the sides to get the coordinates.


Another possibility, somewhat simpler but more technical, is drawing two points: One corresponding to the higher left corner, and the other to the lower right. These two point give you four coordinates, corresponding to the four corners in the diagram. See it in the following example:Image Removed

...

title
Tip

heatmapSampleData

Try drawing some point in geojson.io and you'll see its coordinates appearing in the JSON editor in the map's right. Remember the first coordinate is longitude (east-west) and the second one is latitude (north-south).


And that's it. With this idea in mind, generating heatmaps in Cesium is quite easy.

Library use (or how I can, at last, create a heatmap in Cesium)

Having seen the theory, we can start generating a heatmap like the one in the previous example.


Firstly we need to get the data of the point entities. These are in an ontology served by a REST service. To do this, we need to use AJAX, so in the webpage's <head>, you must include a call to a jQuery library:

Include the jQuery library
Code Block
languagexml
themeEclipse
firstline16
titleInclude the jQuery library
linenumberstrue
<script <script src="https://www.onesaitplatform.online/web/Cesium160/Cesium.js"></script>
<script src="js/heatmap/CesiumHeatmap.js"></script>
<script
  src="https://code.jquery.com/jquery-3.3.1.min.js"
  integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
  crossorigin="anonymous"
></script>

Next, in the Cesium viewer's Javascript code, you must call the REST service  using the following code:

...

...

Call the REST service using AJAX
linenumbers
Code Block
languagetruexml
jQuery.ajax({
	/** REST service URL */
	url:
		'https://www.onesaitplatform.online/api-manager/server/api/v1/api_tuto_cesium_heatmap/getPointFeatures',

	/** Header with token to access the API */
	headers: {
		'Content-Type': 'application/json',
		Accept: 'application/json',
		'X-OP-APIKey': 'user_token'

	/** API service type */
	type: 'GET',

	/** Content offered by the API; in this case, the ontology, it's in JSON format */
	contentType: 'application/json',

	/** If the token is valid, information is recovered */
	success: function(jsonData) {
		
	},
	/** If the token is not valid, launch error */
	error: function(data, errorThrown) {
		alert('A problem reading the ontology has been detected.')
	}
})

Warning

...

Ontology use

Bear in mind that the URL refers to the API generated for this example. If you want to generate a heatmap with your own data, you must create your own REST service and include its URL.

Warning

...

Personal token use

Remember to replace 'user_token' (line 10 in the previous code) with your personal token.

Tip

...

Code management

All the code from here onward will be in the function returning the jsonData (from line 20 in the previous code).

We will now define a variable containing an object with the previously-defined rectangle's corners:

...

Defining the rectangle's corners
linenumbers
Code Block
languagetruexml
/** Defining the study area's boundaries (corners)*/
let extent = {
  west: -15.4522,
  south: 28.1276,
  east: -15.417,
  north: 28.1534
}

We will also create another variable defining the heatmap's configuration properties. For now, we will specify the most basic ones; we will later give details on how to customize the heatmap.

...

Configuring the heatmap's properties
Code Block
linenumberslanguagetruexml
/** Defining heatmap's properties */
let heatmapSetup = {
  radius: 150,
  maxOpacity: 0.8,
  minOpacity: 0.2,
  blur: 0.75
}

Info

...

Configuration options

If you are interested, you can see all the generic customization options in heatmap.js's documentation.

Next, generate an instance of CesiumHeatmap using CesiumHeatmap.create(). This instance will be configured with three members:

  • The map's viewer: understood as the 'viewer' object that defines the viewer.

  • The interpolation limits: corresponding with the previously defined rectangle's limits.

  • A number of configuration properties: that we have just created.

So, a basic instance can be defined as:

Creating CesiumHeatmap instance
Code Block
languagexml
themeEclipse
firstline1
titleCreating CesiumHeatmap instance
linenumberstrue
/** Generating heatmap instance */
let heatMap = CesiumHeatmap.create(viewer, extent, heatmapSetup)

...

Now, you only need to add the data to the viewer, specifying the viewer's coordinate's format. There are two options:

  • setData(): for data in X & Y coordinates (UTM style).

  • setWGS84Data(): for data in longitude and latitude coordinates.

In both cases, the function gets input parameters:

  • Maximum and minimum interpolation value: for a range of given values, the more the maximum and minimum values are near the points' maximum and minimum values, the more the differentiation visual effect will be in the interpolation (the colors will be more extreme).

  • List of point entities: with their weight values.

For the example data, as they are defined with longitude and latitude coordinates, it will be something like:

...

Adding heatmap to viewer
linenumbers
Code Block
languagetruexml
/** Add heatmap to viewer, considering input data as longitude and latitude (WGS84) */
heatMap.setWGS84Data(15, 26, data)


In this case, 15 is the minimum value to be considered in the interpolation, 26 is the maximum value, and data are the values of the ontology points, following the format{ x: longitude, y: latitude, value: weight }.

To get those values, you can generate a function that iterates each element in the ontology, returning a list with the coordinates and weight values, and then gives this list to the heatmap generating function. In the case of the example ontology, the used function is this:

...

, the used function is this:

Function iterating the JSON and extracting the needed data
Code Block
linenumberslanguagetruexml
function getData(values) {
  /** List containing coordinates and values of each point */
  let data = []

  /** Iterate by every entity in the JSON */
  for (let feature of values.features) {
    /** Define fields: longitude, latitude and weight */
    let longitude = feature.geometry.coordinates[0]
    let latitude = feature.geometry.coordinates[1]
    let weight = feature.properties.value

    /** Add each field to the list */
    data.push({ x: longitude, y: latitude, value: weight })
  }

  /** Return point list */
  return data
}

...

Then, the function to add data looks like this, understanding as jsonData the object returning the function where the code is:

...

...

Add heatmap to viewer
linenumbers
Code Block
languagetruexml
/** Add heatmap to viewer, considering input data as longitude and latitude (WGS84) */
heatMapInstance.setWGS84Data(15, 26, getData(jsonData))

Having done that, when we view the map, the heatmap should look like this:

Iframe
srchttps://www.onesaitplatform.online/web/wp_cesium_heatmap_example/index.html
width600
frameborderhide
styledisplay: block; margin-left: auto; margin-right: auto;
alignmiddle
titleCesium heatmap example
height450
title
Tip

See bigger map

You want to see this map bigger? We've uploaded it to the Platform. Have a look and don't worry about the source code; it's at the end of this tutorial.

Heatmap configuration

The heatmap customization options are the following ones:

  • useEntitiesIfAvailable: specify whether entities will be used to generate the heatmap(true) or an image provider will always be used (false). The default value is true.

  • minCanvasSize: minimum size, in pixels, for the color map canvas. The default value is 700.

  • maxCanvasSize: maximum size, in pixels, for the color map canvas. The default value is 2000.

  • radius: each point's interpolation size. If the radius is not enough and does not touch another point, it gives a series of concentric circles.

  • radiusFactor: size factor used if no radius is specified. The value is the higher height and width, divided by this value to generate the radius value to be used. The default value is 60.

  • spacingFactor: additional space around the analysis area's borders. The extra space is calculated by multiplying the points' radius by this value. The default value is 1.5.

  • maxOpacity: maximum used opacity value. The default value is 0.8.

  • minOpacity: minimum used opacity value. The default value is 0.1.

  • blur: blur value to be represented. The default value is 0.85.

  • gradient: a set of colors and limit conforming the map's color gradient. The default value is:

    • '.3': 'blue',

    • '.65': 'yellow',

    • '.8': 'orange',

    • '.95': 'red'

...


Unless specifically required, it's best to interact only with radius, maximum and minimum opacity, blur and gradient, because the other properties do not give such a direct visual result and modifying them can generate abominations depending on the values to be shown in the heatmap (or alternatively, to not detecting any obvious change).

...

About the heatmap's symbology, this is achieved by modifying the gradient's options. Each gradient range is defined by the gradient's cut-off value (under a color, over another color; .65 means, for instance, that yellow is under this color and orange over it), and the value of the color to be used. This color is based on CSS, meaning that it accepts both the color's name (blue) and the color's hexadecimal value (#0000FF).

We will now see the heatmap with different gradient values:


...


Info
title

Gradient cut-off values

You can do as many color leaps as you want; two, three, nine, etc. You must bear in mind that the percentage progression, from 0 to 100 (or 0 to 1, as Cesium work), must be followed → 0.1, 0.2, 0.45, 0.50, 0.65, 0.80, 0.90


Viewer code

This is the code used in the example viewer.

...

Código del visor al completo
Code Block
linenumberslanguagetruexml
/** Viewer definition */
const viewer = new Cesium.Viewer('cesiumContainer', {
  /** Lower left ball widget off  */
  animation: false,
  /** Start button */
  homeButton: false,
  /** Base map selector widget */
  baseLayerPicker: false,
  /** Full screen button */
  fullscreenButton: false,
  /** Geocoder box */
  geocoder: false,
  /** Information box */
  infoBox: false,
  /** Base map */
  imageryProvider: Cesium.createOpenStreetMapImageryProvider({
	url: 'https://a.tile.openstreetmap.org/'
  }),
  /** Navigation help button */
  navigationHelpButton: false,
  /** Button to select modes 2D/2,5D/3D */
  sceneModePicker: false,
  /** Lower time bar widget */
  timeline: false,
  /** Force scene mode */
  sceneMode: Cesium.SceneMode.SCENE3D,
  /** Green box appears when selecting entities */
  selectionIndicator: false
})

/** Remove HDR effect of version 1.52 */
viewer.scene.highDynamicRange = false

jQuery.ajax({
  /** URL to Rest service */
  url:
  'https://www.onesaitplatform.online/api-manager/server/api/v1/api_tuto_cesium_heatmap/getPointFeatures',

  /** Header with token to access the API */
  headers: {
    'Content-Type': 'application/json',
	Accept: 'application/json',
	'X-OP-APIKey': 'user_token'
  },

  /** API service type */
  type: 'GET',

  /** Content offered by the API; in this case it's a JSON */
  contentType: 'application/json',

  /** If the token is valid, ontology information is recovered
   * then served to work with it */
  success: function(jsonData) {
	/** Defining the study area's boundaries (corners)*/
	let extent = {
	  west: -15.4522,
	  south: 28.1276,
	  east: -15.417,
	  north: 28.1534
	}

	/** Defining heatmap's properties */
	let heatmapSetup = {
	  radius: 150,
	  maxOpacity: 0.8,
	  minOpacity: 0.2,
	  blur: 0.75
	}

	/** Function that iterate over the JSON and extracts the coordinates and value data */
	function getData(values) {
	  /** List with the coordinates and values of each point */
	  let data = []

	  /**  Iterate by every entity in the JSON */
	  for (let feature of values.features) {
	    /** Define fields: longitude, latitude and weight */ 
		let longitude = feature.geometry.coordinates[0]
		let latitude = feature.geometry.coordinates[1]
		let weight = feature.properties.value

		/** Add each field to the list  */
		data.push({ x: longitude, y: latitude, value: weight })
	  }

	  /** Return point list  */
	  return data
	}

	/** Generating heatmap instance */
	let heatMap = CesiumHeatmap.create(viewer, extent, heatmapSetup)

	/** Adding heatmap to viewer */
	heatMap.setWGS84Data(15, 26, getData(jsonData))

	/** Zoom on the heatmap */
	viewer.camera.flyTo({
		destination: Cesium.Cartesian3.fromDegrees(
			-15.4301002359,
			28.1405417072,
			6000.0
		),
		orientation: {
			heading: Cesium.Math.toRadians(0),
			pitch: Cesium.Math.toRadians(-90.0),
			roll: 0.0
		}
	})
  },
  /** If the token is not valid, launch error */
  error: function(data, errorThrown) {
	alert('A problem reading the ontology has been detected.')
  }
})

...