// The original code is here: https://github.com/manuelnas/CesiumHeatmap/blob/master/HeatmapImageryProvider.js // the author does not provider any license description // the license of HeatMap.js is here : https://github.com/pa7/heatmap.js/blob/master/LICENSE // I did nothing but just make this file easier for Cesiumer to load (function (window) { var HeatmapImageryProvider = function (options) { options = Cesium.defaultValue(options, {}); var arrPoint = options.data; if (!Cesium.defined(arrPoint) || arrPoint.length == 0) { throw new Cesium.DeveloperError('data is required.'); } this._options = Cesium.defaultValue(options.heatmapoptions, {}); this._options.xField = Cesium.defaultValue(this._options.xField, 'x'); this._options.yField = Cesium.defaultValue(this._options.yField, 'y'); this._options.valueField = Cesium.defaultValue(this._options.valueField, 'value'); //计算数据的最大最小值和边界 start var _x = arrPoint[0][this._options.xField]; var _y = arrPoint[0][this._options.yField]; var _value = arrPoint[0][this._options.valueField]; var bounds = { "east": _x, "west": _x, "north": _y, "south": _y }; var min = _value; var max = _value; for (var i = 0; i < arrPoint.length; i++) { _x = arrPoint[i][this._options.xField]; _y = arrPoint[i][this._options.yField]; _value = arrPoint[i][this._options.valueField]; if (min > _value) min = _value; if (max < _value) max = _value; if (_x > bounds.east) bounds.east = _x; if (_x < bounds.west) bounds.west = _x; if (_y > bounds.north) bounds.north = _y; if (_y < bounds.south) bounds.south = _y; } //计算数据的最大最小值和边界 end if (Cesium.defined(options.min)) { min = options.min; } if (Cesium.defined(options.max)) { max = options.max; } this._min = min; this._max= max; this._data =arrPoint; var chInstance = options.chInstance || h337; this._wmp = new Cesium.WebMercatorProjection(); this._mbounds = this.wgs84ToMercatorBB(bounds); this._options.gradient = Cesium.defaultValue(this._options.gradient, { 0.25: "rgb(0,0,255)", 0.55: "rgb(0,255,0)", 0.85: "yellow", 1.0: "rgb(255,0,0)" }); this._setWidthAndHeight(this._mbounds); this._options.radius = Cesium.defaultValue(this._options.radius, Math.round(Cesium.defaultValue(this._options.radius, ((this.width > this.height) ? this.width / 60 : this.height / 60)))); this._spacing = this._options.radius * 1.5; 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._container = this._getContainer(this.width, this.height); this._options.container = this._container; this._heatmap = chInstance.create(this._options); //热力图对象 this._image = this._canvas = this._container.children[0]; this._tilingScheme = new Cesium.WebMercatorTilingScheme({ rectangleSouthwestInMeters: new Cesium.Cartesian2(this._mbounds.west, this._mbounds.south), rectangleNortheastInMeters: new Cesium.Cartesian2(this._mbounds.east, this._mbounds.north) }); this._texture = undefined; this._tileWidth = this.width; this._tileHeight = this.height; this._ready = false; this.readyPromise = Cesium.when.defer(); this._ready = this.setWGS84Data( this._min, this._max, this._data); this.readyPromise.resolve(true); }; Cesium.defineProperties(HeatmapImageryProvider.prototype, { /** * Gets the URL of the single, top-level imagery tile. * @memberof HeatmapImageryProvider.prototype * @type {String} * @readonly */ url: { get: function () { return this._url; } }, /** * Gets the width of each tile, in pixels. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {Number} * @readonly */ tileWidth: { get: function () { if (!this._ready) { throw new Cesium.DeveloperError('tileWidth must not be called before the imagery provider is ready.'); } return this._tileWidth; } }, /** * Gets the height of each tile, in pixels. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {Number} * @readonly */ tileHeight: { get: function () { if (!this._ready) { throw new Cesium.DeveloperError('tileHeight must not be called before the imagery provider is ready.'); } return this._tileHeight; } }, /** * Gets the maximum level-of-detail that can be requested. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {Number} * @readonly */ maximumLevel: { get: function () { if (!this._ready) { throw new Cesium.DeveloperError('maximumLevel must not be called before the imagery provider is ready.'); } return 0; } }, /** * Gets the minimum level-of-detail that can be requested. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {Number} * @readonly */ minimumLevel: { get: function () { if (!this._ready) { throw new Cesium.DeveloperError('minimumLevel must not be called before the imagery provider is ready.'); } return 0; } }, /** * Gets the tiling scheme used by this provider. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {TilingScheme} * @readonly */ tilingScheme: { get: function () { if (!this._ready) { throw new Cesium.DeveloperError('tilingScheme must not be called before the imagery provider is ready.'); } return this._tilingScheme; } }, /** * Gets the rectangle, in radians, of the imagery provided by this instance. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {Rectangle} * @readonly */ rectangle: { get: function () { return this._tilingScheme.rectangle;//TODO: change to custom rectangle? } }, /** * Gets the tile discard policy. If not undefined, the discard policy is responsible * for filtering out "missing" tiles via its shouldDiscardImage function. If this function * returns undefined, no tiles are filtered. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {TileDiscardPolicy} * @readonly */ tileDiscardPolicy: { get: function () { if (!this._ready) { throw new Cesium.DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.'); } return undefined; } }, /** * Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing * to the event, you will be notified of the error and can potentially recover from it. Event listeners * are passed an instance of {@link TileProviderError}. * @memberof HeatmapImageryProvider.prototype * @type {Event} * @readonly */ errorEvent: { get: function () { return this._errorEvent; } }, /** * Gets a value indicating whether or not the provider is ready for use. * @memberof HeatmapImageryProvider.prototype * @type {Boolean} * @readonly */ ready: { get: function () { return this._ready; } }, /** * Gets the credit to display when this imagery provider is active. Typically this is used to credit * the source of the imagery. This function should not be called before {@link HeatmapImageryProvider#ready} returns true. * @memberof HeatmapImageryProvider.prototype * @type {Credit} * @readonly */ credit: { get: function () { return this._credit; } }, /** * Gets a value indicating whether or not the images provided by this imagery provider * include an alpha channel. If this property is false, an alpha channel, if present, will * be ignored. If this property is true, any images without an alpha channel will be treated * as if their alpha is 1.0 everywhere. When this property is false, memory usage * and texture upload time are reduced. * @memberof HeatmapImageryProvider.prototype * @type {Boolean} * @readonly */ hasAlphaChannel: { get: function () { return true; } } }); HeatmapImageryProvider.prototype._setWidthAndHeight = function (mbb) { var maxCanvasSize = 2000; var minCanvasSize = 700; 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 > maxCanvasSize) { this._factor = this.width / maxCanvasSize; if (this.height / this._factor < minCanvasSize) { this._factor = this.height / minCanvasSize; } } else if (this.height > this.width && this.height > maxCanvasSize) { this._factor = this.height / maxCanvasSize; if (this.width / this._factor < minCanvasSize) { this._factor = this.width / minCanvasSize; } } else if (this.width < this.height && this.width < minCanvasSize) { this._factor = this.width / minCanvasSize; if (this.height / this._factor > maxCanvasSize) { this._factor = this.height / maxCanvasSize; } } else if (this.height < this.width && this.height < minCanvasSize) { this._factor = this.height / minCanvasSize; if (this.width / this._factor > maxCanvasSize) { this._factor = this.width / maxCanvasSize; } } this.width = this.width / this._factor; this.height = this.height / this._factor; }; HeatmapImageryProvider.prototype._getContainer = function (width, height, id) { var c = document.createElement("div"); if (id) { c.setAttribute("id", id); } c.setAttribute("style", "width: " + width + "px; height: " + height + "px; margin: 0px; display: none;"); document.body.appendChild(c); return c; }; /** * Convert a WGS84 location into a Mercator location. * * @param {Object} point The WGS84 location. * @param {Number} [point.x] The longitude of the location. * @param {Number} [point.y] The latitude of the location. * @returns {Cartesian3} The Mercator location. */ HeatmapImageryProvider.prototype.wgs84ToMercator = function (point) { return this._wmp.project(Cesium.Cartographic.fromDegrees(point[this._options.xField], point[this._options.yField])); }; /** * Convert a WGS84 bounding box into a Mercator bounding box. * * @param {Object} bounds The WGS84 bounding box. * @param {Number} [bounds.north] The northernmost position. * @param {Number} [bounds.south] The southrnmost position. * @param {Number} [bounds.east] The easternmost position. * @param {Number} [bounds.west] The westernmost position. * @returns {Object} The Mercator bounding box containing north, south, east and west properties. */ HeatmapImageryProvider.prototype.wgs84ToMercatorBB = function (bounds) { var ne = this._wmp.project(Cesium.Cartographic.fromDegrees(bounds.east, bounds.north)); var sw = this._wmp.project(Cesium.Cartographic.fromDegrees(bounds.west, bounds.south)); return { north: ne.y, south: sw.y, east: ne.x, west: sw.x }; }; /** * Convert a mercator location into a WGS84 location. * * @param {Object} point The Mercator lcation. * @param {Number} [point.x] The x of the location. * @param {Number} [point.y] The y of the location. * @returns {Object} The WGS84 location. */ HeatmapImageryProvider.prototype.mercatorToWgs84 = function (p) { var wp = this._wmp.unproject(new Cesium.Cartesian3(p.x, p.y)); return { x: wp.longitude, y: wp.latitude }; }; /** * Convert a Mercator bounding box into a WGS84 bounding box. * * @param {Object} bounds The Mercator bounding box. * @param {Number} [bounds.north] The northernmost position. * @param {Number} [bounds.south] The southrnmost position. * @param {Number} [bounds.east] The easternmost position. * @param {Number} [bounds.west] The westernmost position. * @returns {Object} The WGS84 bounding box containing north, south, east and west properties. */ HeatmapImageryProvider.prototype.mercatorToWgs84BB = function (bounds) { var sw = this._wmp.unproject(new Cesium.Cartesian3(bounds.west, bounds.south)); var ne = this._wmp.unproject(new Cesium.Cartesian3(bounds.east, bounds.north)); return { north: this.rad2deg(ne.latitude), east: this.rad2deg(ne.longitude), south: this.rad2deg(sw.latitude), west: this.rad2deg(sw.longitude) }; }; /** * Convert degrees into radians. * * @param {Number} degrees The degrees to be converted to radians. * @returns {Number} The converted radians. */ HeatmapImageryProvider.prototype.deg2rad = function (degrees) { return (degrees * (Math.PI / 180.0)); }; /** * Convert radians into degrees. * * @param {Number} radians The radians to be converted to degrees. * @returns {Number} The converted degrees. */ HeatmapImageryProvider.prototype.rad2deg = function (radians) { return (radians / (Math.PI / 180.0)); }; /** * Convert a WGS84 location to the corresponding heatmap location. * * @param {Object} point The WGS84 location. * @param {Number} [point.x] The longitude of the location. * @param {Number} [point.y] The latitude of the location. * @returns {Object} The corresponding heatmap location. */ HeatmapImageryProvider.prototype.wgs84PointToHeatmapPoint = function (point) { return this.mercatorPointToHeatmapPoint(this.wgs84ToMercator(point)); }; /** * Convert a mercator location to the corresponding heatmap location. * * @param {Object} point The Mercator lcation. * @param {Number} [point.x] The x of the location. * @param {Number} [point.y] The y of the location. * @returns {Object} The corresponding heatmap location. */ HeatmapImageryProvider.prototype.mercatorPointToHeatmapPoint = function (point) { var pn = {}; pn[this._options.xField] = Math.round((point.x - this._xoffset) / this._factor + this._spacing); pn[this._options.yField] = Math.round((point.y - this._yoffset) / this._factor + this._spacing); pn[this._options.yField] = this.height - pn[this._options.yField]; return pn; }; /** * Set an array of heatmap locations. * * @param {Number} min The minimum allowed value for the data points. * @param {Number} max The maximum allowed value for the data points. * @param {Array} data An array of data points with heatmap coordinates(x, y) and value * @returns {Boolean} Wheter or not the data was successfully added or failed. */ HeatmapImageryProvider.prototype.setData = function (min, max, data) { 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. * * @param {Number} min The minimum allowed value for the data points. * @param {Number} max The maximum allowed value for the data points. * @param {Array} data An array of data points with WGS84 coordinates(x=lon, y=lat) and value * @returns {Boolean} Wheter or not the data was successfully added or failed. */ HeatmapImageryProvider.prototype.setWGS84Data = function (min, max, data) { if (data && data.length > 0 && min !== null && min !== false && max !== null && max !== false) { var convdata = []; for (var i = 0; i < data.length; i++) { var gp = data[i]; var hp = this.wgs84PointToHeatmapPoint(gp); var value = gp[this._options.valueField]; if (value) { hp[this._options.valueField] = value; } convdata.push(hp); } return this.setData(min, max, convdata); } return false; }; /** * Gets the credits to be displayed when a given tile is displayed. * * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level; * @returns {Credit[]} The credits to be displayed when the tile is displayed. * * @exception {DeveloperError} getTileCredits must not be called before the imagery provider is ready. */ HeatmapImageryProvider.prototype.getTileCredits = function (x, y, level) { return undefined; }; /** * Requests the image for a given tile. This function should * not be called before {@link HeatmapImageryProvider#ready} returns true. * * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. * @returns {Promise} A promise for the image that will resolve when the image is available, or * undefined if there are too many active requests to the server, and the request * should be retried later. The resolved image may be either an * Image or a Canvas DOM object. * * @exception {DeveloperError} requestImage must not be called before the imagery provider is ready. */ HeatmapImageryProvider.prototype.requestImage = function (x, y, level) { if (!this._ready) { throw new Cesium.DeveloperError('requestImage must not be called before the imagery provider is ready.'); } return this._image; }; /** * Picking features is not currently supported by this imagery provider, so this function simply returns * undefined. * * @param {Number} x The tile X coordinate. * @param {Number} y The tile Y coordinate. * @param {Number} level The tile level. * @param {Number} longitude The longitude at which to pick features. * @param {Number} latitude The latitude at which to pick features. * @return {Promise} A promise for the picked features that will resolve when the asynchronous * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo} * instances. The array may be empty if no features are found at the given location. * It may also be undefined if picking is not supported. */ HeatmapImageryProvider.prototype.pickFeatures = function () { return undefined; }; wutu3d.HeatmapImageryProvider = HeatmapImageryProvider; })(window);