>;
5 |
6 | constructor() {
7 | this.listeners = new Map([
8 | ['drawStart', new Set()],
9 | ['drawUpdate', new Set()],
10 | ['drawEnd', new Set()],
11 | ['editStart', new Set()],
12 | ['editEnd', new Set()],
13 | ]);
14 | }
15 |
16 | on(event: EventType, listener: EventListener) {
17 | if (!this.listeners.has(event)) {
18 | console.warn("Event binding must be one of 'drawStart', 'drawUpdate', or 'drawEnd'.");
19 | return;
20 | }
21 | this.listeners.get(event)!.add(listener);
22 | }
23 |
24 | off(event: EventType, listener: EventListener) {
25 | if (this.listeners.has(event)) {
26 | this.listeners.get(event)!.delete(listener);
27 | }
28 | }
29 |
30 | dispatchEvent(event: EventType, eventData?: any) {
31 | if (this.listeners.has(event)) {
32 | this.listeners.get(event)!.forEach((listener) => {
33 | listener(eventData);
34 | });
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 ethan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cesium-plot-js",
3 | "version": "0.0.6",
4 | "main": "dist/CesiumPlot.umd.js",
5 | "homepage": "https://github.com/ethan-zf/cesium-plot-js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git@github.com:ethan-zf/cesium-plot-js.git"
9 | },
10 | "files": [
11 | "dist/",
12 | "README.md",
13 | "package.json"
14 | ],
15 | "keywords": [
16 | "cesium-plot",
17 | "cesium-draw",
18 | "cesium",
19 | "draw",
20 | "plot",
21 | "map"
22 | ],
23 | "scripts": {
24 | "dev": "vite --mode dev",
25 | "build": "vite build --mode prod",
26 | "lint": "eslint . --ext ts --report-unused-disable-directives --max-warnings 0",
27 | "preview": "vite preview"
28 | },
29 | "devDependencies": {
30 | "@types/lodash.clonedeep": "^4.5.9",
31 | "@types/lodash.merge": "^4.6.9",
32 | "@typescript-eslint/eslint-plugin": "^6.0.0",
33 | "@typescript-eslint/parser": "^6.0.0",
34 | "cesium": "1.99.0",
35 | "eslint": "^8.45.0",
36 | "typescript": "^5.0.2",
37 | "vite": "^4.4.5"
38 | },
39 | "dependencies": {
40 | "lodash.clonedeep": "^4.5.0",
41 | "lodash.merge": "^4.6.2"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/line/freehand-line.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | // @ts-ignore
3 | import { Cartesian3 } from 'cesium';
4 | import { PolygonStyle } from '../interface';
5 |
6 | export default class FreehandLine extends Base {
7 | points: Cartesian3[] = [];
8 | freehand: boolean;
9 |
10 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
11 | super(cesium, viewer, style);
12 | this.cesium = cesium;
13 | this.freehand = true;
14 | this.setState('drawing');
15 | }
16 |
17 | getType(): 'polygon' | 'line' {
18 | return 'line';
19 | }
20 |
21 | /**
22 | * Add points only on click events
23 | */
24 | addPoint(cartesian: Cartesian3) {
25 | this.points.push(cartesian);
26 | if (this.points.length < 2) {
27 | this.onMouseMove();
28 | } else {
29 | this.finishDrawing();
30 | }
31 | }
32 |
33 | /**
34 | * Draw a shape based on mouse movement points during the initial drawing.
35 | */
36 | updateMovingPoint(cartesian: Cartesian3) {
37 | this.points.push(cartesian);
38 | this.setGeometryPoints(this.points);
39 | this.drawLine();
40 | this.eventDispatcher.dispatchEvent('drawUpdate', cartesian);
41 | }
42 |
43 | /**
44 | * In edit mode, drag key points to update corresponding key point data.
45 | */
46 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
47 | this.points[index] = cartesian;
48 | this.setGeometryPoints(this.points);
49 | this.drawLine();
50 | }
51 |
52 | getPoints() {
53 | return this.points;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/polygon/polygon.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | // @ts-ignore
3 | import { Cartesian3 } from 'cesium';
4 |
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class Polygon extends Base {
8 | points: Cartesian3[] = [];
9 |
10 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
11 | super(cesium, viewer, style);
12 | this.cesium = cesium;
13 | this.setState('drawing');
14 | this.onDoubleClick();
15 | }
16 |
17 | getType(): 'polygon' | 'line' {
18 | return 'polygon';
19 | }
20 |
21 | /**
22 | * Add points only on click events
23 | */
24 | addPoint(cartesian: Cartesian3) {
25 | this.points.push(cartesian);
26 | if (this.points.length === 1) {
27 | this.onMouseMove();
28 | }
29 | }
30 |
31 | /**
32 | * Draw a shape based on mouse movement points during the initial drawing.
33 | */
34 | updateMovingPoint(cartesian: Cartesian3) {
35 | const tempPoints = [...this.points, cartesian];
36 | this.setGeometryPoints(tempPoints);
37 | if (tempPoints.length === 2) {
38 | this.addTempLine();
39 | } else {
40 | this.removeTempLine();
41 | this.drawPolygon();
42 | }
43 | }
44 |
45 | /**
46 | * In edit mode, drag key points to update corresponding key point data.
47 | */
48 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
49 | this.points[index] = cartesian;
50 | this.setGeometryPoints(this.points);
51 | this.drawPolygon();
52 | }
53 |
54 | getPoints() {
55 | return this.points;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/polygon/triangle.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | // @ts-ignore
3 | import { Cartesian3 } from 'cesium';
4 |
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class Triangle extends Base {
8 | points: Cartesian3[] = [];
9 |
10 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
11 | super(cesium, viewer, style);
12 | this.cesium = cesium;
13 | this.setState('drawing');
14 | }
15 |
16 | getType(): 'polygon' | 'line' {
17 | return 'polygon';
18 | }
19 |
20 | /**
21 | * Add points only on click events
22 | */
23 | addPoint(cartesian: Cartesian3) {
24 | this.points.push(cartesian);
25 | if (this.points.length === 1) {
26 | this.onMouseMove();
27 | } else if (this.points.length === 3) {
28 | this.finishDrawing();
29 | }
30 | }
31 |
32 | /**
33 | * Draw a shape based on mouse movement points during the initial drawing.
34 | */
35 | updateMovingPoint(cartesian: Cartesian3) {
36 | const tempPoints = [...this.points, cartesian];
37 | this.setGeometryPoints(tempPoints);
38 | if (tempPoints.length === 2) {
39 | this.addTempLine();
40 | } else {
41 | this.removeTempLine();
42 | this.drawPolygon();
43 | }
44 | }
45 |
46 | /**
47 | * In edit mode, drag key points to update corresponding key point data.
48 | */
49 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
50 | this.points[index] = cartesian;
51 | this.setGeometryPoints(this.points);
52 | this.drawPolygon();
53 | }
54 |
55 | getPoints() {
56 | return this.points;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.0.6
2 |
3 | ---
4 |
5 | #### ✨ New Features
6 |
7 | - 新增: 绘制扇形
8 |
9 | - 新增: 根据数据回显图形
10 |
11 | - 新增: 获取图形关键点位方法:`getPoints`
12 |
13 |
14 | #### 🐞 Bug fixes
15 |
16 | - 修复:绘制过程中临时创建的线没有被删除的问题
17 |
18 | - 修复:双击控制点导致图形无法拖拽的问题
19 |
20 | - 修复:双箭头执行生长动画后,编辑状态无法拖拽的问题
21 |
22 | - 修复:双箭头整体被拖拽后生长动画路径不正确的问题
23 |
24 |
25 | ## 0.0.5
26 |
27 | ---
28 |
29 | #### ✨ New Features
30 |
31 | - Add: Fade-in Fade-out Animation for Graphics
32 |
33 | - Add: Growing Animation for Arrow-shaped Graphics
34 |
35 | #### ⚠️ Breaking changes
36 |
37 | - Set Default Style to Cesium Default Style
38 |
39 | #### 🐞 Bug fixes
40 |
41 | - Disable Cesium's Auto-Centering Viewport Behavior on Double Click
42 |
43 |
44 | ## 0.0.4
45 |
46 | ---
47 |
48 | #### ✨ New Features
49 |
50 | - Adding ES Module package to the published package
51 |
52 | #### 🐞 Bug fixes
53 |
54 | - Fix the issue where polygons and triangles cannot delete their outer edges when removed.
55 |
56 | ### 0.0.3
57 |
58 | ---
59 |
60 | #### ✨ New Features
61 |
62 | - Publish to CDN and NPM
63 |
64 | ## 0.0.2
65 |
66 | ---
67 |
68 | #### ✨ New Features
69 |
70 | - Adding graphical drawing capabilities:
71 | Triangle
72 | Polygon
73 | Circle
74 |
75 | ## 0.0.1
76 |
77 | ---
78 |
79 | #### ✨ New Features
80 |
81 | - Adding graphical drawing capabilities:
82 | FineArrow
83 | AttackArrow
84 | SwallowtailAttackArrow
85 | SquadCombat
86 | SwallowtailSquadCombat
87 | StraightArrow
88 | CurvedArrow
89 | AssaultDirection
90 | DoubleArrow
91 | FreehandLine
92 | FreehandPolygon
93 | Curve
94 | Ellipse
95 | Lune
96 | Reactangle
97 |
--------------------------------------------------------------------------------
/src/polygon/freehand-polygon.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | // @ts-ignore
3 | import { Cartesian3 } from 'cesium';
4 | import { PolygonStyle } from '../interface';
5 |
6 | export default class FreehandPolygon extends Base {
7 | points: Cartesian3[] = [];
8 | freehand: boolean;
9 |
10 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
11 | super(cesium, viewer, style);
12 | this.cesium = cesium;
13 | this.freehand = true;
14 | this.setState('drawing');
15 | }
16 |
17 | getType(): 'polygon' | 'line' {
18 | return 'polygon';
19 | }
20 |
21 | /**
22 | * Add points only on click events
23 | */
24 | addPoint(cartesian: Cartesian3) {
25 | this.points.push(cartesian);
26 | if (this.points.length === 1) {
27 | this.onMouseMove();
28 | } else if (this.points.length > 2) {
29 | this.finishDrawing();
30 | }
31 | }
32 |
33 | /**
34 | * Draw a shape based on mouse movement points during the initial drawing.
35 | */
36 | updateMovingPoint(cartesian: Cartesian3) {
37 | this.points.push(cartesian);
38 | if (this.points.length > 2) {
39 | this.setGeometryPoints(this.points);
40 | this.drawPolygon();
41 | this.eventDispatcher.dispatchEvent('drawUpdate', cartesian);
42 | }
43 | }
44 |
45 | /**
46 | * In edit mode, drag key points to update corresponding key point data.
47 | */
48 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
49 | this.points[index] = cartesian;
50 | this.setGeometryPoints(this.points);
51 | this.drawPolygon();
52 | }
53 |
54 | getPoints() {
55 | return this.points;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/polygon/rectangle.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | // @ts-ignore
3 | import { Cartesian3 } from 'cesium';
4 |
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class Rectangle extends Base {
8 | points: Cartesian3[] = [];
9 |
10 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
11 | super(cesium, viewer, style);
12 | this.cesium = cesium;
13 | this.setState('drawing');
14 | }
15 |
16 | getType(): 'polygon' | 'line' {
17 | return 'polygon';
18 | }
19 |
20 | /**
21 | * Add points only on click events
22 | */
23 | addPoint(cartesian: Cartesian3) {
24 | this.points.push(cartesian);
25 | if (this.points.length === 1) {
26 | this.onMouseMove();
27 | } else if (this.points.length > 1) {
28 | this.finishDrawing();
29 | }
30 | }
31 |
32 | /**
33 | * Draw a shape based on mouse movement points during the initial drawing.
34 | */
35 | updateMovingPoint(cartesian: Cartesian3) {
36 | const tempPoints = [...this.points, cartesian];
37 | const geometryPoints = this.createGraphic(tempPoints);
38 | this.setGeometryPoints(geometryPoints);
39 | this.drawPolygon();
40 | }
41 |
42 | /**
43 | * In edit mode, drag key points to update corresponding key point data.
44 | */
45 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
46 | this.points[index] = cartesian;
47 | const geometryPoints = this.createGraphic(this.points);
48 | this.setGeometryPoints(geometryPoints);
49 | this.drawPolygon();
50 | }
51 |
52 | createGraphic(positions: Cartesian3[]) {
53 | const [p1, p2] = positions.map(this.cartesianToLnglat);
54 | const coords = [...p1, p1[0], p2[1], ...p2, p2[0], p1[1], ...p1];
55 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(coords);
56 | return cartesianPoints;
57 | }
58 |
59 | getPoints() {
60 | return this.points;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/arrow/assault-direction.ts:
--------------------------------------------------------------------------------
1 | import FineArrow from './fine-arrow';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class AssaultDirection extends FineArrow {
8 | points: Cartesian3[] = [];
9 | arrowLengthScale: number = 5;
10 | maxArrowLength: number = 2;
11 | tailWidthFactor: number;
12 | neckWidthFactor: number;
13 | headWidthFactor: number;
14 | headAngle: number;
15 | neckAngle: number;
16 | minPointsForShape: number;
17 |
18 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
19 | super(cesium, viewer, style);
20 | this.cesium = cesium;
21 | this.tailWidthFactor = 0.08;
22 | this.neckWidthFactor = 0.1;
23 | this.headWidthFactor = 0.13;
24 | this.headAngle = Math.PI / 4;
25 | this.neckAngle = Math.PI * 0.17741;
26 | this.minPointsForShape = 2;
27 | this.setState('drawing');
28 | }
29 |
30 | createGraphic(positions: Cartesian3[]) {
31 | const [p1, p2] = positions.map(this.cartesianToLnglat);
32 | const len = Utils.getBaseLength([p1, p2]) * 1.5;
33 | const tailWidth = len * this.tailWidthFactor;
34 | const neckWidth = len * this.neckWidthFactor;
35 | const headWidth = len * this.headWidthFactor;
36 | const tailLeft = Utils.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, true);
37 | const tailRight = Utils.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, false);
38 | const headLeft = Utils.getThirdPoint(p1, p2, this.headAngle, headWidth, false);
39 | const headRight = Utils.getThirdPoint(p1, p2, this.headAngle, headWidth, true);
40 | const neckLeft = Utils.getThirdPoint(p1, p2, this.neckAngle, neckWidth, false);
41 | const neckRight = Utils.getThirdPoint(p1, p2, this.neckAngle, neckWidth, true);
42 | const points = [...tailLeft, ...neckLeft, ...headLeft, ...p2, ...headRight, ...neckRight, ...tailRight, ...p1];
43 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(points);
44 | return cartesianPoints;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import FineArrow from './arrow/fine-arrow';
2 | import AttackArrow from './arrow/attack-arrow';
3 | import SwallowtailAttackArrow from './arrow/swallowtail-attack-arrow';
4 | import SquadCombat from './arrow/squad-combat';
5 | import SwallowtailSquadCombat from './arrow/swallowtail-squad-combat';
6 | import StraightArrow from './arrow/straight-arrow';
7 | import CurvedArrow from './arrow/curved-arrow';
8 | import AssaultDirection from './arrow/assault-direction';
9 | import DoubleArrow from './arrow/double-arrow';
10 | import FreehandLine from './line/freehand-line';
11 | import FreehandPolygon from './polygon/freehand-polygon';
12 | import Curve from './line/curve';
13 | import Ellipse from './polygon/ellipse';
14 | import Lune from './polygon/lune';
15 | import Reactangle from './polygon/rectangle';
16 | import Triangle from './polygon/triangle';
17 | import Polygon from './polygon/polygon';
18 | import Circle from './polygon/circle';
19 | import Sector from './polygon/sector';
20 |
21 | import { GeometryStyle } from './interface';
22 | import * as CesiumTypeOnly from 'cesium';
23 |
24 | const CesiumPlot: any = {
25 | FineArrow,
26 | AttackArrow,
27 | SwallowtailAttackArrow,
28 | SquadCombat,
29 | SwallowtailSquadCombat,
30 | StraightArrow,
31 | CurvedArrow,
32 | AssaultDirection,
33 | DoubleArrow,
34 | FreehandLine,
35 | FreehandPolygon,
36 | Curve,
37 | Ellipse,
38 | Lune,
39 | Reactangle,
40 | Triangle,
41 | Polygon,
42 | Circle,
43 | Sector,
44 | };
45 |
46 | type CreateGeometryFromDataOpts = {
47 | type: string;
48 | cartesianPoints: CesiumTypeOnly.Cartesian3[];
49 | style: GeometryStyle;
50 | };
51 | /**
52 | * 根据点位数据生成几何图形
53 | * @param points
54 | */
55 | CesiumPlot.createGeometryFromData = (cesium: any, viewer: any, opts: CreateGeometryFromDataOpts) => {
56 | const { type, style, cartesianPoints } = opts;
57 | const geometry = new CesiumPlot[type](cesium, viewer, style);
58 |
59 | geometry.points = cartesianPoints;
60 | const geometryPoints = geometry.createGraphic(cartesianPoints);
61 | geometry.setGeometryPoints(geometryPoints);
62 | if (geometry.type == 'polygon') {
63 | geometry.drawPolygon();
64 | } else {
65 | geometry.drawLine();
66 | }
67 | geometry.finishDrawing();
68 | geometry.onClick();
69 | return geometry;
70 | };
71 |
72 | export default CesiumPlot;
73 |
--------------------------------------------------------------------------------
/src/line/curve.ts:
--------------------------------------------------------------------------------
1 | import * as Utils from '../utils';
2 | import Base from '../base';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'kmap-3d-engine';
5 | import { LineStyle } from '../interface';
6 |
7 | export default class Curve extends Base {
8 | points: Cartesian3[] = [];
9 | arrowLengthScale: number = 5;
10 | maxArrowLength: number = 3000000;
11 | t: number;
12 |
13 | constructor(cesium: any, viewer: any, style?: LineStyle) {
14 | super(cesium, viewer, style);
15 | this.cesium = cesium;
16 | this.t = 0.3;
17 | this.setState('drawing');
18 | this.onDoubleClick();
19 | }
20 |
21 | getType(): 'polygon' | 'line' {
22 | return 'line';
23 | }
24 |
25 | /**
26 | * Points are only added upon click events.
27 | */
28 | addPoint(cartesian: Cartesian3) {
29 | this.points.push(cartesian);
30 | if (this.points.length < 2) {
31 | this.onMouseMove();
32 | } else if (this.points.length === 2) {
33 | this.setGeometryPoints(this.points);
34 | this.drawLine();
35 | }
36 | }
37 |
38 | /**
39 | * Draw the shape based on the mouse movement position during the initial drawing.
40 | */
41 | updateMovingPoint(cartesian: Cartesian3) {
42 | const tempPoints = [...this.points, cartesian];
43 | let geometryPoints = [];
44 | if (tempPoints.length === 2) {
45 | this.setGeometryPoints(tempPoints);
46 | this.drawLine();
47 | } else {
48 | geometryPoints = this.createGraphic(tempPoints);
49 | this.setGeometryPoints(geometryPoints);
50 | }
51 | }
52 |
53 | /**
54 | * During editing mode, drag key points to update the corresponding data.
55 | */
56 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
57 | this.points[index] = cartesian;
58 | const geometryPoints = this.createGraphic(this.points);
59 | this.setGeometryPoints(geometryPoints);
60 | this.drawLine();
61 | }
62 |
63 | /**
64 | * Generate geometric shape points based on key points..
65 | */
66 | createGraphic(positions: Cartesian3[]) {
67 | const lnglatPoints = positions.map(pnt => {
68 | return this.cartesianToLnglat(pnt);
69 | });
70 |
71 | const curvePoints = Utils.getCurvePoints(this.t, lnglatPoints);
72 | const temp = [].concat(...curvePoints);
73 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
74 | return cartesianPoints;
75 | }
76 |
77 | getPoints() {
78 | return this.points;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/polygon/sector.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | // @ts-ignore
3 | import { Cartesian3 } from 'cesium';
4 | import * as Utils from '../utils';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class Sector extends Base {
8 | points: Cartesian3[] = [];
9 |
10 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
11 | super(cesium, viewer, style);
12 | this.cesium = cesium;
13 | this.setState('drawing');
14 | }
15 |
16 | getType(): 'polygon' | 'line' {
17 | return 'polygon';
18 | }
19 |
20 | /**
21 | * Add points only on click events
22 | */
23 | addPoint(cartesian: Cartesian3) {
24 | this.points.push(cartesian);
25 | if (this.points.length === 1) {
26 | this.onMouseMove();
27 | }else if (this.points.length === 3) {
28 | this.finishDrawing();
29 | }
30 | }
31 |
32 | /**
33 | * Draw a shape based on mouse movement points during the initial drawing.
34 | */
35 | updateMovingPoint(cartesian: Cartesian3) {
36 | const tempPoints = [...this.points, cartesian];
37 | this.setGeometryPoints(tempPoints);
38 | if (tempPoints.length === 2) {
39 | this.addTempLine();
40 | } else {
41 | this.removeTempLine();
42 | const geometryPoints = this.createGraphic(tempPoints);
43 | this.setGeometryPoints(geometryPoints);
44 | this.drawPolygon();
45 | }
46 | }
47 |
48 | createGraphic(positions: Cartesian3[]) {
49 | const lnglatPoints = positions.map((pnt) => {
50 | return this.cartesianToLnglat(pnt);
51 | });
52 | const [center, pnt2, pnt3] = [lnglatPoints[0], lnglatPoints[1], lnglatPoints[2]];
53 | const radius = Utils.MathDistance(pnt2, center);
54 | const startAngle = Utils.getAzimuth(pnt2, center);
55 | const endAngle = Utils.getAzimuth(pnt3, center);
56 | const res = Utils.getArcPoints(center, radius, startAngle, endAngle);
57 | res.push(center, res[0]);
58 |
59 | const temp = [].concat(...res);
60 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
61 | return cartesianPoints;
62 | }
63 |
64 | /**
65 | * In edit mode, drag key points to update corresponding key point data.
66 | */
67 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
68 | this.points[index] = cartesian;
69 | const geometryPoints = this.createGraphic(this.points);
70 | this.setGeometryPoints(geometryPoints);
71 | this.drawPolygon();
72 | }
73 |
74 | getPoints() {
75 | return this.points;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/polygon/circle.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 |
6 | import { PolygonStyle } from '../interface';
7 |
8 | export default class Circle extends Base {
9 | points: Cartesian3[] = [];
10 | freehand: boolean;
11 |
12 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
13 | super(cesium, viewer, style);
14 | this.cesium = cesium;
15 | this.freehand = true;
16 | this.setState('drawing');
17 | }
18 |
19 | getType(): 'polygon' | 'line' {
20 | return 'polygon';
21 | }
22 |
23 | /**
24 | * Add points only on click events
25 | */
26 | addPoint(cartesian: Cartesian3) {
27 | this.points.push(cartesian);
28 | if (this.points.length === 1) {
29 | this.onMouseMove();
30 | } else if (this.points.length > 1) {
31 | this.finishDrawing();
32 | }
33 | }
34 |
35 | /**
36 | * Draw a shape based on mouse movement points during the initial drawing.
37 | */
38 | updateMovingPoint(cartesian: Cartesian3) {
39 | const tempPoints = [...this.points, cartesian];
40 | const geometryPoints = this.createGraphic(tempPoints);
41 | this.setGeometryPoints(geometryPoints);
42 | this.drawPolygon();
43 | }
44 |
45 | /**
46 | * In edit mode, drag key points to update corresponding key point data.
47 | */
48 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
49 | this.points[index] = cartesian;
50 | const geometryPoints = this.createGraphic(this.points);
51 | this.setGeometryPoints(geometryPoints);
52 | this.drawPolygon();
53 | }
54 |
55 | createGraphic(positions: Cartesian3[]) {
56 | const lnglatPoints = positions.map((pnt) => {
57 | return this.cartesianToLnglat(pnt);
58 | });
59 | const center = lnglatPoints[0];
60 | const pnt2 = lnglatPoints[1];
61 |
62 | const radius = Utils.MathDistance(center, pnt2);
63 |
64 | const res = this.generatePoints(center, radius);
65 | const temp = [].concat(...res);
66 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
67 | return cartesianPoints;
68 | }
69 |
70 | generatePoints(center, radius) {
71 | let x, y, angle;
72 | const points = [];
73 | for (let i = 0; i <= 100; i++) {
74 | angle = (Math.PI * 2 * i) / 100;
75 | x = center[0] + radius * Math.cos(angle);
76 | y = center[1] + radius * Math.sin(angle);
77 | points.push([x, y]);
78 | }
79 | return points;
80 | }
81 |
82 | getPoints() {
83 | return this.points;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/arrow/straight-arrow.ts:
--------------------------------------------------------------------------------
1 | import * as Utils from '../utils';
2 | import Base from '../base';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { LineStyle } from '../interface';
6 |
7 | export default class StraightArrow extends Base {
8 | points: Cartesian3[] = [];
9 | arrowLengthScale: number = 5;
10 | maxArrowLength: number = 3000000;
11 | minPointsForShape: number;
12 |
13 | constructor(cesium: any, viewer: any, style?: LineStyle) {
14 | super(cesium, viewer, style);
15 | this.cesium = cesium;
16 | this.minPointsForShape = 2;
17 | this.setState('drawing');
18 | }
19 |
20 | getType(): 'polygon' | 'line' {
21 | return 'line';
22 | }
23 |
24 | /**
25 | * Add points only on click events
26 | */
27 | addPoint(cartesian: Cartesian3) {
28 | if (this.points.length < 2) {
29 | this.points.push(cartesian);
30 | this.onMouseMove();
31 | }
32 | if (this.points.length === 2) {
33 | const geometryPoints = this.createGraphic(this.points);
34 | this.setGeometryPoints(geometryPoints);
35 | this.drawLine();
36 | this.finishDrawing();
37 | }
38 | }
39 |
40 | /**
41 | * Draw a shape based on mouse movement points during the initial drawing.
42 | */
43 | updateMovingPoint(cartesian: Cartesian3) {
44 | const tempPoints = [...this.points, cartesian];
45 | const geometryPoints = this.createGraphic(tempPoints);
46 | this.setGeometryPoints(geometryPoints);
47 | this.drawLine();
48 | }
49 |
50 | /**
51 | * In edit mode, drag key points to update corresponding key point data.
52 | */
53 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
54 | this.points[index] = cartesian;
55 | const geometryPoints = this.createGraphic(this.points);
56 | this.setGeometryPoints(geometryPoints);
57 | this.drawLine();
58 | }
59 |
60 | /**
61 | * Generate geometric shapes based on key points.
62 | */
63 | createGraphic(positions: Cartesian3[]) {
64 | const [pnt1, pnt2] = positions.map(this.cartesianToLnglat);
65 | const distance = Utils.MathDistance(pnt1, pnt2);
66 | let len = distance / this.arrowLengthScale;
67 | len = len > this.maxArrowLength ? this.maxArrowLength : len;
68 | const leftPnt = Utils.getThirdPoint(pnt1, pnt2, Math.PI / 6, len / 2, false);
69 | const rightPnt = Utils.getThirdPoint(pnt1, pnt2, Math.PI / 6, len / 2, true);
70 | const points = [...pnt1, ...pnt2, ...leftPnt, ...pnt2, ...rightPnt];
71 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(points);
72 | return cartesianPoints;
73 | }
74 |
75 | getPoints() {
76 | return this.points;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/arrow/swallowtail-squad-combat.ts:
--------------------------------------------------------------------------------
1 | import * as Utils from '../utils';
2 | import SquadCombat from './squad-combat';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class SwallowtailSquadCombat extends SquadCombat {
8 | points: Cartesian3[] = [];
9 | headHeightFactor: number;
10 | headWidthFactor: number;
11 | neckHeightFactor: number;
12 | neckWidthFactor: number;
13 | tailWidthFactor: number;
14 | swallowTailFactor: number;
15 |
16 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
17 | super(cesium, viewer, style);
18 |
19 | this.cesium = cesium;
20 | this.headHeightFactor = 0.18;
21 | this.headWidthFactor = 0.3;
22 | this.neckHeightFactor = 0.85;
23 | this.neckWidthFactor = 0.15;
24 | this.tailWidthFactor = 0.1;
25 | this.swallowTailFactor = 1;
26 | this.minPointsForShape = 2;
27 | }
28 |
29 | /**
30 | * Generate geometric shapes based on key points.
31 | */
32 | createGraphic(positions: Cartesian3[]): Cartesian3[] {
33 | const lnglatPoints = positions.map((pnt) => {
34 | return this.cartesianToLnglat(pnt);
35 | });
36 |
37 | const tailPnts = this.getTailPoints(lnglatPoints);
38 | const headPnts = this.getArrowHeadPoints(lnglatPoints, tailPnts[0], tailPnts[2]);
39 | const neckLeft = headPnts[0];
40 | const neckRight = headPnts[4];
41 | const bodyPnts = this.getArrowBodyPoints(lnglatPoints, neckLeft, neckRight, this.tailWidthFactor);
42 | const count = bodyPnts.length;
43 | let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
44 | leftPnts.push(neckLeft);
45 | let rightPnts = [tailPnts[2]].concat(bodyPnts.slice(count / 2, count));
46 | rightPnts.push(neckRight);
47 | leftPnts = Utils.getQBSplinePoints(leftPnts);
48 | rightPnts = Utils.getQBSplinePoints(rightPnts);
49 |
50 | const points = leftPnts.concat(headPnts, rightPnts.reverse(), [tailPnts[1], leftPnts[0]]);
51 | const temp = [].concat(...points);
52 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
53 | return cartesianPoints;
54 | }
55 |
56 | getTailPoints(points) {
57 | const allLen = Utils.getBaseLength(points);
58 | const tailWidth = allLen * this.tailWidthFactor;
59 | const tailLeft = Utils.getThirdPoint(points[1], points[0], Math.PI / 2, tailWidth, false);
60 | const tailRight = Utils.getThirdPoint(points[1], points[0], Math.PI / 2, tailWidth, true);
61 | const len = tailWidth * this.swallowTailFactor;
62 | const swallowTailPnt = Utils.getThirdPoint(points[1], points[0], 0, len, true);
63 | return [tailLeft, swallowTailPnt, tailRight];
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | CesiumPlot
8 |
9 |
10 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/polygon/ellipse.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 |
6 | import { PolygonStyle } from '../interface';
7 |
8 | export default class Ellipse extends Base {
9 | points: Cartesian3[] = [];
10 | freehand: boolean;
11 |
12 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
13 | super(cesium, viewer, style);
14 | this.cesium = cesium;
15 | this.freehand = true;
16 | this.setState('drawing');
17 | }
18 |
19 | getType(): 'polygon' | 'line' {
20 | return 'polygon';
21 | }
22 |
23 | /**
24 | * Add points only on click events
25 | */
26 | addPoint(cartesian: Cartesian3) {
27 | this.points.push(cartesian);
28 | if (this.points.length === 1) {
29 | this.onMouseMove();
30 | } else if (this.points.length > 1) {
31 | this.finishDrawing();
32 | }
33 | }
34 |
35 | /**
36 | * Draw a shape based on mouse movement points during the initial drawing.
37 | */
38 | updateMovingPoint(cartesian: Cartesian3) {
39 | const tempPoints = [...this.points, cartesian];
40 | const geometryPoints = this.createGraphic(tempPoints);
41 | this.setGeometryPoints(geometryPoints);
42 | this.drawPolygon();
43 | }
44 |
45 | /**
46 | * In edit mode, drag key points to update corresponding key point data.
47 | */
48 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
49 | this.points[index] = cartesian;
50 | const geometryPoints = this.createGraphic(this.points);
51 | this.setGeometryPoints(geometryPoints);
52 | this.drawPolygon();
53 | }
54 |
55 | createGraphic(positions: Cartesian3[]) {
56 | const lnglatPoints = positions.map((pnt) => {
57 | return this.cartesianToLnglat(pnt);
58 | });
59 | const pnt1 = lnglatPoints[0];
60 | const pnt2 = lnglatPoints[1];
61 |
62 | const center = Utils.Mid(pnt1, pnt2);
63 | const majorRadius = Math.abs((pnt1[0] - pnt2[0]) / 2);
64 | const minorRadius = Math.abs((pnt1[1] - pnt2[1]) / 2);
65 | const res = this.generatePoints(center, majorRadius, minorRadius);
66 | const temp = [].concat(...res);
67 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
68 | return cartesianPoints;
69 | }
70 |
71 | generatePoints(center, majorRadius, minorRadius) {
72 | let [x, y, angle, points] = [null, null, 0, []];
73 | for (let i = 0; i <= 100; i++) {
74 | angle = (Math.PI * 2 * i) / 100;
75 | x = center[0] + majorRadius * Math.cos(angle);
76 | y = center[1] + minorRadius * Math.sin(angle);
77 | points.push([x, y]);
78 | }
79 | return points;
80 | }
81 |
82 | getPoints() {
83 | return this.points;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/arrow/swallowtail-attack-arrow.ts:
--------------------------------------------------------------------------------
1 | import * as Utils from '../utils';
2 | import AttackArrow from './attack-arrow';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class SwallowtailAttackArrow extends AttackArrow {
8 | points: Cartesian3[] = [];
9 | headHeightFactor: number;
10 | headWidthFactor: number;
11 | neckHeightFactor: number;
12 | neckWidthFactor: number;
13 | headTailFactor: number;
14 | tailWidthFactor: number;
15 | swallowTailFactor: number;
16 | swallowTailPnt: [number, number];
17 |
18 | constructor(cesium: any, viewer: any, style: PolygonStyle) {
19 | super(cesium, viewer, style);
20 | this.cesium = cesium;
21 | this.headHeightFactor = 0.18;
22 | this.headWidthFactor = 0.3;
23 | this.neckHeightFactor = 0.85;
24 | this.neckWidthFactor = 0.15;
25 | this.tailWidthFactor = 0.1;
26 | this.headTailFactor = 0.8;
27 | this.swallowTailFactor = 1;
28 | this.swallowTailPnt = [0, 0];
29 | this.minPointsForShape = 3;
30 | }
31 |
32 | /**
33 | * Generate geometric shapes based on key points.
34 | */
35 | createGraphic(positions: Cartesian3[]): Cartesian3[] {
36 | const lnglatPoints = positions.map((pnt) => {
37 | return this.cartesianToLnglat(pnt);
38 | });
39 | let [tailLeft, tailRight] = [lnglatPoints[0], lnglatPoints[1]];
40 | if (Utils.isClockWise(lnglatPoints[0], lnglatPoints[1], lnglatPoints[2])) {
41 | tailLeft = lnglatPoints[1];
42 | tailRight = lnglatPoints[0];
43 | }
44 | const midTail = Utils.Mid(tailLeft, tailRight);
45 | const bonePnts = [midTail].concat(lnglatPoints.slice(2));
46 | const headPnts = this.getArrowHeadPoints(bonePnts, tailLeft, tailRight);
47 | const [neckLeft, neckRight] = [headPnts[0], headPnts[4]];
48 | const tailWidth = Utils.MathDistance(tailLeft, tailRight);
49 | const allLen = Utils.getBaseLength(bonePnts);
50 | const len = allLen * this.tailWidthFactor * this.swallowTailFactor;
51 | this.swallowTailPnt = Utils.getThirdPoint(bonePnts[1], bonePnts[0], 0, len, true);
52 | const factor = tailWidth / allLen;
53 | const bodyPnts = this.getArrowBodyPoints(bonePnts, neckLeft, neckRight, factor);
54 | const count = bodyPnts.length;
55 | let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
56 | leftPnts.push(neckLeft);
57 | let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
58 | rightPnts.push(neckRight);
59 | leftPnts = Utils.getQBSplinePoints(leftPnts);
60 | rightPnts = Utils.getQBSplinePoints(rightPnts);
61 | const points = leftPnts.concat(headPnts, rightPnts.reverse(), [this.swallowTailPnt, leftPnts[0]]);
62 | const temp = [].concat(...points);
63 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
64 | return cartesianPoints;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CesiumPlot
7 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/polygon/lune.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class Lune extends Base {
8 | points: Cartesian3[] = [];
9 | freehand: boolean;
10 |
11 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
12 | super(cesium, viewer, style);
13 | this.cesium = cesium;
14 | this.freehand = true;
15 | this.setState('drawing');
16 | }
17 |
18 | getType(): 'polygon' | 'line' {
19 | return 'polygon';
20 | }
21 |
22 | /**
23 | * Add points only on click events
24 | */
25 | addPoint(cartesian: Cartesian3) {
26 | this.points.push(cartesian);
27 | if (this.points.length === 1) {
28 | this.onMouseMove();
29 | } else if (this.points.length === 2) {
30 | } else if (this.points.length > 2) {
31 | this.finishDrawing();
32 | }
33 | }
34 |
35 | /**
36 | * Draw a shape based on mouse movement points during the initial drawing.
37 | */
38 | updateMovingPoint(cartesian: Cartesian3) {
39 | const tempPoints = [...this.points, cartesian];
40 | const geometryPoints = this.createGraphic(tempPoints);
41 | this.setGeometryPoints(geometryPoints);
42 | this.drawPolygon();
43 | }
44 |
45 | /**
46 | * In edit mode, drag key points to update corresponding key point data.
47 | */
48 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
49 | this.points[index] = cartesian;
50 | const geometryPoints = this.createGraphic(this.points);
51 | this.setGeometryPoints(geometryPoints);
52 | this.drawPolygon();
53 | }
54 |
55 | createGraphic(positions: Cartesian3[]) {
56 | const lnglatPoints = positions.map((pnt) => {
57 | return this.cartesianToLnglat(pnt);
58 | });
59 |
60 | if (lnglatPoints.length === 2) {
61 | const mid = Utils.Mid(lnglatPoints[0], lnglatPoints[1]);
62 | const d = Utils.MathDistance(lnglatPoints[0], mid);
63 | const pnt = Utils.getThirdPoint(lnglatPoints[0], mid, Math.PI / 2, d, false);
64 | lnglatPoints.push(pnt);
65 | }
66 | let [pnt1, pnt2, pnt3, startAngle, endAngle] = [
67 | lnglatPoints[0],
68 | lnglatPoints[1],
69 | lnglatPoints[2],
70 | undefined,
71 | undefined,
72 | ];
73 | const center = Utils.getCircleCenterOfThreePoints(pnt1, pnt2, pnt3);
74 | const radius = Utils.MathDistance(pnt1, center);
75 | const angle1 = Utils.getAzimuth(pnt1, center);
76 | const angle2 = Utils.getAzimuth(pnt2, center);
77 | if (Utils.isClockWise(pnt1, pnt2, pnt3)) {
78 | startAngle = angle2;
79 | endAngle = angle1;
80 | } else {
81 | startAngle = angle1;
82 | endAngle = angle2;
83 | }
84 |
85 | let points = Utils.getArcPoints(center, radius, startAngle, endAngle);
86 | const temp = [].concat(...points);
87 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
88 | return cartesianPoints;
89 | }
90 |
91 | getPoints() {
92 | return this.points;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/arrow/squad-combat.ts:
--------------------------------------------------------------------------------
1 | import * as Utils from '../utils';
2 | import AttackArrow from './attack-arrow';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class SquadCombat extends AttackArrow {
8 | points: Cartesian3[] = [];
9 | headHeightFactor: number;
10 | headWidthFactor: number;
11 | neckHeightFactor: number;
12 | neckWidthFactor: number;
13 | tailWidthFactor: number;
14 |
15 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
16 | super(cesium, viewer, style);
17 | this.cesium = cesium;
18 | this.headHeightFactor = 0.18;
19 | this.headWidthFactor = 0.3;
20 | this.neckHeightFactor = 0.85;
21 | this.neckWidthFactor = 0.15;
22 | this.tailWidthFactor = 0.1;
23 | this.minPointsForShape = 2;
24 | }
25 |
26 | /**
27 | * Add points only on click events
28 | */
29 | addPoint(cartesian: Cartesian3) {
30 | this.points.push(cartesian);
31 | if (this.points.length < 2) {
32 | this.onMouseMove();
33 | } else if (this.points.length > 2) {
34 | this.lineEntity && this.viewer.entities.remove(this.lineEntity);
35 | }
36 | }
37 |
38 | /**
39 | * Draw a shape based on mouse movement points during the initial drawing.
40 | */
41 | updateMovingPoint(cartesian: Cartesian3) {
42 | const tempPoints = [...this.points, cartesian];
43 | this.setGeometryPoints(tempPoints);
44 | if (tempPoints.length < 2) {
45 | return;
46 | } else {
47 | const geometryPoints = this.createGraphic(tempPoints);
48 | this.setGeometryPoints(geometryPoints);
49 | this.drawPolygon();
50 | }
51 | }
52 |
53 | /**
54 | * Generate geometric shapes based on key points.
55 | */
56 | createGraphic(positions: Cartesian3[]): Cartesian3[] {
57 | const lnglatPoints = positions.map((pnt) => {
58 | return this.cartesianToLnglat(pnt);
59 | });
60 | const tailPnts = this.getTailPoints(lnglatPoints);
61 | const headPnts = this.getArrowHeadPoints(lnglatPoints, tailPnts[0], tailPnts[1]);
62 | const neckLeft = headPnts[0];
63 | const neckRight = headPnts[4];
64 | const bodyPnts = this.getArrowBodyPoints(lnglatPoints, neckLeft, neckRight, this.tailWidthFactor);
65 | const count = bodyPnts.length;
66 | let leftPnts = [tailPnts[0]].concat(bodyPnts.slice(0, count / 2));
67 | leftPnts.push(neckLeft);
68 | let rightPnts = [tailPnts[1]].concat(bodyPnts.slice(count / 2, count));
69 | rightPnts.push(neckRight);
70 | leftPnts = Utils.getQBSplinePoints(leftPnts);
71 | rightPnts = Utils.getQBSplinePoints(rightPnts);
72 | const points = leftPnts.concat(headPnts, rightPnts.reverse());
73 | const temp = [].concat(...points);
74 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
75 | return cartesianPoints;
76 | }
77 |
78 | getTailPoints(points) {
79 | const allLen = Utils.getBaseLength(points);
80 | const tailWidth = allLen * this.tailWidthFactor;
81 | const tailLeft = Utils.getThirdPoint(points[1], points[0], Math.PI / 2, tailWidth, false);
82 | const tailRight = Utils.getThirdPoint(points[1], points[0], Math.PI / 2, tailWidth, true);
83 | return [tailLeft, tailRight];
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/arrow/fine-arrow.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class FineArrow extends Base {
8 | points: Cartesian3[] = [];
9 | arrowLengthScale: number = 5;
10 | maxArrowLength: number = 2;
11 | tailWidthFactor: number;
12 | neckWidthFactor: number;
13 | headWidthFactor: number;
14 | headAngle: number;
15 | neckAngle: number;
16 | minPointsForShape: number;
17 |
18 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
19 | super(cesium, viewer, style);
20 | this.cesium = cesium;
21 | this.tailWidthFactor = 0.1;
22 | this.neckWidthFactor = 0.2;
23 | this.headWidthFactor = 0.25;
24 | this.headAngle = Math.PI / 8.5;
25 | this.neckAngle = Math.PI / 13;
26 | this.minPointsForShape = 2;
27 | this.setState('drawing');
28 | }
29 |
30 | getType(): 'polygon' | 'line' {
31 | return 'polygon';
32 | }
33 |
34 | /**
35 | * Add points only on click events
36 | */
37 | addPoint(cartesian: Cartesian3) {
38 | if (this.points.length < 2) {
39 | this.points.push(cartesian);
40 | this.onMouseMove();
41 | }
42 | if (this.points.length === 2) {
43 | const geometryPoints = this.createGraphic(this.points);
44 | this.setGeometryPoints(geometryPoints);
45 | this.drawPolygon();
46 | this.finishDrawing();
47 | }
48 | }
49 |
50 | /**
51 | * Draw a shape based on mouse movement points during the initial drawing.
52 | */
53 | updateMovingPoint(cartesian: Cartesian3) {
54 | const tempPoints = [...this.points, cartesian];
55 | const geometryPoints = this.createGraphic(tempPoints);
56 | this.setGeometryPoints(geometryPoints);
57 | this.drawPolygon();
58 | }
59 |
60 | /**
61 | * In edit mode, drag key points to update corresponding key point data.
62 | */
63 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
64 | this.points[index] = cartesian;
65 | const geometryPoints = this.createGraphic(this.points);
66 | this.setGeometryPoints(geometryPoints);
67 | this.drawPolygon();
68 | }
69 |
70 | /**
71 | * Generate geometric shapes based on key points.
72 | */
73 | createGraphic(positions: Cartesian3[]) {
74 | const [p1, p2] = positions.map(this.cartesianToLnglat);
75 | const len = Utils.getBaseLength([p1, p2]);
76 | const tailWidth = len * this.tailWidthFactor;
77 | const neckWidth = len * this.neckWidthFactor;
78 | const headWidth = len * this.headWidthFactor;
79 | const tailLeft = Utils.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, true);
80 | const tailRight = Utils.getThirdPoint(p2, p1, Math.PI / 2, tailWidth, false);
81 | const headLeft = Utils.getThirdPoint(p1, p2, this.headAngle, headWidth, false);
82 | const headRight = Utils.getThirdPoint(p1, p2, this.headAngle, headWidth, true);
83 | const neckLeft = Utils.getThirdPoint(p1, p2, this.neckAngle, neckWidth, false);
84 | const neckRight = Utils.getThirdPoint(p1, p2, this.neckAngle, neckWidth, true);
85 | const points = [...tailLeft, ...neckLeft, ...headLeft, ...p2, ...headRight, ...neckRight, ...tailRight, ...p1];
86 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(points);
87 | return cartesianPoints;
88 | }
89 |
90 | getPoints() {
91 | return this.points;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/arrow/curved-arrow.ts:
--------------------------------------------------------------------------------
1 | import * as Utils from '../utils';
2 | import Base from '../base';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { LineStyle } from '../interface';
6 |
7 | export default class CurvedArrow extends Base {
8 | points: Cartesian3[] = [];
9 | arrowLengthScale: number = 5;
10 | maxArrowLength: number = 3000000;
11 | t: number;
12 | minPointsForShape: number;
13 |
14 | constructor(cesium: any, viewer: any, style?: LineStyle) {
15 | super(cesium, viewer, style);
16 | this.cesium = cesium;
17 | this.t = 0.3;
18 | this.minPointsForShape = 2;
19 | this.setState('drawing');
20 | this.onDoubleClick();
21 | }
22 |
23 | getType(): 'polygon' | 'line' {
24 | return 'line';
25 | }
26 |
27 | /**
28 | * Add points only on click events
29 | */
30 | addPoint(cartesian: Cartesian3) {
31 | this.points.push(cartesian);
32 | if (this.points.length < 2) {
33 | this.onMouseMove();
34 | }
35 | }
36 |
37 | /**
38 | * Draw a shape based on mouse movement points during the initial drawing.
39 | */
40 | updateMovingPoint(cartesian: Cartesian3) {
41 | const tempPoints = [...this.points, cartesian];
42 | let geometryPoints = this.createGraphic(tempPoints);
43 | this.setGeometryPoints(geometryPoints);
44 | this.drawLine();
45 | }
46 |
47 | createStraightArrow(positions: Cartesian3[]) {
48 | const [pnt1, pnt2] = positions.map(this.cartesianToLnglat);
49 | const distance = Utils.MathDistance(pnt1, pnt2);
50 | let len = distance / this.arrowLengthScale;
51 | len = len > this.maxArrowLength ? this.maxArrowLength : len;
52 | const leftPnt = Utils.getThirdPoint(pnt1, pnt2, Math.PI / 6, len / 2, false);
53 | const rightPnt = Utils.getThirdPoint(pnt1, pnt2, Math.PI / 6, len / 2, true);
54 | const points = [...pnt1, ...pnt2, ...leftPnt, ...pnt2, ...rightPnt];
55 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(points);
56 | return cartesianPoints;
57 | }
58 |
59 | /**
60 | * In edit mode, drag key points to update corresponding key point data.
61 | */
62 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
63 | this.points[index] = cartesian;
64 | const geometryPoints = this.createGraphic(this.points);
65 | this.setGeometryPoints(geometryPoints);
66 | this.drawLine();
67 | }
68 |
69 | /**
70 | * Generate geometric shapes based on key points.
71 | */
72 | createGraphic(positions: Cartesian3[]) {
73 | const lnglatPoints = positions.map((pnt) => {
74 | return this.cartesianToLnglat(pnt);
75 | });
76 |
77 | if (positions.length === 2) {
78 | // If there are only two points, draw a fine straight arrow.
79 | return this.createStraightArrow(positions);
80 | }
81 |
82 | const curvePoints = Utils.getCurvePoints(this.t, lnglatPoints);
83 | const pnt1 = lnglatPoints[lnglatPoints.length - 2];
84 | const pnt2 = lnglatPoints[lnglatPoints.length - 1];
85 |
86 | const distance = Utils.wholeDistance(lnglatPoints);
87 | let len = distance / this.arrowLengthScale;
88 | len = len > this.maxArrowLength ? this.maxArrowLength : len;
89 | const leftPnt = Utils.getThirdPoint(curvePoints[curvePoints.length - 2], curvePoints[curvePoints.length - 1], Math.PI / 6, len / 2, false);
90 | const rightPnt = Utils.getThirdPoint(curvePoints[curvePoints.length - 2], curvePoints[curvePoints.length - 1], Math.PI / 6, len / 2, true);
91 | const temp = [].concat(...curvePoints);
92 | const points = [...temp, ...leftPnt, ...pnt2, ...rightPnt];
93 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(points);
94 | return cartesianPoints;
95 | }
96 |
97 | getPoints() {
98 | return this.points;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/debug/index.ts:
--------------------------------------------------------------------------------
1 | import CesiumPlot from '../src';
2 | import * as Cesium from 'cesium';
3 | import 'cesium/Build/Cesium/Widgets/widgets.css';
4 | let raster = new Cesium.ArcGisMapServerImageryProvider({
5 | url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
6 | });
7 |
8 | const viewer = new Cesium.Viewer('cesiumContainer', {
9 | animation: false,
10 | shouldAnimate: true,
11 | geocoder: false,
12 | homeButton: false,
13 | infoBox: false,
14 | fullscreenButton: false,
15 | sceneModePicker: false,
16 | selectionIndicator: false,
17 | timeline: false,
18 | navigationHelpButton: false,
19 | baseLayerPicker: false,
20 | imageryProvider: raster,
21 | contextOptions: {
22 | requestWebgl2: true,
23 | },
24 | });
25 |
26 | viewer.scene.postProcessStages.fxaa.enabled = true;
27 | viewer.scene.camera.setView({
28 | destination: Cesium.Cartesian3.fromDegrees(107.857, 35.594498, 10000),
29 | });
30 |
31 | let geometry: any;
32 | let geometryType: string;
33 | const dragStartHandler = () => {
34 | console.error('start');
35 | };
36 | const drawUpdateHandler = (cartesian: Cesium.Cartesian3) => {
37 | console.error('update', cartesian);
38 | };
39 |
40 | const drawEndHandler = (geometryPoints: any) => {
41 | console.error('drawEnd', geometryPoints);
42 | };
43 |
44 | const editStartHandler = () => {
45 | console.error('editStart');
46 | };
47 |
48 | const editEndHandler = (geometryPoints: any) => {
49 | console.error('editEnd', geometryPoints);
50 | };
51 |
52 | const buttonGroup = document.getElementById('button-group') as HTMLElement;
53 | buttonGroup.onclick = (evt) => {
54 | const targetElement = evt.target as HTMLElement;
55 | geometryType = targetElement.id;
56 | geometry = new CesiumPlot[geometryType](Cesium, viewer, {
57 | material: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 0.5)'),
58 | outlineMaterial: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 1)'),
59 | outlineWidth: 3,
60 | });
61 | };
62 |
63 | const operationButtonGroup = document.getElementById('operation-button-group') as HTMLElement;
64 | operationButtonGroup.onclick = (evt) => {
65 | const targetElement = evt.target as HTMLElement;
66 | switch (targetElement.id) {
67 | case 'hide':
68 | geometry &&
69 | geometry.hide({
70 | duration: 1000,
71 | delay: 0,
72 | });
73 | break;
74 | case 'show':
75 | geometry && geometry.show();
76 | break;
77 | case 'remove':
78 | geometry && geometry.remove();
79 | // geometry = null;
80 | break;
81 | case 'addEvent':
82 | if (geometry) {
83 | geometry.on('drawStart', dragStartHandler);
84 | geometry.on('drawUpdate', drawUpdateHandler);
85 | geometry.on('drawEnd', drawEndHandler);
86 | geometry.on('editStart', editStartHandler);
87 | geometry.on('editEnd', editEndHandler);
88 | }
89 | break;
90 | case 'removeEvent':
91 | if (geometry) {
92 | geometry.off('drawStart', dragStartHandler);
93 | geometry.off('drawUpdate', drawUpdateHandler);
94 | geometry.off('drawEnd', drawEndHandler);
95 | geometry.off('editStart', editStartHandler);
96 | geometry.off('editEnd', editEndHandler);
97 | }
98 | break;
99 | case 'startGrowthAnimation':
100 | if (geometry) {
101 | geometry.startGrowthAnimation();
102 | }
103 | break;
104 | case 'createGeometryFromData':
105 | if (geometry) {
106 | const points = geometry.getPoints();
107 | geometry = CesiumPlot.createGeometryFromData(Cesium, viewer, {
108 | type: geometryType,
109 | cartesianPoints: points,
110 | style: {
111 | material: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 0.5)'),
112 | outlineMaterial: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 1)'),
113 | outlineWidth: 3,
114 | },
115 | });
116 | }
117 | break;
118 | case 'cancelDraw':
119 | if (geometry) {
120 | geometry.remove();
121 | }
122 | break;
123 | default:
124 | break;
125 | }
126 | };
127 |
--------------------------------------------------------------------------------
/src/arrow/attack-arrow.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 |
7 | export default class AttackArrow extends Base {
8 | points: Cartesian3[] = [];
9 | headHeightFactor: number;
10 | headWidthFactor: number;
11 | neckHeightFactor: number;
12 | neckWidthFactor: number;
13 | headTailFactor: number;
14 | minPointsForShape: number;
15 |
16 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
17 | super(cesium, viewer, style);
18 | this.cesium = cesium;
19 | this.headHeightFactor = 0.18;
20 | this.headWidthFactor = 0.3;
21 | this.neckHeightFactor = 0.85;
22 | this.neckWidthFactor = 0.15;
23 | this.headTailFactor = 0.8;
24 | this.minPointsForShape = 3;
25 | this.setState('drawing');
26 | this.onDoubleClick();
27 | }
28 |
29 | getType(): 'polygon' | 'line' {
30 | return 'polygon';
31 | }
32 |
33 | /**
34 | * Add points only on click events
35 | */
36 | addPoint(cartesian: Cartesian3) {
37 | this.points.push(cartesian);
38 | if (this.points.length < 2) {
39 | this.onMouseMove();
40 | } else if (this.points.length === 2) {
41 | this.setGeometryPoints(this.points);
42 | this.drawPolygon();
43 | }
44 | }
45 |
46 | /**
47 | * Draw a shape based on mouse movement points during the initial drawing.
48 | */
49 | updateMovingPoint(cartesian: Cartesian3) {
50 | const tempPoints = [...this.points, cartesian];
51 | this.setGeometryPoints(tempPoints);
52 | if (tempPoints.length === 2) {
53 | this.addTempLine();
54 | } else {
55 | this.removeTempLine();
56 | const geometryPoints = this.createGraphic(tempPoints);
57 | this.setGeometryPoints(geometryPoints);
58 | this.drawPolygon();
59 | }
60 | }
61 |
62 | /**
63 | * Generate geometric shapes based on key points.
64 | */
65 | createGraphic(positions: Cartesian3[]): Cartesian3[] {
66 | const lnglatPoints = positions.map((pnt) => {
67 | return this.cartesianToLnglat(pnt);
68 | });
69 |
70 | let [tailLeft, tailRight] = [lnglatPoints[0], lnglatPoints[1]];
71 | if (Utils.isClockWise(lnglatPoints[0], lnglatPoints[1], lnglatPoints[2])) {
72 | tailLeft = lnglatPoints[1];
73 | tailRight = lnglatPoints[0];
74 | }
75 |
76 | const midTail = Utils.Mid(tailLeft, tailRight);
77 | const bonePnts = [midTail].concat(lnglatPoints.slice(2));
78 | const headPnts = this.getArrowHeadPoints(bonePnts, tailLeft, tailRight);
79 | const [neckLeft, neckRight] = [headPnts[0], headPnts[4]];
80 | const tailWidthFactor = Utils.MathDistance(tailLeft, tailRight) / Utils.getBaseLength(bonePnts);
81 | const bodyPnts = this.getArrowBodyPoints(bonePnts, neckLeft, neckRight, tailWidthFactor);
82 | const count = bodyPnts.length;
83 | let leftPnts = [tailLeft].concat(bodyPnts.slice(0, count / 2));
84 | leftPnts.push(neckLeft);
85 | let rightPnts = [tailRight].concat(bodyPnts.slice(count / 2, count));
86 | rightPnts.push(neckRight);
87 | leftPnts = Utils.getQBSplinePoints(leftPnts);
88 | rightPnts = Utils.getQBSplinePoints(rightPnts);
89 | const points = leftPnts.concat(headPnts, rightPnts.reverse());
90 | const temp = [].concat(...points);
91 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
92 | return cartesianPoints;
93 | }
94 |
95 | getPoints() {
96 | return this.points;
97 | }
98 |
99 | getArrowHeadPoints(points, tailLeft, tailRight) {
100 | try {
101 | let len = Utils.getBaseLength(points);
102 | let headHeight = len * this.headHeightFactor;
103 | const headPnt = points[points.length - 1];
104 | len = Utils.MathDistance(headPnt, points[points.length - 2]);
105 | const tailWidth = Utils.MathDistance(tailLeft, tailRight);
106 | if (headHeight > tailWidth * this.headTailFactor) {
107 | headHeight = tailWidth * this.headTailFactor;
108 | }
109 | const headWidth = headHeight * this.headWidthFactor;
110 | const neckWidth = headHeight * this.neckWidthFactor;
111 | headHeight = headHeight > len ? len : headHeight;
112 | const neckHeight = headHeight * this.neckHeightFactor;
113 | const headEndPnt = Utils.getThirdPoint(points[points.length - 2], headPnt, 0, headHeight, true);
114 | const neckEndPnt = Utils.getThirdPoint(points[points.length - 2], headPnt, 0, neckHeight, true);
115 | const headLeft = Utils.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, false);
116 | const headRight = Utils.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, true);
117 | const neckLeft = Utils.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, false);
118 | const neckRight = Utils.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, true);
119 | return [neckLeft, headLeft, headPnt, headRight, neckRight];
120 | } catch (e) {
121 | console.log(e);
122 | }
123 | }
124 |
125 | getArrowBodyPoints(points, neckLeft, neckRight, tailWidthFactor) {
126 | const allLen = Utils.wholeDistance(points);
127 | const len = Utils.getBaseLength(points);
128 | const tailWidth = len * tailWidthFactor;
129 | const neckWidth = Utils.MathDistance(neckLeft, neckRight);
130 | const widthDif = (tailWidth - neckWidth) / 2;
131 | let [tempLen, leftBodyPnts, rightBodyPnts] = [0, [], []];
132 | for (let i = 1; i < points.length - 1; i++) {
133 | const angle = Utils.getAngleOfThreePoints(points[i - 1], points[i], points[i + 1]) / 2;
134 | tempLen += Utils.MathDistance(points[i - 1], points[i]);
135 | const w = (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
136 | const left = Utils.getThirdPoint(points[i - 1], points[i], Math.PI - angle, w, true);
137 | const right = Utils.getThirdPoint(points[i - 1], points[i], angle, w, false);
138 | leftBodyPnts.push(left);
139 | rightBodyPnts.push(right);
140 | }
141 | return leftBodyPnts.concat(rightBodyPnts);
142 | }
143 |
144 | /**
145 | * In edit mode, drag key points to update corresponding key point data.
146 | */
147 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
148 | this.points[index] = cartesian;
149 | const geometryPoints = this.createGraphic(this.points);
150 | this.setGeometryPoints(geometryPoints);
151 | this.drawPolygon();
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cesium-plot-js
2 |
3 | cesium 军事标绘插件,支持绘制多边形、曲线、箭头等图形
4 |
5 | 
6 |
7 | 淡入淡出效果:
8 |
9 | 
10 |
11 | 生长动画:
12 | 
13 |
14 | 在线示例: [demo](https://ethan-zf.github.io/cesium-plot-js/examples/index.html)
15 |
16 | ### CDN
17 |
18 | 1. 引入文件
19 |
20 | ```
21 |
22 | ```
23 |
24 | 2. 调用绘制 api
25 |
26 | ```
27 | new CesiumPlot.FineArrow(Cesium, viewer);
28 | ```
29 |
30 | ### NPM
31 |
32 | 1. install
33 |
34 | ```
35 | npm i cesium-plot-js
36 | ```
37 |
38 | 2. import
39 |
40 | ```
41 | import CesiumPlot from 'cesium-plot-js';
42 | ```
43 |
44 | 3. 调用绘制 api
45 |
46 | ```
47 | new CesiumPlot.FineArrow(Cesium, viewer);
48 | ```
49 |
50 | ### Classes
51 |
52 | 每个图形为独立的类,绑定事件或其他操作通过类的实例来实现
53 |
54 | | 类名 | 类型 | 描述 | 生长动画 |
55 | | ---------------------- | --------- | ---------------- | -------- |
56 | | Polygon | 'polygon' | 多边形 | ❌ |
57 | | Reactangle | 'polygon' | 矩形 | ❌ |
58 | | Triangle | 'polygon' | 三角形 | ❌ |
59 | | Circle | 'polygon' | 圆形 | ❌ |
60 | | Sector | 'polygon' | 扇形 | ❌ |
61 | | StraightArrow | 'line' | 细直箭头 | ✔️ |
62 | | CurvedArrow | 'line' | 曲线箭头 | ✔️ |
63 | | FineArrow | 'polygon' | 直箭头 | ✔️ |
64 | | AttackArrow | 'polygon' | 进攻方向箭头 | ✔️ |
65 | | SwallowtailAttackArrow | 'polygon' | 燕尾进攻方向箭头 | ✔️ |
66 | | SquadCombat | 'polygon' | 分队战斗方向 | ✔️ |
67 | | SwallowtailSquadCombat | 'polygon' | 燕尾分队战斗方向 | ✔️ |
68 | | AssaultDirection | 'polygon' | 突击方向 | ✔️ |
69 | | DoubleArrow | 'polygon' | 双箭头 | ✔️ |
70 | | FreehandLine | 'line' | 自由线 | ❌ |
71 | | FreehandPolygon | 'polygon' | 自由面 | ❌ |
72 | | Curve | 'line' | 曲线 | ❌ |
73 | | Ellipse | 'polygon' | 椭圆 | ❌ |
74 | | Lune | 'polygon' | 半月面 | ❌ |
75 |
76 | ### 构造函数
77 |
78 | 所有图形的构造函数:
79 |
80 | <类名>(cesium: Cesium, viewer: Cesium.Viewer, style?: [PolygonStyle](#PolygonStyle) | [LineStyle](#LineStyle))
81 |
82 | PolygonStyle类型
83 |
84 | ```
85 | {
86 | material?: Cesium.MaterialProperty;
87 | outlineWidth?: number;
88 | outlineMaterial?: Cesium.MaterialProperty;
89 | };
90 | ```
91 |
92 | LineStyle类型
93 |
94 | ```
95 | {
96 | material?: Cesium.Color;
97 | lineWidth?: number;
98 | };
99 | ```
100 |
101 | 示例
102 |
103 | ```
104 | // 初始化viewer
105 | const viewer = new Cesium.Viewer('cesiumContainer');
106 | // 抗锯齿
107 | viewer.scene.postProcessStages.fxaa.enabled = true;
108 | // 设置自定义样式
109 | const geometry = new CesiumPlot.FineArrow(Cesium, viewer, {
110 | material: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 0.5)'),
111 | outlineMaterial: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 1)'),
112 | outlineWidth: 3,
113 | });
114 | ```
115 |
116 | ### 类的实例方法
117 |
118 | | 方法名 | 参数 | 描述 |
119 | | -------------------- | --------------------------------------------------------------------- | ---------------------------------------------------- |
120 | | hide | options?: [AnimationOpts](#AnimationOpts) | 隐藏,options 可配置动画参数,参数缺省时,不显示动画 |
121 | | show | options?: [AnimationOpts](#AnimationOpts) | 显示,options 可配置动画参数,参数缺省时,不显示动画 |
122 | | startGrowthAnimation | options?: [AnimationOpts](#AnimationOpts) | 生长动画,options 可配置动画参数 |
123 | | getPoints | | 获取图形关键点位 |
124 | | remove | | 删除 |
125 | | on | (event: [EventType](#EventType), listener: (eventData?: any) => void) | 绑定事件 |
126 | | off | (event: [EventType](#EventType)) | 解绑事件 |
127 |
128 | AnimationOpts类型
129 |
130 | | 参数 | 类型 | 默认值 | 描述 |
131 | | -------- | ---------- | ------ | ---------------------- |
132 | | duration | number | 2000 | 动画持续时间(ms) |
133 | | delay | number | 0 | 动画延迟启动时间(ms) |
134 | | callback | () => void | - | 动画结束回调 |
135 |
136 | ```
137 | // 隐藏图形
138 | const geometry = new CesiumPlot.Reactangle(Cesium, viewer);
139 | geometry.hide();
140 | ```
141 |
142 | ```
143 | // 绑定事件
144 | const geometry = new CesiumPlot.Reactangle(Cesium, viewer);
145 | geometry.on('drawEnd', (data)=>{
146 | console.log(data)
147 | });
148 | ```
149 |
150 | ### 静态方法
151 |
152 | **CesiumPlot.createGeometryFromData(cesium: Cesium, viewer: Cesium.Viewer, options:[CreateGeometryFromDataOpts](#CreateGeometryFromDataOpts))**
153 |
154 | 根据图形的关键点位重新生成图形
155 |
156 | CreateGeometryFromDataOpts参数类型
157 |
158 | ```
159 | {
160 | type: 'FineArrow'|'AttackArrow'|'SwallowtailAttackArrow'|'SquadCombat'|'SwallowtailSquadCombat'|'StraightArrow'|'CurvedArrow'|'AssaultDirection'|'DoubleArrow'|'FreehandLine'|'FreehandPolygon'|'Curve'|'Ellipse'|'Lune'|'Reactangle'|'Triangle'|'Polygon'|'Circle'|'Sector', // 图形类型
161 | cartesianPoints: Cesium.Cartesian3[], // 图形关键点位,可通过实例方法getPoints或者drawEnd事件获得
162 | style?: PolygonStyle | LineStyle // 样式
163 | }
164 | ```
165 |
166 | Events
167 |
168 | - **drawStart**
169 |
170 | 绘制开始
171 |
172 | - **drawUpdate**
173 |
174 | 绘制过程中点位更新,回调事件返回更新的 Cartesian3 点位
175 |
176 | - **drawEnd**
177 |
178 | 绘制结束,回调事件返回图形的关键点位
179 |
180 | ```
181 | geometry.on('drawEnd', (data) => {
182 | console.log(data);
183 | });
184 | ```
185 |
186 | - **editStart**
187 |
188 | 编辑开始
189 |
190 | - **editEnd**
191 |
192 | 编辑结束,回调事件返回图形的关键点位
193 |
--------------------------------------------------------------------------------
/examples/index.js:
--------------------------------------------------------------------------------
1 | window.onload = () => {
2 | let raster = new Cesium.ArcGisMapServerImageryProvider({
3 | url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer',
4 | });
5 |
6 | const viewer = new Cesium.Viewer('cesiumContainer', {
7 | animation: false,
8 | shouldAnimate: true,
9 | geocoder: false,
10 | homeButton: false,
11 | infoBox: false,
12 | fullscreenButton: false,
13 | sceneModePicker: false,
14 | selectionIndicator: false,
15 | timeline: false,
16 | navigationHelpButton: false,
17 | baseLayerPicker: false,
18 | imageryProvider: raster,
19 | contextOptions: {
20 | requestWebgl2: true,
21 | },
22 | });
23 |
24 | viewer.scene.postProcessStages.fxaa.enabled = true;
25 | viewer.scene.camera.setView({
26 | destination: Cesium.Cartesian3.fromDegrees(107.857, 35.594498, 10000),
27 | });
28 | // let scene = viewer.scene;
29 | // viewer.scene.debugShowFramesPerSecond = true;
30 |
31 | // viewer.camera.flyTo({
32 | // destination: new Cesium.Cartesian3(-2480561.3182985717, 4681691.324170088, 3539464.2534263907),
33 | // orientation: {
34 | // heading: 2.0002851646951996,
35 | // pitch: -0.25963088874216256,
36 | // roll: 6.283183024299778,
37 | // },
38 | // complete: function () {
39 | // console.error('fly complete');
40 | // },
41 | // });
42 |
43 | // // let geometry = new CesiumPlot.Polygon(Cesium, viewer);
44 | // let geometry = new CesiumPlot.FineArrow(Cesium, viewer, {
45 | // material: Cesium.Color.fromCssColorString('rgba(255, 178, 208, 0.5)'),
46 | // outlineMaterial: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 1)'),
47 | // outlineWidth: 3,
48 | // });
49 | let geometry;
50 | const dragStartHandler = () => {
51 | console.error('start');
52 | };
53 | const drawUpdateHandler = (cartesian) => {
54 | console.error('update', cartesian);
55 | };
56 |
57 | const drawEndHandler = (geometryPoints) => {
58 | console.error('drawEnd', geometryPoints);
59 | };
60 |
61 | const editStartHandler = () => {
62 | console.error('editStart');
63 | };
64 |
65 | const editEndHandler = (geometryPoints) => {
66 | console.error('editEnd', geometryPoints);
67 | };
68 | const buttonGroup = document.getElementById('button-group');
69 | buttonGroup.onclick = (evt) => {
70 | const targetElement = evt.target;
71 | switch (targetElement.id) {
72 | case 'drawCircle':
73 | geometry = new CesiumPlot.Circle(Cesium, viewer);
74 | break;
75 | case 'drawPolygon':
76 | geometry = new CesiumPlot.Polygon(Cesium, viewer);
77 | break;
78 | case 'drawReactangle':
79 | geometry = new CesiumPlot.Reactangle(Cesium, viewer);
80 | break;
81 | case 'drawTriangle':
82 | geometry = new CesiumPlot.Triangle(Cesium, viewer);
83 | break;
84 | case 'drawFineArrow':
85 | geometry = new CesiumPlot.FineArrow(Cesium, viewer, {
86 | // material: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 0.5)'),
87 | // outlineMaterial: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 1)'),
88 | // outlineWidth: 3,
89 | });
90 | break;
91 | case 'drawAttackArrow':
92 | geometry = new CesiumPlot.AttackArrow(Cesium, viewer, {
93 | // outlineMaterial: Cesium.Color.RED,
94 | });
95 | break;
96 | case 'drawSwallowtailAttackArrow':
97 | geometry = new CesiumPlot.SwallowtailAttackArrow(Cesium, viewer, {
98 | // outlineMaterial: Cesium.Color.BLUE,
99 | });
100 | break;
101 | case 'drawSquadCombat':
102 | geometry = new CesiumPlot.SquadCombat(Cesium, viewer, {
103 | // outlineMaterial: new Cesium.PolylineDashMaterialProperty({
104 | // color: Cesium.Color.RED,
105 | // dashLength: 16.0,
106 | // }),
107 | });
108 | break;
109 | case 'drawSwallowtailSquadCombat':
110 | geometry = new CesiumPlot.SwallowtailSquadCombat(Cesium, viewer);
111 | break;
112 | case 'drawStraightArrow':
113 | geometry = new CesiumPlot.StraightArrow(Cesium, viewer, {
114 | // material: Cesium.Color.RED,
115 | });
116 | break;
117 | case 'drawAssaultDirection':
118 | geometry = new CesiumPlot.AssaultDirection(Cesium, viewer);
119 | break;
120 | case 'drawCurvedArrow':
121 | geometry = new CesiumPlot.CurvedArrow(Cesium, viewer, {
122 | // material: Cesium.Color.BLUE,
123 | });
124 | break;
125 | case 'drawDoubleArrow':
126 | geometry = new CesiumPlot.DoubleArrow(Cesium, viewer, {
127 | // outlineMaterial: Cesium.Color.GREEN,
128 | });
129 | break;
130 | case 'drawFreehandLine':
131 | geometry = new CesiumPlot.FreehandLine(Cesium, viewer);
132 | break;
133 | case 'drawCurve':
134 | geometry = new CesiumPlot.Curve(Cesium, viewer);
135 | break;
136 | case 'drawEllipse':
137 | geometry = new CesiumPlot.Ellipse(Cesium, viewer);
138 | break;
139 | case 'drawLune':
140 | geometry = new CesiumPlot.Lune(Cesium, viewer);
141 | break;
142 | case 'drawFreehandPolygon':
143 | geometry = new CesiumPlot.FreehandPolygon(Cesium, viewer, {
144 | // material: Cesium.Color.GREEN,
145 | // outlineMaterial: Cesium.Color.fromCssColorString('rgba(59, 178, 208, 1)'),
146 | // outlineWidth: 2,
147 | });
148 | break;
149 | case 'hide':
150 | geometry &&
151 | geometry.hide({
152 | deration: 2000,
153 | });
154 | break;
155 | case 'show':
156 | geometry &&
157 | geometry.show({
158 | deration: 2000,
159 | });
160 | break;
161 | case 'remove':
162 | geometry && geometry.remove();
163 | geometry = null;
164 | break;
165 | case 'addEvent':
166 | if (geometry) {
167 | geometry.on('drawStart', dragStartHandler);
168 | geometry.on('drawUpdate', drawUpdateHandler);
169 | geometry.on('drawEnd', drawEndHandler);
170 | geometry.on('editStart', editStartHandler);
171 | geometry.on('editEnd', editEndHandler);
172 | }
173 | break;
174 | case 'removeEvent':
175 | if (geometry) {
176 | geometry.off('drawStart', dragStartHandler);
177 | geometry.off('drawUpdate', drawUpdateHandler);
178 | geometry.off('drawEnd', drawEndHandler);
179 | geometry.off('editStart', editStartHandler);
180 | geometry.off('editEnd', editEndHandler);
181 | }
182 | break;
183 | case 'startGrowthAnimation':
184 | if (geometry) {
185 | geometry.startGrowthAnimation({
186 | duration: 2000,
187 | });
188 | }
189 | break;
190 | default:
191 | break;
192 | }
193 | };
194 | };
195 |
--------------------------------------------------------------------------------
/src/arrow/double-arrow.ts:
--------------------------------------------------------------------------------
1 | import Base from '../base';
2 | import * as Utils from '../utils';
3 | // @ts-ignore
4 | import { Cartesian3 } from 'cesium';
5 | import { PolygonStyle } from '../interface';
6 | type Position = [number, number];
7 |
8 | export default class DoubleArrow extends Base {
9 | points: Cartesian3[] = [];
10 | arrowLengthScale: number = 5;
11 | maxArrowLength: number = 2;
12 | neckWidthFactor: number;
13 | headWidthFactor: number;
14 | headHeightFactor: number;
15 | neckHeightFactor: number;
16 | connPoint: Position;
17 | tempPoint4: Position;
18 | minPointsForShape: number;
19 | llBodyPnts: Position[] = [];
20 | rrBodyPnts: Position[] = [];
21 | curveControlPointLeft: Cartesian3;
22 | curveControlPointRight: Cartesian3;
23 | isClockWise: boolean;
24 |
25 | constructor(cesium: any, viewer: any, style?: PolygonStyle) {
26 | super(cesium, viewer, style);
27 | this.cesium = cesium;
28 | this.headHeightFactor = 0.25;
29 | this.headWidthFactor = 0.3;
30 | this.neckHeightFactor = 0.85;
31 | this.neckWidthFactor = 0.15;
32 | this.connPoint = [0, 0];
33 | this.tempPoint4 = [0, 0];
34 | this.minPointsForShape = 4;
35 | this.setState('drawing');
36 | }
37 |
38 | getType(): 'polygon' | 'line' {
39 | return 'polygon';
40 | }
41 |
42 | /**
43 | * Add points only on click events
44 | */
45 | addPoint(cartesian: Cartesian3) {
46 | this.points.push(cartesian);
47 | if (this.points.length < 2) {
48 | this.onMouseMove();
49 | } else if (this.points.length === 2) {
50 | this.setGeometryPoints(this.points);
51 | this.drawPolygon();
52 | } else if (this.points.length === 3) {
53 | this.lineEntity && this.viewer.entities.remove(this.lineEntity);
54 | } else {
55 | this.finishDrawing();
56 |
57 | // // 辅助查看插值控制点位置
58 | // this.viewer.entities.add({
59 | // position: this.curveControlPointLeft,
60 | // point: {
61 | // pixelSize: 10,
62 | // heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
63 | // color: this.cesium.Color.RED,
64 | // },
65 | // });
66 | // this.viewer.entities.add({
67 | // position: this.curveControlPointRight,
68 | // point: {
69 | // pixelSize: 10,
70 | // heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
71 | // color: this.cesium.Color.RED,
72 | // },
73 | // });
74 | }
75 | }
76 |
77 | finishDrawing() {
78 | this.curveControlPointLeft = this.cesium.Cartesian3.fromDegrees(this.llBodyPnts[2][0], this.llBodyPnts[2][1]);
79 | this.curveControlPointRight = this.cesium.Cartesian3.fromDegrees(this.rrBodyPnts[1][0], this.rrBodyPnts[1][1]);
80 | super.finishDrawing();
81 | }
82 | /**
83 | * Draw a shape based on mouse movement points during the initial drawing.
84 | */
85 | updateMovingPoint(cartesian: Cartesian3) {
86 | const tempPoints = [...this.points, cartesian];
87 | this.setGeometryPoints(tempPoints);
88 | if (tempPoints.length === 2) {
89 | this.addTempLine();
90 | } else if (tempPoints.length > 2) {
91 | this.removeTempLine();
92 | const geometryPoints = this.createGraphic(tempPoints);
93 | this.setGeometryPoints(geometryPoints);
94 | this.drawPolygon();
95 | }
96 | }
97 |
98 | /**
99 | * In edit mode, drag key points to update corresponding key point data.
100 | */
101 | updateDraggingPoint(cartesian: Cartesian3, index: number) {
102 | this.points[index] = cartesian;
103 | const geometryPoints = this.createGraphic(this.points);
104 | this.setGeometryPoints(geometryPoints);
105 | this.drawPolygon();
106 | }
107 |
108 | /**
109 | * Generate geometric shapes based on key points.
110 | */
111 | createGraphic(positions: Cartesian3[]) {
112 | const lnglatPoints = positions.map((pnt) => {
113 | return this.cartesianToLnglat(pnt);
114 | });
115 | const [pnt1, pnt2, pnt3] = [lnglatPoints[0], lnglatPoints[1], lnglatPoints[2]];
116 | const count = lnglatPoints.length;
117 | if (count === 3) {
118 | this.tempPoint4 = this.getTempPoint4(pnt1, pnt2, pnt3);
119 | this.connPoint = Utils.Mid(pnt1, pnt2);
120 | } else if (count === 4) {
121 | this.tempPoint4 = lnglatPoints[3];
122 | this.connPoint = Utils.Mid(pnt1, pnt2);
123 | } else {
124 | this.tempPoint4 = lnglatPoints[3];
125 | this.connPoint = lnglatPoints[4];
126 | }
127 | let leftArrowPnts: Position[];
128 | let rightArrowPnts;
129 | this.isClockWise = Utils.isClockWise(pnt1, pnt2, pnt3);
130 | if (this.isClockWise) {
131 | leftArrowPnts = this.getArrowPoints(pnt1, this.connPoint, this.tempPoint4, false);
132 | rightArrowPnts = this.getArrowPoints(this.connPoint, pnt2, pnt3, true);
133 | } else {
134 | leftArrowPnts = this.getArrowPoints(pnt2, this.connPoint, pnt3, false);
135 | rightArrowPnts = this.getArrowPoints(this.connPoint, pnt1, this.tempPoint4, true);
136 | }
137 | const m = leftArrowPnts.length;
138 | const t = (m - 5) / 2;
139 | const llBodyPnts = leftArrowPnts.slice(0, t);
140 | const lArrowPnts = leftArrowPnts.slice(t, t + 5);
141 | let lrBodyPnts = leftArrowPnts.slice(t + 5, m);
142 | this.llBodyPnts = llBodyPnts;
143 | let rlBodyPnts = rightArrowPnts.slice(0, t);
144 | const rArrowPnts = rightArrowPnts.slice(t, t + 5);
145 | const rrBodyPnts = rightArrowPnts.slice(t + 5, m);
146 | this.rrBodyPnts = rrBodyPnts;
147 | rlBodyPnts = Utils.getBezierPoints(rlBodyPnts);
148 | const bodyPnts = Utils.getBezierPoints(rrBodyPnts.concat(llBodyPnts.slice(1)));
149 | lrBodyPnts = Utils.getBezierPoints(lrBodyPnts);
150 | const pnts = rlBodyPnts.concat(rArrowPnts, bodyPnts, lArrowPnts, lrBodyPnts);
151 | const temp = [].concat(...pnts);
152 | const cartesianPoints = this.cesium.Cartesian3.fromDegreesArray(temp);
153 | return cartesianPoints;
154 | }
155 |
156 | getTempPoint4(linePnt1: Position, linePnt2: Position, point: Position): Position {
157 | const midPnt = Utils.Mid(linePnt1, linePnt2);
158 | const len = Utils.MathDistance(midPnt, point);
159 | const angle = Utils.getAngleOfThreePoints(linePnt1, midPnt, point);
160 | let symPnt = [0, 0] as Position;
161 | let distance1;
162 | let distance2;
163 | let mid;
164 | if (angle < Math.PI / 2) {
165 | distance1 = len * Math.sin(angle);
166 | distance2 = len * Math.cos(angle);
167 | mid = Utils.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, false);
168 | symPnt = Utils.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, true);
169 | } else if (angle >= Math.PI / 2 && angle < Math.PI) {
170 | distance1 = len * Math.sin(Math.PI - angle);
171 | distance2 = len * Math.cos(Math.PI - angle);
172 | mid = Utils.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, false);
173 | symPnt = Utils.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, false);
174 | } else if (angle >= Math.PI && angle < Math.PI * 1.5) {
175 | distance1 = len * Math.sin(angle - Math.PI);
176 | distance2 = len * Math.cos(angle - Math.PI);
177 | mid = Utils.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, true);
178 | symPnt = Utils.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, true);
179 | } else {
180 | distance1 = len * Math.sin(Math.PI * 2 - angle);
181 | distance2 = len * Math.cos(Math.PI * 2 - angle);
182 | mid = Utils.getThirdPoint(linePnt1, midPnt, Math.PI / 2, distance1, true);
183 | symPnt = Utils.getThirdPoint(midPnt, mid, Math.PI / 2, distance2, false);
184 | }
185 | return symPnt;
186 | }
187 |
188 | getArrowPoints(pnt1: Position, pnt2: Position, pnt3: Position, clockWise: boolean): Position[] {
189 | const midPnt = Utils.Mid(pnt1, pnt2);
190 | const len = Utils.MathDistance(midPnt, pnt3);
191 | let midPnt1 = Utils.getThirdPoint(pnt3, midPnt, 0, len * 0.3, true);
192 | let midPnt2 = Utils.getThirdPoint(pnt3, midPnt, 0, len * 0.5, true);
193 | midPnt1 = Utils.getThirdPoint(midPnt, midPnt1, Math.PI / 2, len / 5, clockWise);
194 |
195 | midPnt2 = Utils.getThirdPoint(midPnt, midPnt2, Math.PI / 2, len / 4, clockWise);
196 | const points = [midPnt, midPnt1, midPnt2, pnt3];
197 | const arrowPnts = this.getArrowHeadPoints(points);
198 | if (arrowPnts && Array.isArray(arrowPnts) && arrowPnts.length > 0) {
199 | const neckLeftPoint: Position = arrowPnts[0];
200 | const neckRightPoint: Position = arrowPnts[4];
201 | const tailWidthFactor = Utils.MathDistance(pnt1, pnt2) / Utils.getBaseLength(points) / 2;
202 | const bodyPnts = this.getArrowBodyPoints(points, neckLeftPoint, neckRightPoint, tailWidthFactor);
203 | if (bodyPnts) {
204 | const n = bodyPnts.length;
205 | let lPoints = bodyPnts.slice(0, n / 2);
206 | let rPoints = bodyPnts.slice(n / 2, n);
207 | lPoints.push(neckLeftPoint);
208 | rPoints.push(neckRightPoint);
209 | lPoints = lPoints.reverse();
210 | lPoints.push(pnt2);
211 | rPoints = rPoints.reverse();
212 | rPoints.push(pnt1);
213 | return lPoints.reverse().concat(arrowPnts, rPoints);
214 | }
215 | } else {
216 | throw new Error('Interpolation Error');
217 | }
218 | }
219 |
220 | getArrowBodyPoints(points: Position[], neckLeft: Position, neckRight: Position, tailWidthFactor: number): Position[] {
221 | const allLen = Utils.wholeDistance(points);
222 | const len = Utils.getBaseLength(points);
223 | const tailWidth = len * tailWidthFactor;
224 | const neckWidth = Utils.MathDistance(neckLeft, neckRight);
225 | const widthDif = (tailWidth - neckWidth) / 2;
226 | let tempLen: number = 0;
227 | let leftBodyPnts: Position[] = [];
228 | let rightBodyPnts: Position[] = [];
229 | for (let i = 1; i < points.length - 1; i++) {
230 | const angle = Utils.getAngleOfThreePoints(points[i - 1], points[i], points[i + 1]) / 2;
231 | tempLen += Utils.MathDistance(points[i - 1], points[i]);
232 | const w = (tailWidth / 2 - (tempLen / allLen) * widthDif) / Math.sin(angle);
233 | const left = Utils.getThirdPoint(points[i - 1], points[i], Math.PI - angle, w, true);
234 | const right = Utils.getThirdPoint(points[i - 1], points[i], angle, w, false);
235 | leftBodyPnts.push(left);
236 | rightBodyPnts.push(right);
237 | }
238 | return leftBodyPnts.concat(rightBodyPnts);
239 | }
240 |
241 | getArrowHeadPoints(points: Position[]): Position[] {
242 | const len = Utils.getBaseLength(points);
243 | const headHeight = len * this.headHeightFactor;
244 | const headPnt = points[points.length - 1];
245 | const headWidth = headHeight * this.headWidthFactor;
246 | const neckWidth = headHeight * this.neckWidthFactor;
247 | const neckHeight = headHeight * this.neckHeightFactor;
248 | const headEndPnt = Utils.getThirdPoint(points[points.length - 2], headPnt, 0, headHeight, true);
249 | const neckEndPnt = Utils.getThirdPoint(points[points.length - 2], headPnt, 0, neckHeight, true);
250 | const headLeft = Utils.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, false);
251 | const headRight = Utils.getThirdPoint(headPnt, headEndPnt, Math.PI / 2, headWidth, true);
252 | const neckLeft = Utils.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, false);
253 | const neckRight = Utils.getThirdPoint(headPnt, neckEndPnt, Math.PI / 2, neckWidth, true);
254 | return [neckLeft, headLeft, headPnt, headRight, neckRight];
255 | }
256 |
257 | getPoints() {
258 | return this.points;
259 | }
260 |
261 | getBezierControlPointforGrowthAnimation() {
262 | return this.isClockWise
263 | ? {
264 | left: this.curveControlPointLeft,
265 | right: this.curveControlPointRight,
266 | }
267 | : {
268 | right: this.curveControlPointLeft,
269 | left: this.curveControlPointRight,
270 | };
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | const FITTING_COUNT = 100;
2 | const ZERO_TOLERANCE = 0.0001;
3 |
4 | /**
5 | * 计算两个坐标之间的距离
6 | * @param pnt1
7 | * @param pnt2
8 | * @returns {number}
9 | * @constructor
10 | */
11 | export const MathDistance = (pnt1, pnt2) => Math.sqrt((pnt1[0] - pnt2[0]) ** 2 + (pnt1[1] - pnt2[1]) ** 2);
12 |
13 | /**
14 | * 计算点集合的总距离
15 | * @param points
16 | * @returns {number}
17 | */
18 | export const wholeDistance = (points) => {
19 | let distance = 0;
20 | if (points && Array.isArray(points) && points.length > 0) {
21 | points.forEach((item, index) => {
22 | if (index < points.length - 1) {
23 | distance += MathDistance(item, points[index + 1]);
24 | }
25 | });
26 | }
27 | return distance;
28 | };
29 | /**
30 | * 获取基础长度
31 | * @param points
32 | * @returns {number}
33 | */
34 | export const getBaseLength = (points) => wholeDistance(points) ** 0.99;
35 |
36 | /**
37 | * 求取两个坐标的中间值
38 | * @param point1
39 | * @param point2
40 | * @returns {[*,*]}
41 | * @constructor
42 | */
43 | export const Mid = (point1, point2): [number, number] => [(point1[0] + point2[0]) / 2, (point1[1] + point2[1]) / 2];
44 |
45 | /**
46 | * 通过三个点确定一个圆的中心点
47 | * @param point1
48 | * @param point2
49 | * @param point3
50 | */
51 | export const getCircleCenterOfThreePoints = (point1, point2, point3) => {
52 | const pntA = [(point1[0] + point2[0]) / 2, (point1[1] + point2[1]) / 2];
53 | const pntB = [pntA[0] - point1[1] + point2[1], pntA[1] + point1[0] - point2[0]];
54 | const pntC = [(point1[0] + point3[0]) / 2, (point1[1] + point3[1]) / 2];
55 | const pntD = [pntC[0] - point1[1] + point3[1], pntC[1] + point1[0] - point3[0]];
56 | // eslint-disable-next-line @typescript-eslint/no-use-before-define
57 | return getIntersectPoint(pntA, pntB, pntC, pntD);
58 | };
59 |
60 | /**
61 | * 获取交集的点
62 | * @param pntA
63 | * @param pntB
64 | * @param pntC
65 | * @param pntD
66 | * @returns {[*,*]}
67 | */
68 | export const getIntersectPoint = (pntA, pntB, pntC, pntD) => {
69 | if (pntA[1] === pntB[1]) {
70 | const f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1]);
71 | const x = f * (pntA[1] - pntC[1]) + pntC[0];
72 | const y = pntA[1];
73 | return [x, y];
74 | }
75 | if (pntC[1] === pntD[1]) {
76 | const e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1]);
77 | const x = e * (pntC[1] - pntA[1]) + pntA[0];
78 | const y = pntC[1];
79 | return [x, y];
80 | }
81 | const e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1]);
82 | const f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1]);
83 | const y = (e * pntA[1] - pntA[0] - f * pntC[1] + pntC[0]) / (e - f);
84 | const x = e * y - e * pntA[1] + pntA[0];
85 | return [x, y];
86 | };
87 |
88 | /**
89 | * 获取方位角(地平经度)
90 | * @param startPoint
91 | * @param endPoint
92 | * @returns {*}
93 | */
94 | export const getAzimuth = (startPoint, endPoint) => {
95 | let azimuth;
96 | const angle = Math.asin(Math.abs(endPoint[1] - startPoint[1]) / MathDistance(startPoint, endPoint));
97 | if (endPoint[1] >= startPoint[1] && endPoint[0] >= startPoint[0]) {
98 | azimuth = angle + Math.PI;
99 | } else if (endPoint[1] >= startPoint[1] && endPoint[0] < startPoint[0]) {
100 | azimuth = Math.PI * 2 - angle;
101 | } else if (endPoint[1] < startPoint[1] && endPoint[0] < startPoint[0]) {
102 | azimuth = angle;
103 | } else if (endPoint[1] < startPoint[1] && endPoint[0] >= startPoint[0]) {
104 | azimuth = Math.PI - angle;
105 | }
106 | return azimuth;
107 | };
108 |
109 | /**
110 | * 通过三个点获取方位角
111 | * @param pntA
112 | * @param pntB
113 | * @param pntC
114 | * @returns {number}
115 | */
116 | export const getAngleOfThreePoints = (pntA, pntB, pntC) => {
117 | const angle = getAzimuth(pntB, pntA) - getAzimuth(pntB, pntC);
118 | return angle < 0 ? angle + Math.PI * 2 : angle;
119 | };
120 |
121 | /**
122 | * 判断是否是顺时针
123 | * @param pnt1
124 | * @param pnt2
125 | * @param pnt3
126 | * @returns {boolean}
127 | */
128 | export const isClockWise = (pnt1, pnt2, pnt3) =>
129 | (pnt3[1] - pnt1[1]) * (pnt2[0] - pnt1[0]) > (pnt2[1] - pnt1[1]) * (pnt3[0] - pnt1[0]);
130 |
131 | /**
132 | * 获取线上的点
133 | * @param t
134 | * @param startPnt
135 | * @param endPnt
136 | * @returns {[*,*]}
137 | */
138 | export const getPointOnLine = (t, startPnt, endPnt) => {
139 | const x = startPnt[0] + t * (endPnt[0] - startPnt[0]);
140 | const y = startPnt[1] + t * (endPnt[1] - startPnt[1]);
141 | return [x, y];
142 | };
143 |
144 | /**
145 | * 获取立方值
146 | * @param t
147 | * @param startPnt
148 | * @param cPnt1
149 | * @param cPnt2
150 | * @param endPnt
151 | * @returns {[*,*]}
152 | */
153 | export const getCubicValue = (t, startPnt, cPnt1, cPnt2, endPnt) => {
154 | // eslint-disable-next-line no-param-reassign
155 | t = Math.max(Math.min(t, 1), 0);
156 | const [tp, t2] = [1 - t, t * t];
157 | const t3 = t2 * t;
158 | const tp2 = tp * tp;
159 | const tp3 = tp2 * tp;
160 | const x = tp3 * startPnt[0] + 3 * tp2 * t * cPnt1[0] + 3 * tp * t2 * cPnt2[0] + t3 * endPnt[0];
161 | const y = tp3 * startPnt[1] + 3 * tp2 * t * cPnt1[1] + 3 * tp * t2 * cPnt2[1] + t3 * endPnt[1];
162 | return [x, y];
163 | };
164 |
165 | /**
166 | * 根据起止点和旋转方向求取第三个点
167 | * @param startPnt
168 | * @param endPnt
169 | * @param angle
170 | * @param distance
171 | * @param clockWise
172 | * @returns {[*,*]}
173 | */
174 | export const getThirdPoint = (startPnt, endPnt, angle, distance, clockWise): [number, number] => {
175 | const azimuth = getAzimuth(startPnt, endPnt);
176 | const alpha = clockWise ? azimuth + angle : azimuth - angle;
177 | const dx = distance * Math.cos(alpha);
178 | const dy = distance * Math.sin(alpha);
179 | return [endPnt[0] + dx, endPnt[1] + dy];
180 | };
181 |
182 | /**
183 | * 插值弓形线段点
184 | * @param center
185 | * @param radius
186 | * @param startAngle
187 | * @param endAngle
188 | * @returns {null}
189 | */
190 | export const getArcPoints = (center, radius, startAngle, endAngle) => {
191 | // eslint-disable-next-line
192 | let [x, y, pnts, angleDiff] = [null, null, [], endAngle - startAngle];
193 | angleDiff = angleDiff < 0 ? angleDiff + Math.PI * 2 : angleDiff;
194 | for (let i = 0; i <= 100; i++) {
195 | const angle = startAngle + (angleDiff * i) / 100;
196 | x = center[0] + radius * Math.cos(angle);
197 | y = center[1] + radius * Math.sin(angle);
198 | pnts.push([x, y]);
199 | }
200 | return pnts;
201 | };
202 |
203 | /**
204 | * getBisectorNormals
205 | * @param t
206 | * @param pnt1
207 | * @param pnt2
208 | * @param pnt3
209 | * @returns {[*,*]}
210 | */
211 | export const getBisectorNormals = (t, pnt1, pnt2, pnt3) => {
212 | // eslint-disable-next-line
213 | const normal = getNormal(pnt1, pnt2, pnt3);
214 | let [bisectorNormalRight, bisectorNormalLeft, dt, x, y] = [null, null, null, null, null];
215 | const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
216 | const uX = normal[0] / dist;
217 | const uY = normal[1] / dist;
218 | const d1 = MathDistance(pnt1, pnt2);
219 | const d2 = MathDistance(pnt2, pnt3);
220 | if (dist > ZERO_TOLERANCE) {
221 | if (isClockWise(pnt1, pnt2, pnt3)) {
222 | dt = t * d1;
223 | x = pnt2[0] - dt * uY;
224 | y = pnt2[1] + dt * uX;
225 | bisectorNormalRight = [x, y];
226 | dt = t * d2;
227 | x = pnt2[0] + dt * uY;
228 | y = pnt2[1] - dt * uX;
229 | bisectorNormalLeft = [x, y];
230 | } else {
231 | dt = t * d1;
232 | x = pnt2[0] + dt * uY;
233 | y = pnt2[1] - dt * uX;
234 | bisectorNormalRight = [x, y];
235 | dt = t * d2;
236 | x = pnt2[0] - dt * uY;
237 | y = pnt2[1] + dt * uX;
238 | bisectorNormalLeft = [x, y];
239 | }
240 | } else {
241 | x = pnt2[0] + t * (pnt1[0] - pnt2[0]);
242 | y = pnt2[1] + t * (pnt1[1] - pnt2[1]);
243 | bisectorNormalRight = [x, y];
244 | x = pnt2[0] + t * (pnt3[0] - pnt2[0]);
245 | y = pnt2[1] + t * (pnt3[1] - pnt2[1]);
246 | bisectorNormalLeft = [x, y];
247 | }
248 | return [bisectorNormalRight, bisectorNormalLeft];
249 | };
250 |
251 | /**
252 | * 获取默认三点的内切圆
253 | * @param pnt1
254 | * @param pnt2
255 | * @param pnt3
256 | * @returns {[*,*]}
257 | */
258 | export const getNormal = (pnt1, pnt2, pnt3) => {
259 | let dX1 = pnt1[0] - pnt2[0];
260 | let dY1 = pnt1[1] - pnt2[1];
261 | const d1 = Math.sqrt(dX1 * dX1 + dY1 * dY1);
262 | dX1 /= d1;
263 | dY1 /= d1;
264 | let dX2 = pnt3[0] - pnt2[0];
265 | let dY2 = pnt3[1] - pnt2[1];
266 | const d2 = Math.sqrt(dX2 * dX2 + dY2 * dY2);
267 | dX2 /= d2;
268 | dY2 /= d2;
269 | const uX = dX1 + dX2;
270 | const uY = dY1 + dY2;
271 | return [uX, uY];
272 | };
273 |
274 | /**
275 | * 获取左边控制点
276 | * @param controlPoints
277 | * @param t
278 | * @returns {[*,*]}
279 | */
280 | export const getLeftMostControlPoint = (controlPoints, t) => {
281 | // eslint-disable-next-line
282 | let [pnt1, pnt2, pnt3, controlX, controlY] = [controlPoints[0], controlPoints[1], controlPoints[2], null, null];
283 | const pnts = getBisectorNormals(0, pnt1, pnt2, pnt3);
284 | const normalRight = pnts[0];
285 | const normal = getNormal(pnt1, pnt2, pnt3);
286 | const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
287 | if (dist > ZERO_TOLERANCE) {
288 | const mid = Mid(pnt1, pnt2);
289 | const pX = pnt1[0] - mid[0];
290 | const pY = pnt1[1] - mid[1];
291 | const d1 = MathDistance(pnt1, pnt2);
292 | const n = 2.0 / d1;
293 | const nX = -n * pY;
294 | const nY = n * pX;
295 | const a11 = nX * nX - nY * nY;
296 | const a12 = 2 * nX * nY;
297 | const a22 = nY * nY - nX * nX;
298 | const dX = normalRight[0] - mid[0];
299 | const dY = normalRight[1] - mid[1];
300 | controlX = mid[0] + a11 * dX + a12 * dY;
301 | controlY = mid[1] + a12 * dX + a22 * dY;
302 | } else {
303 | controlX = pnt1[0] + t * (pnt2[0] - pnt1[0]);
304 | controlY = pnt1[1] + t * (pnt2[1] - pnt1[1]);
305 | }
306 | return [controlX, controlY];
307 | };
308 |
309 | /**
310 | * 获取右边控制点
311 | * @param controlPoints
312 | * @param t
313 | * @returns {[*,*]}
314 | */
315 | export const getRightMostControlPoint = (controlPoints, t) => {
316 | const count = controlPoints.length;
317 | const pnt1 = controlPoints[count - 3];
318 | const pnt2 = controlPoints[count - 2];
319 | const pnt3 = controlPoints[count - 1];
320 | const pnts = getBisectorNormals(0, pnt1, pnt2, pnt3);
321 | const normalLeft = pnts[1];
322 | const normal = getNormal(pnt1, pnt2, pnt3);
323 | const dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1]);
324 | let [controlX, controlY] = [null, null];
325 | if (dist > ZERO_TOLERANCE) {
326 | const mid = Mid(pnt2, pnt3);
327 | const pX = pnt3[0] - mid[0];
328 | const pY = pnt3[1] - mid[1];
329 | const d1 = MathDistance(pnt2, pnt3);
330 | const n = 2.0 / d1;
331 | const nX = -n * pY;
332 | const nY = n * pX;
333 | const a11 = nX * nX - nY * nY;
334 | const a12 = 2 * nX * nY;
335 | const a22 = nY * nY - nX * nX;
336 | const dX = normalLeft[0] - mid[0];
337 | const dY = normalLeft[1] - mid[1];
338 | controlX = mid[0] + a11 * dX + a12 * dY;
339 | controlY = mid[1] + a12 * dX + a22 * dY;
340 | } else {
341 | controlX = pnt3[0] + t * (pnt2[0] - pnt3[0]);
342 | controlY = pnt3[1] + t * (pnt2[1] - pnt3[1]);
343 | }
344 | return [controlX, controlY];
345 | };
346 |
347 | /**
348 | * 插值曲线点
349 | * @param t
350 | * @param controlPoints
351 | * @returns {null}
352 | */
353 | export const getCurvePoints = (t, controlPoints) => {
354 | const leftControl = getLeftMostControlPoint(controlPoints, t);
355 | // eslint-disable-next-line
356 | let [pnt1, pnt2, pnt3, normals, points] = [null, null, null, [leftControl], []];
357 | for (let i = 0; i < controlPoints.length - 2; i++) {
358 | [pnt1, pnt2, pnt3] = [controlPoints[i], controlPoints[i + 1], controlPoints[i + 2]];
359 | const normalPoints = getBisectorNormals(t, pnt1, pnt2, pnt3);
360 | normals = normals.concat(normalPoints);
361 | }
362 | const rightControl = getRightMostControlPoint(controlPoints, t);
363 | if (rightControl) {
364 | normals.push(rightControl);
365 | }
366 | for (let i = 0; i < controlPoints.length - 1; i++) {
367 | pnt1 = controlPoints[i];
368 | pnt2 = controlPoints[i + 1];
369 | points.push(pnt1);
370 | for (let j = 0; j < FITTING_COUNT; j++) {
371 | const pnt = getCubicValue(j / FITTING_COUNT, pnt1, normals[i * 2], normals[i * 2 + 1], pnt2);
372 | points.push(pnt);
373 | }
374 | points.push(pnt2);
375 | }
376 | return points;
377 | };
378 |
379 | /**
380 | * 贝塞尔曲线
381 | * @param points
382 | * @returns {*}
383 | */
384 | export const getBezierPoints = function (points) {
385 | if (points.length <= 2) {
386 | return points;
387 | }
388 | const bezierPoints = [];
389 | const n = points.length - 1;
390 | for (let t = 0; t <= 1; t += 0.01) {
391 | let [x, y] = [0, 0];
392 | for (let index = 0; index <= n; index++) {
393 | // eslint-disable-next-line
394 | const factor = getBinomialFactor(n, index);
395 | const a = t ** index;
396 | const b = (1 - t) ** (n - index);
397 | x += factor * a * b * points[index][0];
398 | y += factor * a * b * points[index][1];
399 | }
400 | bezierPoints.push([x, y]);
401 | }
402 | bezierPoints.push(points[n]);
403 | return bezierPoints;
404 | };
405 |
406 | /**
407 | * 获取阶乘数据
408 | * @param n
409 | * @returns {number}
410 | */
411 | export const getFactorial = (n) => {
412 | let result = 1;
413 | switch (n) {
414 | case n <= 1:
415 | result = 1;
416 | break;
417 | case n === 2:
418 | result = 2;
419 | break;
420 | case n === 3:
421 | result = 6;
422 | break;
423 | case n === 24:
424 | result = 24;
425 | break;
426 | case n === 5:
427 | result = 120;
428 | break;
429 | default:
430 | for (let i = 1; i <= n; i++) {
431 | result *= i;
432 | }
433 | break;
434 | }
435 | return result;
436 | };
437 |
438 | /**
439 | * 获取二项分布
440 | * @param n
441 | * @param index
442 | * @returns {number}
443 | */
444 | export const getBinomialFactor = (n, index) => getFactorial(n) / (getFactorial(index) * getFactorial(n - index));
445 |
446 | /**
447 | * 插值线性点
448 | * @param points
449 | * @returns {*}
450 | */
451 | export const getQBSplinePoints = (points) => {
452 | if (points.length <= 2) {
453 | return points;
454 | }
455 | const [n, bSplinePoints] = [2, []];
456 | const m = points.length - n - 1;
457 | bSplinePoints.push(points[0]);
458 | for (let i = 0; i <= m; i++) {
459 | for (let t = 0; t <= 1; t += 0.05) {
460 | let [x, y] = [0, 0];
461 | for (let k = 0; k <= n; k++) {
462 | // eslint-disable-next-line
463 | const factor = getQuadricBSplineFactor(k, t);
464 | x += factor * points[i + k][0];
465 | y += factor * points[i + k][1];
466 | }
467 | bSplinePoints.push([x, y]);
468 | }
469 | }
470 | bSplinePoints.push(points[points.length - 1]);
471 | return bSplinePoints;
472 | };
473 |
474 | /**
475 | * 得到二次线性因子
476 | * @param k
477 | * @param t
478 | * @returns {number}
479 | */
480 | export const getQuadricBSplineFactor = (k, t) => {
481 | let res = 0;
482 | if (k === 0) {
483 | res = (t - 1) ** 2 / 2;
484 | } else if (k === 1) {
485 | res = (-2 * t ** 2 + 2 * t + 1) / 2;
486 | } else if (k === 2) {
487 | res = t ** 2 / 2;
488 | }
489 | return res;
490 | };
491 |
492 | /**
493 | * 获取id
494 | * @returns {*|string|!Array.}
495 | */
496 | export const getuuid = () => {
497 | const [s, hexDigits] = [[], '0123456789abcdef'];
498 | for (let i = 0; i < 36; i++) {
499 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
500 | }
501 | s[14] = '4';
502 | // eslint-disable-next-line no-bitwise
503 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
504 | // eslint-disable-next-line no-multi-assign
505 | s[8] = s[13] = s[18] = s[23] = '-';
506 | return s.join('');
507 | };
508 |
509 | /**
510 | * 添加标识
511 | * @param obj
512 | * @returns {*}
513 | */
514 | export const stamp = function (obj) {
515 | const key = '_event_id_';
516 | obj[key] = obj[key] || getuuid();
517 | return obj[key];
518 | };
519 |
520 | /**
521 | * 去除字符串前后空格
522 | * @param str
523 | * @returns {*}
524 | */
525 | export const trim = (str) => (str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''));
526 |
527 | /**
528 | * 将类名截取成数组
529 | * @param str
530 | * @returns {Array|*}
531 | */
532 | export const splitWords = (str) => trim(str).split(/\s+/);
533 |
534 | /**
535 | * 判断是否为对象
536 | * @param value
537 | * @returns {boolean}
538 | */
539 | export const isObject = (value) => {
540 | const type = typeof value;
541 | return value !== null && (type === 'object' || type === 'function');
542 | };
543 |
544 | /**
545 | * merge
546 | * @param a
547 | * @param b
548 | * @returns {*}
549 | */
550 | export const merge = (a, b) => {
551 | // eslint-disable-next-line no-restricted-syntax
552 | for (const key in b) {
553 | if (isObject(b[key]) && isObject(a[key])) {
554 | merge(a[key], b[key]);
555 | } else {
556 | a[key] = b[key];
557 | }
558 | }
559 | return a;
560 | };
561 |
562 | export function preventDefault(e) {
563 | // eslint-disable-next-line no-param-reassign
564 | e = e || window.event;
565 | if (e.preventDefault) {
566 | e.preventDefault();
567 | } else {
568 | e.returnValue = false;
569 | }
570 | }
571 |
572 | export function bindAll(fns, context) {
573 | fns.forEach((fn) => {
574 | if (!context[fn]) {
575 | return;
576 | }
577 | context[fn] = context[fn].bind(context);
578 | });
579 | }
580 |
--------------------------------------------------------------------------------
/src/base.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import * as CesiumTypeOnly from 'cesium';
3 | import {
4 | State,
5 | GeometryStyle,
6 | PolygonStyle,
7 | LineStyle,
8 | EventType,
9 | EventListener,
10 | VisibleAnimationOpts,
11 | GrowthAnimationOpts,
12 | } from './interface';
13 | import EventDispatcher from './events';
14 | import cloneDeep from 'lodash.clonedeep';
15 | // import merge from 'lodash.merge';
16 | import * as Utils from './utils';
17 |
18 | export default class Base {
19 | cesium: typeof CesiumTypeOnly;
20 | viewer: CesiumTypeOnly.Viewer;
21 | eventHandler: CesiumTypeOnly.ScreenSpaceEventHandler;
22 | polygonEntity: CesiumTypeOnly.Entity;
23 | geometryPoints: CesiumTypeOnly.Cartesian3[] = [];
24 | state: State = 'drawing';
25 | controlPoints: CesiumTypeOnly.EntityCollection = [];
26 | controlPointsEventHandler: CesiumTypeOnly.ScreenSpaceEventHandler;
27 | lineEntity: CesiumTypeOnly.Entity;
28 | type!: 'polygon' | 'line';
29 | freehand!: boolean;
30 | style: GeometryStyle | undefined;
31 | outlineEntity: CesiumTypeOnly.Entity;
32 | eventDispatcher: EventDispatcher;
33 | dragEventHandler: CesiumTypeOnly.ScreenSpaceEventHandler;
34 | entityId: string = '';
35 | points: CesiumTypeOnly.Cartesian3[] = [];
36 | styleCache: GeometryStyle | undefined;
37 | minPointsForShape: number = 0;
38 | tempLineEntity: CesiumTypeOnly.Entity;
39 |
40 | constructor(cesium: CesiumTypeOnly, viewer: CesiumTypeOnly.Viewer, style?: GeometryStyle) {
41 | this.cesium = cesium;
42 | this.viewer = viewer;
43 | this.type = this.getType();
44 |
45 | this.mergeStyle(style);
46 | this.cartesianToLnglat = this.cartesianToLnglat.bind(this);
47 | this.pixelToCartesian = this.pixelToCartesian.bind(this);
48 | this.eventDispatcher = new EventDispatcher();
49 | // Disable default behavior for double-clicking on entities.
50 | viewer.trackedEntity = undefined;
51 | viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
52 |
53 | this.onClick();
54 | }
55 |
56 | mergeStyle(style: GeometryStyle | undefined) {
57 | if (this.type === 'polygon') {
58 | this.style = Object.assign(
59 | {
60 | material: new this.cesium.Color(),
61 | outlineMaterial: new this.cesium.Color(),
62 | outlineWidth: 2,
63 | },
64 | style,
65 | );
66 | } else if (this.type === 'line') {
67 | this.style = Object.assign(
68 | {
69 | material: new this.cesium.Color(),
70 | lineWidth: 2,
71 | },
72 | style,
73 | );
74 | }
75 | //Cache the initial settings to avoid modification of properties due to reference type assignment.
76 | this.styleCache = cloneDeep(this.style);
77 | }
78 |
79 | /**
80 | * The base class provides a method to change the state, and different logic is implemented based on the state.
81 | * The state is controlled by individual sub-components according to the actual situation.
82 | * @param state
83 | */
84 | setState(state: State) {
85 | this.state = state;
86 | }
87 |
88 | getState(): State {
89 | return this.state;
90 | }
91 |
92 | /**
93 | * Bind a global click event that responds differently based on the state. When in the drawing state,
94 | * a click will add points for geometric shapes. During editing, selecting a drawn shape puts it in an
95 | * editable state. Clicking on empty space sets it to a static state.
96 | */
97 | onClick() {
98 | this.eventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas);
99 | this.eventHandler.setInputAction((evt: any) => {
100 | const pickedObject = this.viewer.scene.pick(evt.position);
101 | const hitEntities = this.cesium.defined(pickedObject) && pickedObject.id instanceof this.cesium.Entity;
102 | let activeEntity = this.polygonEntity;
103 | if (this.type === 'line') {
104 | activeEntity = this.lineEntity;
105 | }
106 |
107 | if (this.state === 'drawing') {
108 | // In the drawing state, the points clicked are key nodes of the shape, and they are saved in this.points.
109 | const cartesian = this.pixelToCartesian(evt.position);
110 | const points = this.getPoints();
111 | // If the click is outside the sphere, position information cannot be obtained.
112 | if (!cartesian) {
113 | return;
114 | }
115 |
116 | // "For non-freehand drawn shapes, validate that the distance between two consecutive clicks is greater than 10 meters
117 | if (!this.freehand && points.length > 0 && !this.checkDistance(cartesian, points[points.length - 1])) {
118 | return;
119 | }
120 | this.addPoint(cartesian);
121 |
122 | // Trigger 'drawStart' when the first point is being drawn.
123 | if (this.getPoints().length === 1) {
124 | this.eventDispatcher.dispatchEvent('drawStart');
125 | }
126 | this.eventDispatcher.dispatchEvent('drawUpdate', cartesian);
127 | } else if (this.state === 'edit') {
128 | //In edit mode, exit the editing state and delete control points when clicking outside the currently edited shape.
129 | if (!hitEntities || activeEntity.id !== pickedObject.id.id) {
130 | this.setState('static');
131 | this.removeControlPoints();
132 | this.disableDrag();
133 | // Trigger 'drawEnd' and return the geometry shape points when exiting the edit mode.
134 | this.eventDispatcher.dispatchEvent('editEnd', this.getPoints());
135 | }
136 | } else if (this.state === 'static') {
137 | //When drawing multiple shapes, the click events for all shapes are triggered. Only when hitting a completed shape should it enter editing mode.
138 | if (hitEntities && activeEntity.id === pickedObject.id.id) {
139 | const pickedGraphics = this.type === 'line' ? pickedObject.id.polyline : pickedObject.id.polygon;
140 | if (this.cesium.defined(pickedGraphics)) {
141 | // Hit Geometry Shape.
142 | this.setState('edit');
143 | this.addControlPoints();
144 | this.draggable();
145 | this.eventDispatcher.dispatchEvent('editStart');
146 | }
147 | }
148 | }
149 | }, this.cesium.ScreenSpaceEventType.LEFT_CLICK);
150 | }
151 |
152 | onMouseMove() {
153 | this.eventHandler.setInputAction((evt: any) => {
154 | const points = this.getPoints();
155 | const cartesian = this.pixelToCartesian(evt.endPosition);
156 | if (!cartesian) {
157 | return;
158 | }
159 | if (this.checkDistance(cartesian, points[points.length - 1])) {
160 | // Synchronize data to subclasses.If the distance is less than 10 meters, do not proceed
161 | this.updateMovingPoint(cartesian, points.length);
162 | }
163 | }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE);
164 | }
165 |
166 | onDoubleClick() {
167 | this.eventHandler.setInputAction((evt: any) => {
168 | if (this.state === 'drawing') {
169 | this.finishDrawing();
170 | }
171 | }, this.cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
172 | }
173 |
174 | /**
175 | * Check if the distance between two points is greater than 10 meters.
176 | */
177 | checkDistance(cartesian1: CesiumTypeOnly.Cartesian3, cartesian2: CesiumTypeOnly.Cartesian3) {
178 | const distance = this.cesium.Cartesian3.distance(cartesian1, cartesian2);
179 | return distance > 10;
180 | }
181 |
182 | finishDrawing() {
183 | // Some polygons draw a separate line between the first two points before drawing the complete shape;
184 | // this line should be removed after drawing is complete.
185 | this.type === 'polygon' && this.lineEntity && this.viewer.entities.remove(this.lineEntity);
186 |
187 | this.removeMoveListener();
188 | // Editable upon initial drawing completion.
189 | this.setState('edit');
190 | this.addControlPoints();
191 | this.draggable();
192 | const entity = this.polygonEntity || this.lineEntity;
193 | this.entityId = entity.id;
194 | /**
195 | * "I've noticed that CallbackProperty can lead to significant performance issues.
196 | * After drawing multiple shapes, the map becomes noticeably laggy. Using methods
197 | * like requestAnimationFrame or setInterval doesn't provide a smooth way to display
198 | * shapes during the drawing process. As a temporary solution, I've set the hierarchy
199 | * or positions to static after drawing is complete. This addresses the performance
200 | * problem, but introduces a new issue: after setting the data to static, the shapes
201 | * redraw, resulting in a flicker. However, this seems to be a relatively reasonable
202 | * approach given the current circumstances."
203 | */
204 | // TODO...
205 | // if (this.type === 'polygon') {
206 | // this.polygonEntity.polygon.hierarchy = new this.cesium.PolygonHierarchy(this.geometryPoints);
207 | // this.outlineEntity.polyline.positions = [...this.geometryPoints, this.geometryPoints[0]];
208 | // } else if (this.type === 'line') {
209 | // this.lineEntity.polyline.positions = this.geometryPoints;
210 | // }
211 |
212 | this.eventDispatcher.dispatchEvent('drawEnd', this.getPoints());
213 | }
214 |
215 | removeClickListener() {
216 | this.eventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_CLICK);
217 | }
218 |
219 | removeMoveListener() {
220 | this.eventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.MOUSE_MOVE);
221 | }
222 |
223 | removeDoubleClickListener() {
224 | this.eventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
225 | }
226 |
227 | setGeometryPoints(geometryPoints: CesiumTypeOnly.Cartesian3[]) {
228 | this.geometryPoints = geometryPoints;
229 | }
230 |
231 | getGeometryPoints(): CesiumTypeOnly.Cartesian3[] {
232 | return this.geometryPoints;
233 | }
234 |
235 | drawPolygon() {
236 | const callback = () => {
237 | return new this.cesium.PolygonHierarchy(this.geometryPoints);
238 | };
239 | if (!this.polygonEntity) {
240 | const style = this.style as PolygonStyle;
241 | this.polygonEntity = this.viewer.entities.add({
242 | polygon: new this.cesium.PolygonGraphics({
243 | hierarchy: new this.cesium.CallbackProperty(callback, false),
244 | show: true,
245 | material: style.material,
246 | }),
247 | });
248 |
249 | // Due to limitations in PolygonGraphics outlining, a separate line style is drawn.
250 | this.outlineEntity = this.viewer.entities.add({
251 | polyline: {
252 | positions: new this.cesium.CallbackProperty(() => {
253 | return [...this.geometryPoints, this.geometryPoints[0]];
254 | }, false),
255 | width: style.outlineWidth,
256 | material: style.outlineMaterial,
257 | clampToGround: true,
258 | },
259 | });
260 | }
261 | }
262 |
263 | drawLine() {
264 | if (!this.lineEntity) {
265 | const style = this.style as LineStyle;
266 | this.lineEntity = this.addLineEntity(style);
267 | }
268 | }
269 |
270 | addTempLine() {
271 | if (!this.tempLineEntity) {
272 | // The line style between the first two points matches the outline style.
273 | const style = this.style as PolygonStyle;
274 | const lineStyle = {
275 | material: style.outlineMaterial,
276 | lineWidth: style.outlineWidth,
277 | };
278 | this.tempLineEntity = this.addLineEntity(lineStyle);
279 | }
280 | }
281 |
282 | removeTempLine() {
283 | if (this.tempLineEntity) {
284 | this.viewer.entities.remove(this.tempLineEntity);
285 | }
286 | }
287 |
288 | addLineEntity(style: LineStyle) {
289 | const entity = this.viewer.entities.add({
290 | polyline: {
291 | positions: new this.cesium.CallbackProperty(() => this.geometryPoints, false),
292 | width: style.lineWidth,
293 | material: style.material,
294 | clampToGround: true,
295 | },
296 | });
297 | return entity;
298 | }
299 |
300 | cartesianToLnglat(cartesian: CesiumTypeOnly.Cartesian3): [number, number] {
301 | const lnglat = this.viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian);
302 | const lat = this.cesium.Math.toDegrees(lnglat.latitude);
303 | const lng = this.cesium.Math.toDegrees(lnglat.longitude);
304 | return [lng, lat];
305 | }
306 |
307 | pixelToCartesian(position: CesiumTypeOnly.Cartesian2): CesiumTypeOnly.Cartesian3 | undefined {
308 | const ray = this.viewer.camera.getPickRay(position);
309 | const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
310 | return cartesian;
311 | }
312 |
313 | /**
314 | * Display key points when creating a shape, allowing dragging of these points to edit and generate new shapes.
315 | */
316 | addControlPoints() {
317 | const points = this.getPoints();
318 | this.controlPoints = points.map((position) => {
319 | // return this.viewer.entities.add({
320 | // position,
321 | // billboard: {
322 | // image: './src/assets/circle_red.png',
323 | // },
324 | // });
325 |
326 | return this.viewer.entities.add({
327 | position,
328 | point: {
329 | pixelSize: 10,
330 | heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
331 | color: this.cesium.Color.RED,
332 | },
333 | });
334 | });
335 |
336 | let isDragging = false;
337 | let draggedIcon: CesiumTypeOnly.Entity = null;
338 | let dragStartPosition: CesiumTypeOnly.Cartesian3;
339 |
340 | this.controlPointsEventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas);
341 |
342 | // Listen for left mouse button press events
343 | this.controlPointsEventHandler.setInputAction((clickEvent: any) => {
344 | const pickedObject = this.viewer.scene.pick(clickEvent.position);
345 |
346 | if (this.cesium.defined(pickedObject)) {
347 | for (let i = 0; i < this.controlPoints.length; i++) {
348 | if (pickedObject.id === this.controlPoints[i]) {
349 | isDragging = true;
350 | draggedIcon = this.controlPoints[i];
351 | dragStartPosition = draggedIcon.position._value;
352 | //Save the index of dragged points for dynamic updates during movement
353 | draggedIcon.index = i;
354 | break;
355 | }
356 | }
357 | // Disable default camera interaction.
358 | this.viewer.scene.screenSpaceCameraController.enableRotate = false;
359 | }
360 | }, this.cesium.ScreenSpaceEventType.LEFT_DOWN);
361 |
362 | // Listen for mouse movement events
363 | this.controlPointsEventHandler.setInputAction((moveEvent: any) => {
364 | if (isDragging && draggedIcon) {
365 | const cartesian = this.viewer.camera.pickEllipsoid(moveEvent.endPosition, this.viewer.scene.globe.ellipsoid);
366 | if (cartesian) {
367 | draggedIcon.position.setValue(cartesian);
368 | this.updateDraggingPoint(cartesian, draggedIcon.index);
369 | }
370 | }
371 | }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE);
372 |
373 | // Listen for left mouse button release events
374 | this.controlPointsEventHandler.setInputAction(() => {
375 | // Trigger 'drawUpdate' when there is a change in coordinates before and after dragging.
376 | if (draggedIcon && !this.cesium.Cartesian3.equals(dragStartPosition, draggedIcon.position._value)) {
377 | this.eventDispatcher.dispatchEvent('drawUpdate', draggedIcon.position._value);
378 | }
379 | isDragging = false;
380 | draggedIcon = null;
381 | this.viewer.scene.screenSpaceCameraController.enableRotate = true;
382 | }, this.cesium.ScreenSpaceEventType.LEFT_UP);
383 | }
384 |
385 | removeControlPoints() {
386 | if (this.controlPoints.length > 0) {
387 | this.controlPoints.forEach((entity: CesiumTypeOnly.Entity) => {
388 | this.viewer.entities.remove(entity);
389 | });
390 | this.controlPointsEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_DOWN);
391 | this.controlPointsEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.MOUSE_MOVE);
392 | this.controlPointsEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_UP);
393 | }
394 | }
395 |
396 | /**
397 | * Allow the entire shape to be dragged while in edit mode.
398 | */
399 | draggable() {
400 | let dragging = false;
401 | let startPosition: CesiumTypeOnly.Cartesian3 | undefined;
402 | this.dragEventHandler = new this.cesium.ScreenSpaceEventHandler(this.viewer.canvas);
403 | this.dragEventHandler.setInputAction((event: any) => {
404 | const pickRay = this.viewer.scene.camera.getPickRay(event.position);
405 | if (pickRay) {
406 | const cartesian = this.viewer.scene.globe.pick(pickRay, this.viewer.scene);
407 | const pickedObject = this.viewer.scene.pick(event.position);
408 | if (this.cesium.defined(pickedObject) && pickedObject.id instanceof this.cesium.Entity) {
409 | const clickedEntity = pickedObject.id;
410 | if (this.isCurrentEntity(clickedEntity.id)) {
411 | //Clicking on the current instance's entity initiates drag logic.
412 | dragging = true;
413 | startPosition = cartesian;
414 | this.viewer.scene.screenSpaceCameraController.enableRotate = false;
415 | }
416 | }
417 | }
418 | }, this.cesium.ScreenSpaceEventType.LEFT_DOWN);
419 |
420 | this.dragEventHandler.setInputAction((event: any) => {
421 | if (dragging && startPosition) {
422 | // Retrieve the world coordinates of the current mouse position.
423 | const newPosition = this.pixelToCartesian(event.endPosition);
424 | if (newPosition) {
425 | // Calculate the displacement vector.
426 | const translation = this.cesium.Cartesian3.subtract(newPosition, startPosition, new this.cesium.Cartesian3());
427 | const newPoints = this.geometryPoints.map((p) => {
428 | return this.cesium.Cartesian3.add(p, translation, new this.cesium.Cartesian3());
429 | });
430 |
431 | //Move all key points according to a vector.
432 | this.points = this.points.map((p) => {
433 | return this.cesium.Cartesian3.add(p, translation, new this.cesium.Cartesian3());
434 | });
435 |
436 | // Move control points in the same manner.
437 | this.controlPoints.map((p: CesiumTypeOnly.Entity) => {
438 | const position = p.position?.getValue(this.cesium.JulianDate.now());
439 | const newPosition = this.cesium.Cartesian3.add(position, translation, new this.cesium.Cartesian3());
440 | p.position?.setValue(newPosition);
441 | });
442 |
443 | this.setGeometryPoints(newPoints);
444 | if (this.minPointsForShape === 4) {
445 | // 双箭头在整体被拖拽时,需要同步更新生长动画的插值点
446 | this.curveControlPointLeft = this.cesium.Cartesian3.add(this.curveControlPointLeft, translation, new this.cesium.Cartesian3());
447 | this.curveControlPointRight = this.cesium.Cartesian3.add(this.curveControlPointRight, translation, new this.cesium.Cartesian3());
448 | }
449 | startPosition = newPosition;
450 | }
451 | } else {
452 | const pickRay = this.viewer.scene.camera.getPickRay(event.endPosition);
453 | if (pickRay) {
454 | const pickedObject = this.viewer.scene.pick(event.endPosition);
455 | if (this.cesium.defined(pickedObject) && pickedObject.id instanceof this.cesium.Entity) {
456 | const clickedEntity = pickedObject.id;
457 | // TODO 绘制的图形,需要特殊id标识,可在创建entity时指定id
458 | if (this.isCurrentEntity(clickedEntity.id)) {
459 | this.viewer.scene.canvas.style.cursor = 'move';
460 | } else {
461 | this.viewer.scene.canvas.style.cursor = 'default';
462 | }
463 | } else {
464 | this.viewer.scene.canvas.style.cursor = 'default';
465 | }
466 | }
467 | }
468 | }, this.cesium.ScreenSpaceEventType.MOUSE_MOVE);
469 |
470 | // Listen for the mouse release event to end dragging.
471 | this.dragEventHandler.setInputAction(() => {
472 | dragging = false;
473 | startPosition = undefined;
474 | this.viewer.scene.screenSpaceCameraController.enableRotate = true;
475 | }, this.cesium.ScreenSpaceEventType.LEFT_UP);
476 | }
477 |
478 | // Finish editing, disable dragging."
479 | disableDrag() {
480 | this.dragEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_DOWN);
481 | this.dragEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.MOUSE_MOVE);
482 | this.dragEventHandler.removeInputAction(this.cesium.ScreenSpaceEventType.LEFT_UP);
483 | }
484 |
485 | show(opts: VisibleAnimationOpts) {
486 | if (opts) {
487 | const { duration, delay, callback } = opts;
488 | this.showWithAnimation(duration, delay, callback);
489 | return;
490 | } else {
491 | this.showWithAnimation(0, 0);
492 | }
493 | }
494 |
495 | hide(opts: VisibleAnimationOpts) {
496 | if (opts) {
497 | const { duration, delay, callback } = opts;
498 | this.hideWithAnimation(duration, delay, callback);
499 | return;
500 | } else {
501 | this.hideWithAnimation(0, 0);
502 | }
503 | }
504 |
505 | showWithAnimation(duration: number = 2000, delay: number = 0, callback?: () => void) {
506 | if (this.state !== 'hidden') {
507 | //If not in a static state or already displayed, do not process.
508 | return;
509 | }
510 | this.setState('static');
511 | if (this.type === 'polygon') {
512 | let alpha = 0.3;
513 | const material = this.styleCache.material;
514 | if (material.image) {
515 | // With Texture
516 | alpha = material.color.getValue().alpha;
517 | } else {
518 | alpha = material.alpha;
519 | }
520 |
521 | this.animateOpacity(this.polygonEntity, alpha, duration, delay, callback, this.state);
522 | const outlineAlpha = this.styleCache?.outlineMaterial?.alpha;
523 | this.animateOpacity(this.outlineEntity, outlineAlpha || 1.0, duration, delay, undefined, this.state);
524 | } else if (this.type === 'line') {
525 | const material = this.styleCache.material;
526 | let alpha = 1.0;
527 | if (material.image) {
528 | // With Texture
529 | alpha = material.color.alpha;
530 | } else if (material.dashLength) {
531 | // Dashed Line
532 | const color = material.color.getValue();
533 | alpha = color.alpha;
534 | } else {
535 | // Solid Color
536 | alpha = this.styleCache?.material?.alpha;
537 | }
538 | this.animateOpacity(this.lineEntity, alpha, duration, delay, callback, this.state);
539 | }
540 | if (duration != 0) {
541 | this.setState('animating');
542 | }
543 | }
544 |
545 | hideWithAnimation(duration: number = 2000, delay: number = 0, callback?: () => void) {
546 | if (this.state === 'hidden' || this.state != 'static') {
547 | return;
548 | }
549 | this.setState('hidden');
550 | if (this.type === 'polygon') {
551 | this.animateOpacity(this.polygonEntity, 0.0, duration, delay, callback, this.state);
552 | this.animateOpacity(this.outlineEntity, 0.0, duration, delay, undefined, this.state);
553 | } else if (this.type === 'line') {
554 | this.animateOpacity(this.lineEntity, 0.0, duration, delay, callback, this.state);
555 | }
556 | // if (this.state == 'edit') {
557 | // this.controlPoints.forEach(p => {
558 | // this.animateOpacity(p, 0.0, duration, delay, undefined, this.state);
559 | // });
560 | // }
561 | if (duration != 0) {
562 | this.setState('animating');
563 | }
564 | }
565 |
566 | animateOpacity(
567 | entity: CesiumTypeOnly.Entity,
568 | targetAlpha: number,
569 | duration: number,
570 | delay: number,
571 | callback?: () => void,
572 | state?: State,
573 | ): void {
574 | setTimeout(() => {
575 | const graphics = entity.polygon || entity.polyline || entity.billboard;
576 | let startAlpha: number;
577 | let material = graphics.material;
578 | if (material) {
579 | if (material.image && material.color.alpha !== undefined) {
580 | // Texture material, setting the alpha channel in the color of the custom ImageFlowMaterialProperty.
581 | startAlpha = material.color.alpha;
582 | } else {
583 | startAlpha = material.color.getValue().alpha;
584 | }
585 | } else {
586 | // billbord
587 | const color = graphics.color.getValue();
588 | startAlpha = color.alpha;
589 | }
590 |
591 | let startTime = 0;
592 |
593 | const animate = (currentTime: number) => {
594 | if (!startTime) {
595 | startTime = currentTime;
596 | }
597 | const elapsedTime = currentTime - startTime;
598 |
599 | if (elapsedTime < duration) {
600 | const deltalpha = (elapsedTime / duration) * (targetAlpha - startAlpha);
601 | const newAlpha = startAlpha + deltalpha;
602 |
603 | if (material) {
604 | if (material.image && material.color.alpha !== undefined) {
605 | // Texture Material
606 | material.color.alpha = newAlpha;
607 | } else {
608 | // Solid Color
609 | const newColor = material.color.getValue().withAlpha(newAlpha);
610 | material.color.setValue(newColor);
611 | }
612 | } else {
613 | // billbord
614 | const color = graphics.color.getValue();
615 | const newColor = color.withAlpha(newAlpha);
616 | graphics.color.setValue(newColor);
617 | }
618 |
619 | requestAnimationFrame(animate);
620 | } else {
621 | // Animation Ended
622 | callback && callback();
623 | const restoredState = state ? state : 'static';
624 |
625 | // if (targetAlpha === 0) {
626 | // this.setState('hidden');
627 | // }
628 |
629 | // if (duration == 0) {
630 | // this.setState('drawing');
631 | if (material) {
632 | if (material.image && material.color.alpha !== undefined) {
633 | // Texture Material
634 | material.color.alpha = targetAlpha;
635 | } else {
636 | // Solid Color
637 | const newColor = material.color.getValue().withAlpha(targetAlpha);
638 | material.color.setValue(newColor);
639 | }
640 | } else {
641 | // billbord
642 | const color = graphics.color.getValue();
643 | const newColor = color.withAlpha(targetAlpha);
644 | graphics.color.setValue(newColor);
645 | }
646 | requestAnimationFrame(() => {
647 | this.setState(restoredState);
648 | });
649 | // } else {
650 | // this.setState(restoredState);
651 | // }
652 | }
653 | };
654 |
655 | requestAnimationFrame(animate);
656 | }, delay);
657 | }
658 |
659 | startGrowthAnimation(opts: GrowthAnimationOpts) {
660 | const { duration = 2000, delay = 0, callback } = opts || {};
661 | if (this.state === 'hidden' || this.state != 'static') {
662 | return;
663 | }
664 | if (!this.minPointsForShape) {
665 | console.warn('Growth animation is not supported for this type of shape');
666 | return;
667 | }
668 | this.setState('animating');
669 | if (this.minPointsForShape === 4) {
670 | // For double arrows, special handling is required.
671 | this.doubleArrowGrowthAnimation(duration, delay, callback);
672 | return;
673 | }
674 | setTimeout(() => {
675 | this.hideWithAnimation(0, 0, undefined);
676 | const points = this.getPoints();
677 |
678 | let segmentDuration = 0;
679 | if (this.minPointsForShape === 2) {
680 | segmentDuration = duration / (points.length - 1);
681 | } else {
682 | segmentDuration = duration / (points.length - 2);
683 | }
684 |
685 | let startTime = Date.now();
686 | let movingPointIndex = 0;
687 | this.viewer.clock.shouldAnimate = true;
688 |
689 | const frameListener = (clock) => {
690 | const currentTime = Date.now();
691 | const elapsedTime = currentTime - startTime;
692 | if (elapsedTime >= duration) {
693 | // Animation ends
694 | callback && callback();
695 | startTime = 0;
696 | this.viewer.clock.shouldAnimate = false;
697 | this.viewer.clock.onTick.removeEventListener(frameListener);
698 | this.setState('static');
699 | return;
700 | }
701 |
702 | const currentSegment = Math.floor(elapsedTime / segmentDuration);
703 | let startPoint;
704 |
705 | if (this.minPointsForShape === 2) {
706 | movingPointIndex = currentSegment + 1;
707 | } else {
708 | movingPointIndex = currentSegment + 2;
709 | }
710 | startPoint = points[movingPointIndex - 1];
711 | if (currentSegment == 0 && this.minPointsForShape === 3) {
712 | // The face-arrow determined by three points, with the animation starting from the midpoint of the line connecting the first two points.
713 | startPoint = this.cesium.Cartesian3.midpoint(points[0], points[1], new this.cesium.Cartesian3());
714 | }
715 | let endPoint = points[movingPointIndex];
716 | // To dynamically add points between the startPoint and endPoint, consistent with the initial drawing logic,
717 | // update the point at index movingPointIndex in the points array with the newPosition,
718 | // generate the arrow, and execute the animation.
719 | const t = (elapsedTime - currentSegment * segmentDuration) / segmentDuration;
720 | const newPosition = this.cesium.Cartesian3.lerp(startPoint, endPoint, t, new this.cesium.Cartesian3());
721 | const tempPoints = points.slice(0, movingPointIndex + 1);
722 | tempPoints[tempPoints.length - 1] = newPosition;
723 | const geometryPoints = this.createGraphic(tempPoints);
724 | this.setGeometryPoints(geometryPoints);
725 | this.showWithAnimation(0, 0, undefined);
726 | };
727 | this.viewer.clock.onTick.addEventListener(frameListener);
728 | }, delay);
729 | }
730 |
731 | private doubleArrowGrowthAnimation(duration: number = 2000, delay: number = 0, callback?: Function) {
732 | setTimeout(() => {
733 | this.hideWithAnimation(0, 0, undefined);
734 | const points = this.getPoints();
735 | let startTime = Date.now();
736 | this.viewer.clock.shouldAnimate = true;
737 |
738 | const frameListener = (clock) => {
739 | const currentTime = Date.now();
740 | const elapsedTime = currentTime - startTime;
741 | if (elapsedTime >= duration) {
742 | // Animation ends
743 | callback && callback();
744 | startTime = 0;
745 | this.viewer.clock.shouldAnimate = false;
746 | this.viewer.clock.onTick.removeEventListener(frameListener);
747 | this.setState('static');
748 | return;
749 | }
750 |
751 | // Utils.isClockWise(pnt1, pnt2, pnt3)
752 | const midPoint = this.cesium.Cartesian3.midpoint(points[0], points[1], new this.cesium.Cartesian3());
753 |
754 | const startPointLeft = this.cesium.Cartesian3.midpoint(points[0], midPoint, new this.cesium.Cartesian3());
755 |
756 | const startPointRight = this.cesium.Cartesian3.midpoint(midPoint, points[1], new this.cesium.Cartesian3());
757 | let endPointLeft = points[3];
758 | let endPointRight = points[2];
759 | const t = elapsedTime / duration;
760 | const controlPoint = this.getBezierControlPointforGrowthAnimation();
761 | let curveControlPointsLeft = [startPointLeft, controlPoint.left, endPointLeft];
762 | let curveControlPointsRight = [startPointRight, controlPoint.right, endPointRight];
763 | const newPositionLeft = this.getNewPosition(curveControlPointsLeft, t);
764 | const newPositionRight = this.getNewPosition(curveControlPointsRight, t);
765 |
766 | // Assist in viewing exercise routes
767 | // this.viewer.entities.add({
768 | // position: newPositionLeft,
769 | // point: {
770 | // pixelSize: 4,
771 | // heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
772 | // color: this.cesium.Color.RED,
773 | // },
774 | // });
775 | // this.viewer.entities.add({
776 | // position: newPositionRight,
777 | // point: {
778 | // pixelSize: 4,
779 | // heightReference: this.cesium.HeightReference.CLAMP_TO_GROUND,
780 | // color: this.cesium.Color.RED,
781 | // },
782 | // });
783 | const tempPoints = [...points];
784 | tempPoints[2] = newPositionRight;
785 | tempPoints[3] = newPositionLeft;
786 | const geometryPoints = this.createGraphic(tempPoints);
787 | this.setGeometryPoints(geometryPoints);
788 | this.showWithAnimation(0, 0, undefined);
789 | };
790 | this.viewer.clock.onTick.addEventListener(frameListener);
791 | }, delay);
792 | }
793 |
794 | private getNewPosition(curveControlPoints, t) {
795 | curveControlPoints = curveControlPoints.map((item) => {
796 | return this.cartesianToLnglat(item);
797 | });
798 | let curvePoints = Utils.getCurvePoints(0.3, curveControlPoints);
799 | curvePoints = curvePoints.map((p) => {
800 | return this.cesium.Cartesian3.fromDegrees(p[0], p[1]);
801 | });
802 |
803 | let newPosition = this.interpolateAlongCurve(curvePoints, t);
804 | return newPosition;
805 | }
806 |
807 | private interpolateAlongCurve(curvePoints, t) {
808 | const numPoints = curvePoints.length - 1;
809 | const index = Math.floor(t * numPoints);
810 | const tSegment = t * numPoints - index;
811 | const startPoint = curvePoints[index];
812 | const endPoint = curvePoints[index + 1];
813 | const x = startPoint.x + (endPoint.x - startPoint.x) * tSegment;
814 | const y = startPoint.y + (endPoint.y - startPoint.y) * tSegment;
815 | const z = startPoint.z + (endPoint.z - startPoint.z) * tSegment;
816 |
817 | return new this.cesium.Cartesian3(x, y, z);
818 | }
819 |
820 | remove() {
821 | if (this.type === 'polygon') {
822 | this.viewer.entities.remove(this.polygonEntity);
823 | this.viewer.entities.remove(this.outlineEntity);
824 | this.polygonEntity = null;
825 | this.outlineEntity = null;
826 | this.lineEntity = null;
827 | } else if (this.type === 'line') {
828 | this.viewer.entities.remove(this.lineEntity);
829 | }
830 | this.removeClickListener();
831 | this.removeMoveListener();
832 | this.removeDoubleClickListener();
833 | this.removeControlPoints();
834 | }
835 |
836 | on(eventType: EventType, listener: EventListener) {
837 | this.eventDispatcher.on(eventType, listener);
838 | }
839 |
840 | off(eventType: EventType, listener: EventListener) {
841 | this.eventDispatcher.off(eventType, listener);
842 | }
843 |
844 | isCurrentEntity(id: string) {
845 | // return this.entityId === `CesiumPlot-${id}`;
846 | return this.entityId === id;
847 | }
848 |
849 | addPoint(cartesian: CesiumTypeOnly.Cartesian3) {
850 | //Abstract method that must be implemented by subclasses.
851 | }
852 |
853 | getPoints(): CesiumTypeOnly.Cartesian3[] {
854 | //Abstract method that must be implemented by subclasses.
855 | return [new this.cesium.Cartesian3()];
856 | }
857 |
858 | updateMovingPoint(cartesian: CesiumTypeOnly.Cartesian3, index?: number) {
859 | //Abstract method that must be implemented by subclasses.
860 | }
861 |
862 | updateDraggingPoint(cartesian: CesiumTypeOnly.Cartesian3, index: number) {
863 | //Abstract method that must be implemented by subclasses.
864 | }
865 |
866 | getType(): 'polygon' | 'line' {
867 | return 'polygon';
868 | //Abstract method that must be implemented by subclasses.
869 | }
870 |
871 | createGraphic(points: CesiumTypeOnly.Cartesian3[]): CesiumTypeOnly.Cartesian3[] {
872 | //Abstract method that must be implemented by subclasses.
873 | return points;
874 | }
875 | }
876 |
--------------------------------------------------------------------------------