arcgis js 4 風場視覺化
阿新 • • 發佈:2022-01-20
當我們做洋流或者風場 視覺化時候 echart 雖然也能用 但是資料量過大會很卡
資料呼叫是這個樣子
樣例資料
連結:https://pan.baidu.com/s/1yQrIMBMJdSPwnnI8YOC1_Q提取碼: tnhc
最終呼叫
- import {VectorField} from './VectorField';
- // this.map 為arcgis Map物件例項
- let vectorField = new VectorField(this.map);
- const options = {
-
url: "./static/data/2020081106.json",
- displayOptions: {
- minVelocity: 0, // 最小速度
- maxVelocity: 10, // 最大速度
- velocityScale: 0.005, // 速度
- particleAge: 90, // 粒子生存的幀數
- lineWidth: 0.5, // 線寬
- frameRate: 15, // 運動幀率
-
particleDensity: 10, // 每50x50畫素塊的粒子數
- colorScale: ["#ffffff", "#e9ecfb", "#d3d9f7", "#bdc6f3", "#a7b3ef", "#91a0eb", "#7b8de7", "#657ae3", "#4f67df", "#3954db"]
- }
- }
- vectorField.start(options);
我們需要兩個類
- AnimatedEnvironmentLayer 類
```javascript
import esriLoader from 'esri-loader';
export const AnimatedEnvironmentLayer = {}
AnimatedEnvironmentLayer.create = function () {
let a = null;
return new Promise(async (resolve, reject) => {
- const [asd, MapView, Map, Point, GraphicsLayer, SpatialReference, Basemap, esriRequest, on, dom, BaseLayerView2D, watchUtils, webMercatorUtils] = await (esriLoader.loadModules([
- "esri/core/accessorSupport/decorators",
- "esri/views/MapView",
- "esri/Map",
- "esri/geometry/Point",
- "esri/layers/GraphicsLayer",
- "esri/geometry/SpatialReference",
- "esri/Basemap",
- "esri/request",
- "dojo/on",
- "dojo/dom",
- "esri/views/2d/layers/BaseLayerView2D",
- "esri/core/watchUtils",
- "esri/geometry/support/webMercatorUtils",
- ]));
- class AnimatedEnvironmentLayerView2D extends BaseLayerView2D {
- constructor(props) {
- super();
- this.view = props.view;
- this.layer = props.layer;
- this.view.on("resize", () => {
- if (!this.context) return;
- // resize the canvas
- this.context.canvas.width = this.view.width;
- this.context.canvas.height = this.view.height;
- });
- watchUtils.watch(this.layer, "visible", (nv, olv, pn, ta) => {
- if (!nv) {
- this.clear();
- } else {
- this.prepDraw();
- }
- });
- }
- render(renderParameters) {
- this.viewState = renderParameters.state;
- if (!renderParameters.stationary) {
- // not stationary so clear if drawn and set to prep again
- if (this.drawing) {
- this.clear();
- this.drawing = false;
- }
- this.drawPrepping = false;
- this.drawReady = false;
- return;
- }
- if (!this.drawPrepping && !this.drawReady) {
- // prep the draw
- this.drawPrepping = true;
- if (this.windy && this.windy.gridData) {
- this.prepDraw();
- }
- return;
- }
- if (this.drawReady) {
- if (!this.drawing) {
- // this.animationLoop(); // haven't started drawing so kick off our animation loop
- this.startWindy();
- }
- // draw the custom context into this layers context
- renderParameters.context.drawImage(this.context.canvas, 0, 0);
- this.drawing = true;
- // call request render so we copy the draw again
- this.requestRender();
- }
- }
- startWindy() {
- setTimeout(() => {
- this.windy.start(
- [[0, 0], [this.context.canvas.width, this.context.canvas.height]],
- this.context.canvas.width,
- this.context.canvas.height,
- [[this.southWest.x, this.southWest.y], [this.northEast.x, this.northEast.y]]
- );
- this.setDate();
- }, 500);
- }
- attach() {
- // use attach to initilaize a custom canvas to draw on
- // create the canvas, set some properties.
- const canvas = document.createElement("canvas");
- canvas.id = "ael-" + Date.now();
- canvas.style.position = "absolute";
- canvas.style.top = "0";
- canvas.style.left = "0";
- canvas.width = this.view.width;
- canvas.height = this.view.height;
- const context = canvas.getContext("2d");
- this.context = context;
- this.initWindy();
- }
- initWindy(data) {
- this.windy = new Windy(
- this.context.canvas,
- this.layer.displayOptions,
- undefined
- );
- }
- clear(stopDraw = true) {
- if (stopDraw) {
- this.stopDraw();
- }
- if (this.context) {
- this.context.clearRect(0, 0, this.view.width, this.view.height);
- }
- }
- stopDraw() {
- this.windy.stop();
- this.drawing = false;
- }
- prepDraw(data) {
- if (data) this.windy.setData(data);
- this.setParticleDensity();
- this.startDraw();
- this.drawPrepping = false;
- this.drawReady = true;
- this.requestRender();
- }
- startDraw() {
- // use the extent of the view, and not the extent passed into fetchImage...it was slightly off when it crossed IDL.
- let extent = this.view.extent;
- if (extent.spatialReference.isWebMercator) {
- extent = webMercatorUtils.webMercatorToGeographic(extent);
- }
- this.northEast = new Point({x: extent.xmax, y: extent.ymax});
- this.southWest = new Point({x: extent.xmin, y: extent.ymin});
- // resize the canvas
- this.context.canvas.width = this.view.width;
- this.context.canvas.height = this.view.height;
- // cater for the extent crossing the IDL
- if (this.southWest.x > this.northEast.x && this.northEast.x < 0) {
- this.northEast.x = 360 + this.northEast.x;
- }
- }
- setParticleDensity() {
- if (!Array.isArray(this.layer.displayOptions.particleDensity)) {
- return; // not an array, so must be a number, exit out here as there's no calc to do
- }
- const stops = this.layer.displayOptions.particleDensity;
- const currentZoom = Math.round(this.view.zoom);
- let density = -1;
- const zoomMap = stops.map((stop) => {
- return stop.zoom;
- });
- console.log("zoomMap", zoomMap);
- // loop the zooms
- for (let i = 0; i < stops.length; i++) {
- const stop = stops[i];
- if (stop.zoom === currentZoom) {
- density = stop.density;
- break;
- }
- const nextStop = i + 1 < stops.length ? stops[i + 1] : undefined;
- if (!nextStop) {
- // this is the last one, so just set to this value
- density = stop.density;
- break;
- }
- if (nextStop.zoom > currentZoom) {
- density = stop.density;
- break;
- }
- }
- // if density still not found, set it to the last value in the stops array
- if (density === -1) {
- density = stops[stops.length - 1].density;
- }
- this.windy.calculatedDensity = density;
- }
- setDate() {
- if (this.windy) {
- if (this.windy.refTime && this.windy.forecastTime) {
- // assume the ref time is an iso string, or some other equivalent that javascript Date object can parse.
- const d = new Date(this.windy.refTime);
- // add the forecast time as hours to the refTime;
- d.setHours(d.getHours() + this.windy.forecastTime);
- this.date = d;
- return;
- }
- }
- this.date = undefined;
- }
- }
- var __extends = (this && this.__extends) || (function () {
- var extendStatics = function (d, b) {
- extendStatics = Object.setPrototypeOf ||
- ({__proto__: []} instanceof Array && function (d, b) {
- d.__proto__ = b;
- }) ||
- function (d, b) {
- for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- };
- return extendStatics(d, b);
- };
- return function (d, b) {
- extendStatics(d, b);
- function __() {
- this.constructor = d;
- }
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
- };
- })();
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
- var c = arguments.length,
- r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
- r = Reflect.decorate(decorators, target, key, desc);
- } else {
- for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
- }
- return c > 3 && r && Object.defineProperty(target, key, r), r;
- };
- var __metadata = (this && this.__metadata) || function (k, v) {
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
- };
- var AnimatedEnvironmentLayer = /** @class */ (function (_super) {
- __extends(AnimatedEnvironmentLayer, _super);
- function AnimatedEnvironmentLayer(properties) {
- var _this = _super.call(this, properties) || this;
- // If the active view is set in properties, then set it here.
- _this.url = properties.url;
- _this.displayOptions = properties.displayOptions || {};
- if (Array.isArray(_this.displayOptions.particleDensity)) {
- // make sure the particle density stops array is is order by zoom level lowest zooms first
- _this.displayOptions.particleDensity.sort(function (a, b) {
- return a.zoom - b.zoom;
- });
- }
- _this.reportValues = properties.reportValues === false ? false : true; // default to true
- // watch url prop so a fetch of data and redraw will occur.
- watchUtils.watch(_this, "url", function (a, b, c, d) {
- return _this._urlChanged(a, b, c, d);
- });
- // watch visible so a fetch of data and redraw will occur.
- watchUtils.watch(_this, "visible", function (a, b, c, d) {
- return _this._visibleChanged(a, b, c, d);
- });
- // watch display options so to redraw when changed.
- watchUtils.watch(_this, "displayOptions", function (a, b, c, d) {
- return _this._displayOptionsChanged(a, b, c, d);
- });
- _this.dataFetchRequired = true;
- return _this;
- }
- AnimatedEnvironmentLayer.prototype.createLayerView = function (view) {
- var _this = this;
- // only supports 2d right now.
- if (view.type !== "2d") {
- console.error("不支援3D(only supports 2d right now)", view.type)
- return;
- }
- // hook up the AnimatedEnvironmentLayerView2D as the layer view
- this.layerView = new AnimatedEnvironmentLayerView2D({
- view: view,
- layer: this
- });
- this.layerView.view.on("pointer-move", function (evt) {
- return _this.viewPointerMove(evt);
- });
- this.draw(true);
- return this.layerView;
- };
- AnimatedEnvironmentLayer.prototype.draw = function (forceDataRefetch) {
- var _this = this;
- if (forceDataRefetch != null) {
- this.dataFetchRequired = forceDataRefetch;
- }
- if (!this.url || !this.visible) {
- return;
- } // no url set, not visible or is currently drawing, exit here.
- // if data should be fetched, go get it now.
- if (this.dataFetchRequired) {
- this.isErrored = false;
- this.dataLoading = true;
- esriRequest(this.url, {
- responseType: "json"
- })
- .then(function (response) {
- _this.dataFetchRequired = false;
- _this.doDraw(response.data); // all sorted draw now.
- _this.dataLoading = false;
- })
- .otherwise(function (err) {
- console.error("Error occurred retrieving data. " + err);
- _this.dataLoading = false;
- _this.isErrored = true;
- });
- }
- else {
- // no need for data, just draw. no need for data, just draw.
- this.doDraw();
- }
- };
- AnimatedEnvironmentLayer.prototype.stop = function () {
- if (this.layerView) {
- this.layerView.stopDraw();
- }
- };
- AnimatedEnvironmentLayer.prototype.start = function () {
- this.doDraw();
- };
- AnimatedEnvironmentLayer.prototype.doDraw = function (data) {
- this.layerView.prepDraw(data);
- };
- AnimatedEnvironmentLayer.prototype.viewPointerMove = function (evt) {
- if (!this.layerView.windy || !this.visible) {
- return;
- }
- var mousePos = this._getMousePos(evt);
- var point = this.layerView.view.toMap({x: mousePos.x, y: mousePos.y});
- if (point.spatialReference.isWebMercator) {
- point = webMercatorUtils.webMercatorToGeographic(point);
- }
- var grid = this.layerView.windy.interpolate(point.x, point.y);
- var result = {
- point: point,
- target: this
- };
- if (!grid || (isNaN(grid[0]) || isNaN(grid[1]) || !grid[2])) {
- // the current point contains no data in the windy grid, so emit an object with no speed or direction object
- //當前點在風網格中不包含任何資料,所以發射一個沒有速度或方向的物件
- this.emit("point-report", result);
- return;
- }
- // get the speed and direction and emit the result 獲得速度和方向,併發出結果
- result.velocity = this._vectorToSpeed(grid[0], grid[1]);
- result.degree = this._vectorToDegrees(grid[0], grid[1]);
- this.emit("point-report", result);
- };
- AnimatedEnvironmentLayer.prototype._vectorToSpeed = function (uMs, vMs) {
- var speedAbs = Math.sqrt(Math.pow(uMs, 2) + Math.pow(vMs, 2));
- return speedAbs;
- };
- AnimatedEnvironmentLayer.prototype._vectorToDegrees = function (uMs, vMs) {
- var abs = Math.sqrt(Math.pow(uMs, 2) + Math.pow(vMs, 2));
- var direction = Math.atan2(uMs / abs, vMs / abs);
- var directionToDegrees = direction * 180 / Math.PI + 180;
- directionToDegrees += 180;
- if (directionToDegrees >= 360) {
- directionToDegrees -= 360;
- }
- return directionToDegrees;
- };
- AnimatedEnvironmentLayer.prototype._getMousePos = function (evt) {
- // container on the view is actually a html element at this point, not a string as the typings suggest.
- var container = this.layerView.view.container;
- var rect = container.getBoundingClientRect();
- return {
- x: evt.x - rect.left,
- y: evt.y - rect.top
- };
- };
- AnimatedEnvironmentLayer.prototype._urlChanged = function (a, b, c, d) {
- this.stop();
- this.dataFetchRequired = true;
- this.draw();
- };
- AnimatedEnvironmentLayer.prototype._visibleChanged = function (visible, b, c, d) {
- if (!visible) {
- this.stop();
- }
- else {
- this.draw();
- }
- };
- AnimatedEnvironmentLayer.prototype._displayOptionsChanged = function (newOptions, b, c, d) {
- if (!this.layerView.windy) {
- return;
- }
- this.layerView.windy.stop();
- this.layerView.windy.setDisplayOptions(newOptions);
- this.draw();
- };
- __decorate([
- asd.property(),
- __metadata("design:type", String)
- ], AnimatedEnvironmentLayer.prototype, "url", void 0);
- __decorate([
- asd.property(),
- __metadata("design:type", Object)
- ], AnimatedEnvironmentLayer.prototype, "displayOptions", void 0);
- __decorate([
- asd.property(),
- __metadata("design:type", Boolean)
- ], AnimatedEnvironmentLayer.prototype, "reportValues", void 0);
- __decorate([
- asd.property(),
- __metadata("design:type", Boolean)
- ], AnimatedEnvironmentLayer.prototype, "dataLoading", void 0);
- __decorate([
- asd.property(),
- __metadata("design:type", Boolean)
- ], AnimatedEnvironmentLayer.prototype, "isErrored", void 0);
- AnimatedEnvironmentLayer = __decorate([
- asd.subclass("AnimatedEnvironmentLayer"),
- __metadata("design:paramtypes", [Object])
- ], AnimatedEnvironmentLayer);
- return AnimatedEnvironmentLayer;
- }(asd.declared(GraphicsLayer)));
- var Windy = /** @class */ (function () {
- function Windy(canvas, options, data) {
- this.NULL_WIND_VECTOR = [NaN, NaN, null]; // singleton for no wind in the form: [u, v, magnitude]
- this.canvas = canvas;
- this.setDisplayOptions(options);
- this.gridData = data;
- }
- Windy.prototype.setData = function (data) {
- this.gridData = data;
- };
- Windy.prototype.setDisplayOptions = function (options) {
- this.displayOptions = options;
- // setup some defaults
- this.displayOptions.minVelocity = this.displayOptions.minVelocity || 0;
- this.displayOptions.maxVelocity = this.displayOptions.maxVelocity || 10;
- this.displayOptions.particleDensity = this.displayOptions.particleDensity || 10;
- this.calculatedDensity = Array.isArray(this.displayOptions.particleDensity) ? 10 : this.displayOptions.particleDensity;
- this.displayOptions.velocityScale = (this.displayOptions.velocityScale || 0.005) * (Math.pow(window.devicePixelRatio, 1 / 3) || 1); // scale for velocity (completely arbitrary -- this value looks nice)
- this.displayOptions.particleAge = this.displayOptions.particleAge || 90;
- this.displayOptions.lineWidth = this.displayOptions.lineWidth || 1;
- this.displayOptions.particleReduction = this.displayOptions.particleReduction || (Math.pow(window.devicePixelRatio, 1 / 3) || 1.6); // multiply particle count for mobiles by this amount
- this.displayOptions.frameRate = this.displayOptions.frameRate || 15;
- var defaultColorScale = ["rgb(61,160,247)", "rgb(99,164,217)", "rgb(138,168,188)", "rgb(177,173,158)", "rgb(216,177,129)", "rgb(255,182,100)", "rgb(240,145,87)", "rgb(225,109,74)", "rgb(210,72,61)", "rgb(195,36,48)", "rgb(180,0,35)"];
- this.colorScale = this.displayOptions.colorScale || defaultColorScale;
- this.FRAME_TIME = 1000 / this.displayOptions.frameRate; // desired frames per second
- };
- Windy.prototype.start = function (bounds, width, height, extent) {
- var _this = this;
- var mapBounds = {
- south: this.deg2rad(extent[0][1]),
- north: this.deg2rad(extent[1][1]),
- east: this.deg2rad(extent[1][0]),
- west: this.deg2rad(extent[0][0]),
- width: width,
- height: height
- };
- this.stop();
- // build grid
- this.buildGrid(this.gridData, function (gridResult) {
- var builtBounds = _this.buildBounds(bounds, width, height);
- _this.interpolateField(gridResult, builtBounds, mapBounds, function (bounds, field) {
- // animate the canvas with random points
- Windy.field = field;
- _this.animate(bounds, Windy.field);
- });
- });
- };
- Windy.prototype.stop = function () {
- if (Windy.field) {
- Windy.field.release();
- }
- if (Windy.animationLoop) {
- cancelAnimationFrame(Windy.animationLoop);
- }
- };
- /**
- * Get interpolated grid value from Lon/Lat position
- * @param lon {Float} Longitude