File

projects/angular-cesium/src/lib/heatmap/cesium-heatmap-material-creator.ts

Description

Create heatmap material (Cesium.ImageMaterialProperty with heatmap as the image) works with http://www.patrick-wied.at/static/heatmapjs. must do npm -i heatmap.js usage:

 *
 const mCreator = new CesiumHeatMapMaterialCreator();
 const containingRect = CesiumHeatMapMaterialCreator.calcCircleContainingRect(this.circleCenter, this.circleRadius);
 const userHeatmapOptions = {
            radius : 2000,
            minOpacity : 0,
            maxOpacity : 0.9,
        } as any;

 this.circleHeatMapMaterial = mCreator.create(containingRect, {
            heatPointsData : [
                {
                    x : -100.0,
                    y : 24.0,
                    value : 95
                }
            ],
            min : 0,
            max : 100,
        }, userHeatmapOptions);
 *

inspired by https://github.com/danwild/CesiumHeatmap

Index

Properties
Methods

Methods

Static calcCircleContainingRect
calcCircleContainingRect(center: Cartesian3, radius: number)
Parameters :
Name Type Optional Description
center Cartesian3 No
  • Cartesian3
radius number No
  • Meters
Returns : any
Static calcEllipseContainingRect
calcEllipseContainingRect(center: Cartesian3, semiMajorAxis: number, semiMinorAxis: number)
Parameters :
Name Type Optional Description
center Cartesian3 No
  • Cartesian3
semiMajorAxis number No
  • meters
semiMinorAxis number No
  • meters
Returns : any
Static calculateContainingRectFromPoints
calculateContainingRectFromPoints(points: Cartesian3[])
Parameters :
Name Type Optional Description
points Cartesian3[] No

Cartesian3

Returns : any
Public create
create(containingBoundingRect: Rectangle, heatmapDataSet: HeatmapDataSet, heatmapOptions: HeatMapOptions)

containingBoundingRect: Cesium.Rectangle like {north, east, south, west} min: the minimum allowed value for the data values max: the maximum allowed value for the data values datapoint: {x,y,value} heatmapOptions: a heatmap.js options object (see http://www.patrick-wied.at/static/heatmapjs/docs.html#h337-create)

Parameters :
Name Type Optional
containingBoundingRect Rectangle No
heatmapDataSet HeatmapDataSet No
heatmapOptions HeatMapOptions No
Returns : any
Private createContainer
createContainer(height: number, width: number)
Parameters :
Name Type Optional
height number No
width number No
Returns : { container: any; id: string; }
Private mercatorPointToHeatmapPoint
mercatorPointToHeatmapPoint(p: Cartesian2)

Convert a mercator location to the corresponding heatmap location

p: a WGS84 location like {x: lon, y:lat}

Parameters :
Name Type Optional
p Cartesian2 No
Returns : any
Private mercatorToWgs84BB
mercatorToWgs84BB(bb: any)

Convert a mercator bounding box into a WGS84 bounding box

bb: the mercator bounding box like {north, east, south, west}

Parameters :
Name Type Optional
bb any No
Returns : { north: number; east: number; south: number; west: number; }
Private setClear
setClear(heatMapMaterial: any, id: string)
Parameters :
Name Type Optional
heatMapMaterial any No
id string No
Returns : void
setData
setData(min: any, max: any, data: any)

Set an array of heatmap locations

min: the minimum allowed value for the data values max: the maximum allowed value for the data values data: an array of data points in heatmap coordinates and values like {x, y, value}

Parameters :
Name Type Optional
min any No
max any No
data any No
Returns : boolean
Private setWGS84Data
setWGS84Data(min: any, max: any, data: any)

Set an array of WGS84 locations

min: the minimum allowed value for the data values max: the maximum allowed value for the data values data: an array of data points in WGS84 coordinates and values like { x:lon, y:lat, value }

Parameters :
Name Type Optional
min any No
max any No
data any No
Returns : boolean
Private setWidthAndHeight
setWidthAndHeight(mbb: any)
Parameters :
Name Type Optional
mbb any No
Returns : void
Private wgs84ToMercator
wgs84ToMercator(p: Cartesian2)

Convert a WGS84 location into a mercator location

p: the WGS84 location like {x: lon, y: lat}

Parameters :
Name Type Optional
p Cartesian2 No
Returns : { x: any; y: any; }
Private wgs84ToMercatorBB
wgs84ToMercatorBB(bb: any)

Convert a WGS84 bounding box into a mercator bounding box* bb: the WGS84 bounding box like {north, east, south, west}

Parameters :
Name Type Optional
bb any No
Returns : { north: any; east: any; south: any; west: any; }

Properties

_factor
_factor: number
Type : number
_mbounds
_mbounds: any
Type : any
_rectangle
_rectangle: Rectangle
Type : Rectangle
_spacing
_spacing: number
Type : number
_xoffset
_xoffset: any
Type : any
_yoffset
_yoffset: any
Type : any
bounds
bounds: any
Type : any
Private Static containerCanvasCounter
containerCanvasCounter: number
Type : number
Default value : 0
heatmap
heatmap: any
Type : any
heatmapOptionsDefaults
heatmapOptionsDefaults: object
Type : object
Default value : { minCanvasSize: 700, // minimum size (in pixels) for the heatmap canvas maxCanvasSize: 2000, // maximum size (in pixels) for the heatmap canvas radiusFactor: 60, // data point size factor used if no radius is given // (the greater of height and width divided by this number yields the used radius) spacingFactor: 1, // extra space around the borders (point radius multiplied by this number yields the spacing) maxOpacity: 0.8, // the maximum opacity used if not given in the heatmap options object minOpacity: 0.1, // the minimum opacity used if not given in the heatmap options object blur: 0.85, // the blur used if not given in the heatmap options object gradient: { // the gradient used if not given in the heatmap options object '.3': 'blue', '.65': 'yellow', '.8': 'orange', '.95': 'red' }, }
height
height: number
Type : number
Private rad2deg
rad2deg:
Default value : function (r: number) { const d = r / (Math.PI / 180.0); return d; }
Private wgs84PointToHeatmapPoint
wgs84PointToHeatmapPoint:
Default value : function (p: Cartesian2) { return this.mercatorPointToHeatmapPoint(this.wgs84ToMercator(p)); }

Convert a WGS84 location to the corresponding heatmap location

p: a WGS84 location like {x:lon, y:lat}

width
width: number
Type : number
WMP
WMP:
Default value : new Cesium.WebMercatorProjection()
import { Cartesian2 } from '../angular-cesium/models/cartesian2';
import { Cartesian3 } from '../angular-cesium/models/cartesian3';
import { GeoUtilsService } from '../angular-cesium/services/geo-utils/geo-utils.service';
import * as h337 from 'heatmap.js/build/heatmap.js';
import { Injectable } from '@angular/core';

// Consider moving to a different package.

if (!h337) {
  throw new Error('must install heatmap.js. please do npm -i heatmap.js ');
}


export interface Rectangle {
  west: number;
  south: number;
  east: number;
  north: number;
}


/**
 *  x: lon
 *  y: lat
 *  value: point value
 */
export interface HeatPointDataPoint {
  x: number;
  y: number;
  value: number;
}

/**
 *   min:  the minimum allowed value for the data values
 *  max:  the maximum allowed value for the data values
 *  heatPointsData: an array of data points in WGS84 coordinates and values like { x:lon, y:lat, value)
 */
export interface HeatmapDataSet {
  min?: number;
  max?: number;
  heatPointsData: HeatPointDataPoint[];
}

/**
 * a heatmap.js options object (see http://www.patrick-wied.at/static/heatmapjs/docs.html#h337-create)
 */
export interface HeatMapOptions {
  [propName: string]: any;

  gradient?: any;
  radius?: number;
  opacity?: number;
  maxOpacity?: number;
  minOpacity?: number;
  blur?: any;
}

/**
 * Create heatmap material (Cesium.ImageMaterialProperty with heatmap as the image)
 * works with http://www.patrick-wied.at/static/heatmapjs. must do npm -i heatmap.js
 * usage:
 * ```
 *
 const mCreator = new CesiumHeatMapMaterialCreator();
 const containingRect = CesiumHeatMapMaterialCreator.calcCircleContainingRect(this.circleCenter, this.circleRadius);
 const userHeatmapOptions = {
			radius : 2000,
			minOpacity : 0,
			maxOpacity : 0.9,
		} as any;

 this.circleHeatMapMaterial = mCreator.create(containingRect, {
			heatPointsData : [
				{
					x : -100.0,
					y : 24.0,
					value : 95
				}
			],
			min : 0,
			max : 100,
		}, userHeatmapOptions);
 * ```
 *
 * inspired by https://github.com/danwild/CesiumHeatmap
 */
@Injectable()
export class CesiumHeatMapMaterialCreator {


  private static containerCanvasCounter = 0;

  heatmapOptionsDefaults = {
    minCanvasSize: 700,           // minimum size (in pixels) for the heatmap canvas
    maxCanvasSize: 2000,          // maximum size (in pixels) for the heatmap canvas
    radiusFactor: 60,             // data point size factor used if no radius is given
    // (the greater of height and width divided by this number yields the used radius)
    spacingFactor: 1,           // extra space around the borders (point radius multiplied by this number yields the spacing)
    maxOpacity: 0.8,              // the maximum opacity used if not given in the heatmap options object
    minOpacity: 0.1,              // the minimum opacity used if not given in the heatmap options object
    blur: 0.85,                   // the blur used if not given in the heatmap options object
    gradient: {                   // the gradient used if not given in the heatmap options object
      '.3': 'blue',
      '.65': 'yellow',
      '.8': 'orange',
      '.95': 'red'
    },
  };

  WMP = new Cesium.WebMercatorProjection();
  _spacing: number;
  width: number;
  height: number;
  _mbounds: any;
  bounds: any;
  _factor: number;
  _rectangle: Rectangle;
  heatmap: any;
  _xoffset: any;
  _yoffset: any;

  /**
   *
   * @param center - Cartesian3
   * @param radius - Meters
   */
  static calcCircleContainingRect(center: Cartesian3, radius: number) {
    return CesiumHeatMapMaterialCreator.calcEllipseContainingRect(center, radius, radius);
  }

  /**
   *
   * @param center - Cartesian3
   * @param semiMinorAxis - meters
   * @param semiMajorAxis - meters
   */
  static calcEllipseContainingRect(center: Cartesian3, semiMajorAxis: number, semiMinorAxis: number) {
    const top = GeoUtilsService.pointByLocationDistanceAndAzimuth(
      center,
      semiMinorAxis,
      0,
      true
    );
    const right = GeoUtilsService.pointByLocationDistanceAndAzimuth(
      center,
      semiMajorAxis,
      Math.PI / 2,
      true
    );
    const bottom = GeoUtilsService.pointByLocationDistanceAndAzimuth(
      center,
      semiMajorAxis,
      Math.PI,
      true
    );
    const left = GeoUtilsService.pointByLocationDistanceAndAzimuth(
      center,
      semiMajorAxis,
      Math.PI * 1.5,
      true
    );

    const ellipsePoints = [top, right, bottom, left];
    return Cesium.Rectangle.fromCartesianArray(ellipsePoints);
  }

  /**
   *
   * @param points Cartesian3
   */
  static calculateContainingRectFromPoints(points: Cartesian3[]) {
    return Cesium.Rectangle.fromCartesianArray(points);
  }


  /**  Set an array of heatmap locations
   *
   *  min:  the minimum allowed value for the data values
   *  max:  the maximum allowed value for the data values
   *  data: an array of data points in heatmap coordinates and values like {x, y, value}
   */
  setData(min: any, max: any, data: any) {
    if (data && data.length > 0 && min !== null && min !== false && max !== null && max !== false) {
      this.heatmap.setData({
        min: min,
        max: max,
        data: data
      });

      return true;
    }

    return false;
  }

  /**  Set an array of WGS84 locations
   *
   *  min:  the minimum allowed value for the data values
   *  max:  the maximum allowed value for the data values
   *  data: an array of data points in WGS84 coordinates and values like { x:lon, y:lat, value }
   */
  private setWGS84Data(min: any, max: any, data: any) {
    if (data && data.length > 0 && min !== null && min !== false && max !== null && max !== false) {
      const convdata = [];

      for (let i = 0; i < data.length; i++) {
        const gp = data[i];

        const hp = this.wgs84PointToHeatmapPoint(gp);
        if (gp.value || gp.value === 0) {
          hp.value = gp.value;
        }

        convdata.push(hp);
      }

      return this.setData(min, max, convdata);
    }

    return false;
  }

  /**  Convert a mercator location to the corresponding heatmap location
   *
   *  p: a WGS84 location like {x: lon, y:lat}
   */
  private mercatorPointToHeatmapPoint(p: Cartesian2) {
    const pn: any = {};

    pn.x = Math.round((p.x - this._xoffset) / this._factor + this._spacing);
    pn.y = Math.round((p.y - this._yoffset) / this._factor + this._spacing);
    pn.y = this.height - pn.y;

    return pn;
  }

  /**  Convert a WGS84 location to the corresponding heatmap location
   *
   *  p: a WGS84 location like {x:lon, y:lat}
   */
  private wgs84PointToHeatmapPoint = function (p: Cartesian2) {
    return this.mercatorPointToHeatmapPoint(this.wgs84ToMercator(p));
  };


  private createContainer(height: number, width: number) {
    const id = 'heatmap' + CesiumHeatMapMaterialCreator.containerCanvasCounter++;
    const container = document.createElement('div');
    container.setAttribute('id', id);
    container.setAttribute('style', 'width: ' + width + 'px; height: ' + height + 'px; margin: 0px; display: none;');
    document.body.appendChild(container);
    return {container, id};
  }

  /**  Convert a WGS84 location into a mercator location
   *
   *  p: the WGS84 location like {x: lon, y: lat}
   */
  private wgs84ToMercator(p: Cartesian2) {
    const mp = this.WMP.project(Cesium.Cartographic.fromDegrees(p.x, p.y));
    return {
      x: mp.x,
      y: mp.y
    };
  }

  private rad2deg = function (r: number) {
    const d = r / (Math.PI / 180.0);
    return d;
  };

  /**  Convert a WGS84 bounding box into a mercator bounding box*
   *  bb: the WGS84 bounding box like {north, east, south, west}
   */
  private wgs84ToMercatorBB(bb: any) {
    // TODO validate rad or deg
    const sw = this.WMP.project(Cesium.Cartographic.fromRadians(bb.west, bb.south));
    const ne = this.WMP.project(Cesium.Cartographic.fromRadians(bb.east, bb.north));
    return {
      north: ne.y,
      east: ne.x,
      south: sw.y,
      west: sw.x
    };
  }

  /**  Convert a mercator bounding box into a WGS84 bounding box
   *
   *  bb: the mercator bounding box like {north, east, south, west}
   */
  private mercatorToWgs84BB(bb: any) {
    const sw = this.WMP.unproject(new Cesium.Cartesian3(bb.west, bb.south));
    const ne = this.WMP.unproject(new Cesium.Cartesian3(bb.east, bb.north));
    return {
      north: this.rad2deg(ne.latitude),
      east: this.rad2deg(ne.longitude),
      south: this.rad2deg(sw.latitude),
      west: this.rad2deg(sw.longitude)
    };
  }

  private setWidthAndHeight(mbb: any) {
    this.width = ((mbb.east > 0 && mbb.west < 0) ? mbb.east + Math.abs(mbb.west) : Math.abs(mbb.east - mbb.west));
    this.height = ((mbb.north > 0 && mbb.south < 0) ? mbb.north + Math.abs(mbb.south) : Math.abs(mbb.north - mbb.south));
    this._factor = 1;

    if (this.width > this.height && this.width > this.heatmapOptionsDefaults.maxCanvasSize) {
      this._factor = this.width / this.heatmapOptionsDefaults.maxCanvasSize;

      if (this.height / this._factor < this.heatmapOptionsDefaults.minCanvasSize) {
        this._factor = this.height / this.heatmapOptionsDefaults.minCanvasSize;
      }
    } else if (this.height > this.width && this.height > this.heatmapOptionsDefaults.maxCanvasSize) {
      this._factor = this.height / this.heatmapOptionsDefaults.maxCanvasSize;

      if (this.width / this._factor < this.heatmapOptionsDefaults.minCanvasSize) {
        this._factor = this.width / this.heatmapOptionsDefaults.minCanvasSize;
      }
    } else if (this.width < this.height && this.width < this.heatmapOptionsDefaults.minCanvasSize) {
      this._factor = this.width / this.heatmapOptionsDefaults.minCanvasSize;

      if (this.height / this._factor > this.heatmapOptionsDefaults.maxCanvasSize) {
        this._factor = this.height / this.heatmapOptionsDefaults.maxCanvasSize;
      }
    } else if (this.height < this.width && this.height < this.heatmapOptionsDefaults.minCanvasSize) {
      this._factor = this.height / this.heatmapOptionsDefaults.minCanvasSize;

      if (this.width / this._factor > this.heatmapOptionsDefaults.maxCanvasSize) {
        this._factor = this.width / this.heatmapOptionsDefaults.maxCanvasSize;
      }
    }

    this.width = this.width / this._factor;
    this.height = this.height / this._factor;
  }

  /**
   * containingBoundingRect: Cesium.Rectangle like {north, east, south, west}
   * min:  the minimum allowed value for the data values
   * max:  the maximum allowed value for the data values
   * datapoint: {x,y,value}
   * heatmapOptions: a heatmap.js options object (see http://www.patrick-wied.at/static/heatmapjs/docs.html#h337-create)
   *
   */
  public create(containingBoundingRect: Rectangle, heatmapDataSet: HeatmapDataSet, heatmapOptions: HeatMapOptions) {
    const userBB = containingBoundingRect;
    const {heatPointsData, min = 0, max = 100} = heatmapDataSet;
    const finalHeatmapOptions = Object.assign({}, this.heatmapOptionsDefaults, heatmapOptions);

    this._mbounds = this.wgs84ToMercatorBB(userBB);
    this.setWidthAndHeight(this._mbounds);

    finalHeatmapOptions.radius = Math.round((heatmapOptions.radius) ?
      heatmapOptions.radius : ((this.width > this.height) ?
        this.width / this.heatmapOptionsDefaults.radiusFactor :
        this.height / this.heatmapOptionsDefaults.radiusFactor));
    this._spacing = finalHeatmapOptions.radius * this.heatmapOptionsDefaults.spacingFactor;
    this._xoffset = this._mbounds.west;
    this._yoffset = this._mbounds.south;

    this.width = Math.round(this.width + this._spacing * 2);
    this.height = Math.round(this.height + this._spacing * 2);

    this._mbounds.west -= this._spacing * this._factor;
    this._mbounds.east += this._spacing * this._factor;
    this._mbounds.south -= this._spacing * this._factor;
    this._mbounds.north += this._spacing * this._factor;

    this.bounds = this.mercatorToWgs84BB(this._mbounds);
    this._rectangle = Cesium.Rectangle.fromDegrees(this.bounds.west, this.bounds.south, this.bounds.east, this.bounds.north);

    const {container, id} = this.createContainer(this.height, this.width);
    Object.assign(finalHeatmapOptions, {container});

    this.heatmap = h337.create(finalHeatmapOptions);


    this.setWGS84Data(0, 100, heatPointsData);
    const heatMapCanvas = this.heatmap._renderer.canvas;
    const heatMapMaterial = new Cesium.ImageMaterialProperty({
      image: heatMapCanvas,
      transparent: true,
    });
    this.setClear(heatMapMaterial, id);

    return heatMapMaterial;
  }

  private setClear(heatMapMaterial: any, id: string) {
    heatMapMaterial.clear = () => {
      const elem = document.getElementById(id);
      return elem.parentNode.removeChild(elem);
    };
  }
}

result-matching ""

    No results matching ""