├── .gitignore
├── README.md
├── env.d.ts
├── index.html
├── package.json
├── public
└── favicon.ico
├── src
├── Index.vue
├── js
│ ├── measure
│ │ ├── baseMeasure.js
│ │ ├── measureAzimuth.js
│ │ ├── measureGroundDistance.js
│ │ ├── measureHeight.js
│ │ ├── measureLnglat.js
│ │ ├── measureSection.js
│ │ ├── measureSpaceArea.js
│ │ ├── measureSpaceDistance.js
│ │ ├── measureTool.js
│ │ └── measureTriangle.js
│ ├── prompt
│ │ ├── prompt.css
│ │ └── prompt.js
│ └── util.js
└── main.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .DS_Store
12 | dist
13 | dist-ssr
14 | coverage
15 | *.local
16 |
17 | /cypress/videos/
18 | /cypress/screenshots/
19 |
20 | # Editor directories and files
21 | .vscode/*
22 | !.vscode/extensions.json
23 | .idea
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cesium量算插件
2 | ### [在线体验](http://mapgl.com/shareCode/#/Measure)
3 | gitee:https://gitee.com/caozl1132/CesiumExp-measure
4 | github:https://github.com/gitgitczl/CesiumExp-measure
5 |
6 | ps:如果可以的话,希望大家能给我个star,好让我有更新下去的动力;
7 |
8 | ***
9 | 实现原理:
10 | 其中距离量算的原理是用了Cartesian3提供的distance方法进行了计算;
11 | 面积计算是使用了开源的turf库来进行计算的,此处我只做了空间面积计算,并没有做贴地面积计算,贴地面积计算的原理和空间面积计算一样,只不过贴地时对面做了微分;
12 | 方位角使用了矩阵计算了正北方向的夹角;
13 | 坐标是一个普通的Cartesian3转经纬度;
14 |
15 | ***
16 | 两种调用方法:
17 | 此处调用和我另一个标绘组件的方法类型,一种是使用MeasureTool这个工具类进行统一控制(推荐使用),另一种是直接new 所需的标绘类。
18 | 1、直接通过Tool工具类进行控制:
19 | ```
20 | measureTool = new MeasureTool(viewer);
21 | // 通过on可以实现状态监听
22 | measureTool.on("endCreate",function(measureObj){
23 | // 标绘结束的回调
24 | ......
25 | });
26 | ```
27 |
28 | 2、直接new 具体标绘类型,如下进行贴地距离测量:
29 | ```
30 | let mg = new MeasureGroundDistance(viewer);
31 | mg.starte();
32 | ```
33 | ***
34 | 其它:
35 | qq群:606645466(GIS之家共享交流群)
36 |
37 | [更多案例地址](http://mapgl.com/shareCode/) [更多免费数据](http://mapgl.com/shareData/) [开发文档说明](http://mapgl.com/3dapi/)
38 |
39 | [其它源码下载(标绘、量算、动态材质、漫游、地图分析等)](http://mapgl.com/introduce/)
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
85 |
86 | ${this.opt.content}
87 |
88 |
89 |
90 | ${anchorHtml}
91 |
92 | ${closeHtml}
93 | `;
94 | // 构建弹窗元素
95 | this.promptDiv = window.document.createElement("div");
96 | this.promptDiv.className = `easy3d-prompt ${this.opt.className}`;
97 | this.promptDiv.id = promptId;
98 | this.promptDiv.innerHTML = promptConenet;
99 | let mapDom = window.document.getElementById(mapid);
100 | mapDom.appendChild(this.promptDiv);
101 | const clsBtn = window.document.getElementById(`prompt-close-${this.opt.id}`);
102 | let that = this;
103 | if (clsBtn) {
104 | clsBtn.addEventListener("click", (e) => {
105 | that.hide();
106 | if (that.opt.close) that.opt.close();
107 | })
108 | }
109 |
110 | /**
111 | * @property {Object} promptDom 弹窗div
112 | */
113 | this.promptDom = window.document.getElementById(promptId);
114 |
115 | this.position = this.transPosition(this.opt.position);
116 | // ====================== 创建弹窗内容 end ======================
117 |
118 | if (promptType == 2) this.bindRender(); // 固定位置弹窗 绑定实时渲染 当到地球背面时 隐藏
119 | if (this.opt.show == false) this.hide();
120 | this.containerW = this.viewer.container.offsetWidth;
121 | this.containerH = this.viewer.container.offsetHeight;
122 | this.containerLeft = this.viewer.container.offsetLeft;
123 | this.containerTop = this.viewer.container.offsetTop;
124 |
125 | /**
126 | * @property {Number} contentW 弹窗宽度
127 | */
128 | this.contentW = Math.ceil(Number(this.promptDom.offsetWidth)); // 宽度
129 |
130 | /**
131 | * @property {Number} contentH 弹窗高度
132 | */
133 | this.contentH = this.promptDom.offsetHeight; // 高度
134 |
135 | if (this.opt.success) this.opt.success();
136 | }
137 |
138 | /**
139 | * 销毁
140 | */
141 | destroy() {
142 | if (this.promptDiv) {
143 | window.document.getElementById(this.viewer.container.id).removeChild(this.promptDiv);
144 | this.promptDiv = null;
145 | }
146 | if (this.rendHandler) {
147 | this.rendHandler();
148 | this.rendHandler = null;
149 | }
150 | }
151 | // 实时监听
152 | bindRender() {
153 | let that = this;
154 | this.rendHandler = this.viewer.scene.postRender.addEventListener(function () {
155 | if (!that.isShow && that.promptDom) {
156 | that.promptDom.style.display = "none";
157 | return;
158 | }
159 | if (!that.position) return;
160 | if (that.position instanceof Cesium.Cartesian3) {
161 | let px = Cesium.SceneTransforms.wgs84ToWindowCoordinates(that.viewer.scene, that.position);
162 | if (!px) return;
163 | const occluder = new Cesium.EllipsoidalOccluder(that.viewer.scene.globe.ellipsoid, that.viewer.scene.camera.position);
164 | // 当前点位是否可见 是否在地球背面
165 | const res = occluder.isPointVisible(that.position);
166 | if (res) {
167 | if (that.promptDom) that.promptDom.style.display = "block";
168 | } else {
169 | if (that.promptDom) that.promptDom.style.display = "none";
170 | }
171 | that.setByPX({
172 | x: px.x,
173 | y: px.y
174 | });
175 | } else {
176 | that.setByPX({
177 | x: that.position.x,
178 | y: that.position.y
179 | });
180 | }
181 |
182 | }, this);
183 | }
184 |
185 | /**
186 | *
187 | * @param {Cesium.Cartesian3 | Object} px 弹窗坐标
188 | * @param {String} html 弹窗内容
189 | */
190 | update(px, html) {
191 | if (px instanceof Cesium.Cartesian3) {
192 | this.position = px.clone();
193 | px = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, px);
194 | }
195 | this.contentW = Math.ceil(Number(this.promptDom.offsetWidth)); // 宽度
196 | this.contentH = this.promptDom.offsetHeight; // 高度
197 | if (px) this.setByPX(px);
198 | if (html) this.setContent(html);
199 | }
200 |
201 | // 判断是否在当前视野内
202 | isInView() {
203 | if (!this.position) return false;
204 | let px = null;
205 | if (this.position instanceof Cesium.Cartesian2) {
206 | px = this.position;
207 | } else {
208 | px = Cesium.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, this.position);
209 | }
210 | const occluder = new Cesium.EllipsoidalOccluder(this.viewer.scene.globe.ellipsoid, this.viewer.scene.camera.position);
211 | // 是否在地球背面
212 | const res = occluder.isPointVisible(this.position);
213 | let isin = false;
214 | if (!px) return isin;
215 | if (
216 | px.x > this.containerLeft &&
217 | px.x < (this.containerLeft + this.containerW) &&
218 | px.y > this.containerTop &&
219 | px.y < (this.containerTop + this.containerH)
220 | ) {
221 | isin = true;
222 | }
223 | return res && isin;
224 | }
225 |
226 | /**
227 | * 是否可见
228 | * @param {Boolean} isShow true可见,false不可见
229 | */
230 | setVisible(isShow) {
231 | let isin = this.isInView(this.position);
232 | if (isin && isShow) {
233 | this.isShow = true;
234 | if (this.promptDom) this.promptDom.style.display = "block";
235 | } else {
236 | this.isShow = false;
237 | if (this.promptDom) this.promptDom.style.display = "none";
238 | }
239 | }
240 |
241 | /**
242 | * 显示
243 | */
244 | show() {
245 | this.setVisible(true);
246 | }
247 |
248 | /**
249 | * 隐藏
250 | */
251 | hide() {
252 | this.setVisible(false);
253 | }
254 |
255 | /**
256 | * 设置弹窗内容
257 | * @param {String} content 内容
258 | */
259 | setContent(content) {
260 | let pc = window.document.getElementById(`prompt-content-${this.opt.id}`);
261 | pc.innerHTML = content;
262 | }
263 |
264 | /**
265 | * 设置弹窗坐标
266 | * @param {Object} opt 屏幕坐标
267 | */
268 | setByPX(opt) {
269 | if (!opt) return;
270 | if (this.promptDom) {
271 | const contentW = this.promptDom.offsetWidth; // 宽度
272 | const contentH = this.promptDom.offsetHeight; // 高度
273 | if (this.opt.type == 1) {
274 | this.promptDom.style.left = ((Number(opt.x) + Number(this.opt.offset.x || 0))) + "px";
275 | this.promptDom.style.top = ((Number(opt.y) + Number(this.opt.offset.y || 0))) + "px";
276 | } else {
277 | this.promptDom.style.left = ((Number(opt.x) + Number(this.opt.offset.x || 0)) - Number(this.contentW) / 2) + "px";
278 | this.promptDom.style.top = ((Number(opt.y) + Number(this.opt.offset.y || 0)) - Number(this.contentH)) + "px";
279 | }
280 | }
281 | }
282 |
283 | // 坐标转换
284 | transPosition(p) {
285 | let position;
286 | if (Array.isArray(p)) {
287 | const posi = Cesium.Cartesian3.fromDegrees(p[0], p[1], p[2] || 0);
288 | position = posi.clone();
289 | } else if (p instanceof Cesium.Cartesian3) {
290 | position = p.clone();
291 | } else { // 像素类型
292 | position = p;
293 | }
294 | return position;
295 | }
296 | }
297 |
298 | export default Prompt;
--------------------------------------------------------------------------------
/src/js/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 三维基础方法
3 | * @example util.getCameraView(viewer);
4 | * @exports util
5 | * @alias util
6 | */
7 | let util = {};
8 | /**
9 | * 世界坐标转经纬度
10 | * @param {Cesium.Cartesian3 } cartesian 世界坐标
11 | * @param {Cesium.Viewer} viewer 当前viewer对象
12 | * @returns { Array } 经纬度坐标s
13 | */
14 | util.cartesianToLnglat = function (cartesian, viewer) {
15 | if (!cartesian) return [];
16 | viewer = viewer || window.viewer;
17 | var lnglat = Cesium.Cartographic.fromCartesian(cartesian);
18 | var lat = Cesium.Math.toDegrees(lnglat.latitude);
19 | var lng = Cesium.Math.toDegrees(lnglat.longitude);
20 | var hei = lnglat.height;
21 | return [lng, lat, hei];
22 | }
23 |
24 | util.getViewCenter = (viewer) => {
25 | if (!viewer) return;
26 | var rectangle = viewer.camera.computeViewRectangle();
27 | var west = rectangle.west / Math.PI * 180;
28 | var north = rectangle.north / Math.PI * 180;
29 | var east = rectangle.east / Math.PI * 180;
30 | var south = rectangle.south / Math.PI * 180;
31 | return [(east + west) / 2, (north + south) / 2]
32 | }
33 |
34 | /**
35 | * 世界坐标数组转经纬度数组
36 | * @param {Cesium.Cartesian3[]} cartesians 世界坐标数组
37 | * @param {Cesium.Viewer} viewer 当前viewer对象
38 | * @returns { Array } 经纬度坐标数组
39 | */
40 | util.cartesiansToLnglats = function (cartesians, viewer) {
41 | if (!cartesians || cartesians.length < 1) return;
42 | viewer = viewer || window.viewer;
43 | if (!viewer) {
44 | console.log('util.cartesiansToLnglats方法缺少viewer对象');
45 | return;
46 | }
47 | var arr = [];
48 | for (var i = 0; i < cartesians.length; i++) {
49 | arr.push(util.cartesianToLnglat(cartesians[i], viewer));
50 | }
51 | return arr;
52 | }
53 |
54 | /**
55 | * 经纬度坐标数组转世界坐标数组
56 | * @param {Array[]} lnglats 经纬度坐标数组
57 | * @returns {Cesium.Cartesian3[]} cartesians 世界坐标数组
58 | * @example util.lnglatsToCartesians([[117,40],[118.41]])
59 | */
60 | util.lnglatsToCartesians = function (lnglats) {
61 | if (!lnglats || lnglats.length < 1) return;
62 | var arr = [];
63 | for (var i = 0; i < lnglats.length; i++) {
64 | var c3 = Cesium.Cartesian3.fromDegrees(lnglats[i][0], lnglats[i][1], lnglats[i][2] || 0);
65 | arr.push(c3);
66 | }
67 | return arr;
68 | }
69 |
70 | /**
71 | * 视角定位方法
72 | * @param {Object} opt 定位参数
73 | * @param {Cartesian3|Array} opt.center 当前定位中心点
74 | * @param {Number} opt.heading 当前定位偏转角度 默认为0
75 | * @param {Number} opt.pitch 当前定位仰俯角 默认为-60
76 | * @param {Number} opt.range 当前定位距离 默认为1000米
77 | * @param {Cesium.Viewer} viewer 当前viewer对象
78 | */
79 | util.flyTo = function (opt, viewer) {
80 | if (!viewer) {
81 | console.log('util.flyTo缺少viewer对象');
82 | return;
83 | }
84 | opt = opt || {};
85 | let center = opt.center;
86 | if (!center) {
87 | console.log("缺少定位坐标!");
88 | return;
89 | }
90 | if (center instanceof Cesium.Cartesian3) {
91 | viewer.camera.flyToBoundingSphere(new Cesium.BoundingSphere(center), {
92 | offset: new Cesium.HeadingPitchRange(
93 | Cesium.Math.toRadians(opt.heading || 0),
94 | Cesium.Math.toRadians(opt.pitch || -60),
95 | opt.range || 10000
96 | )
97 | });
98 | }
99 | if (center instanceof Array) {
100 | var boundingSphere = new Cesium.BoundingSphere(Cesium.Cartesian3.fromDegrees(center[0], center[1], center[2]));
101 | viewer.camera.flyToBoundingSphere(boundingSphere, {
102 | offset: new Cesium.HeadingPitchRange(
103 | Cesium.Math.toRadians(opt.heading || 0),
104 | Cesium.Math.toRadians(opt.pitch || -60),
105 | opt.range || 10000
106 | )
107 | });
108 | }
109 | }
110 |
111 | /**
112 | * 获取当相机姿态
113 | * @param {Cesium.Viewer} viewer 当前viewer对象
114 | * @returns {Object} cameraView 当前相机姿态
115 | */
116 | util.getCameraView = function (viewer) {
117 | viewer = viewer || window.viewer;
118 | if (!viewer) {
119 | console.log('util.getCameraView缺少viewer对象');
120 | return;
121 | }
122 | var camera = viewer.camera;
123 | var position = camera.position;
124 | var heading = camera.heading;
125 | var pitch = camera.pitch;
126 | var roll = camera.roll;
127 | var lnglat = Cesium.Cartographic.fromCartesian(position);
128 |
129 | var cameraV = {
130 | "x": Cesium.Math.toDegrees(lnglat.longitude),
131 | "y": Cesium.Math.toDegrees(lnglat.latitude),
132 | "z": lnglat.height,
133 | "heading": Cesium.Math.toDegrees(heading),
134 | "pitch": Cesium.Math.toDegrees(pitch),
135 | "roll": Cesium.Math.toDegrees(roll)
136 | };
137 | return cameraV;
138 | }
139 |
140 | /**
141 | * 设置相机姿态 一般和getCameraView搭配使用
142 | * @param {Object} cameraView 相机姿态参数
143 | * @param {Number} cameraView.duration 定位所需时间
144 | * @param {Cesium.Viewer} viewer 当前viewer对象
145 | */
146 | util.setCameraView = function (obj, viewer) {
147 | viewer = viewer || window.viewer;
148 | if (!viewer) {
149 | console.log('util.setCameraView缺少viewer对象');
150 | return;
151 | }
152 | if (!obj) return;
153 | var position = obj.destination || Cesium.Cartesian3.fromDegrees(obj.x, obj.y, obj.z); // 兼容cartesian3和xyz
154 | viewer.camera.flyTo({
155 | destination: position,
156 | orientation: {
157 | heading: Cesium.Math.toRadians(obj.heading || 0),
158 | pitch: Cesium.Math.toRadians(obj.pitch || 0),
159 | roll: Cesium.Math.toRadians(obj.roll || 0)
160 | },
161 | duration: obj.duration === undefined ? 3 : obj.duration,
162 | complete: obj.complete
163 | });
164 | }
165 |
166 | /**
167 | * 计算当前三角形面积
168 | * @param {Cesium.Cartesian3 } pos1 当前点坐标1
169 | * @param {Cesium.Cartesian3 } pos2 当前点坐标2
170 | * @param {Cesium.Cartesian3 } pos3 当前点坐标3
171 | * @returns {Number} area,面积
172 | */
173 | util.computeAreaOfTriangle = function (pos1, pos2, pos3) {
174 | if (!pos1 || !pos2 || !pos3) {
175 | console.log("传入坐标有误!");
176 | return 0;
177 | }
178 | var a = Cesium.Cartesian3.distance(pos1, pos2);
179 | var b = Cesium.Cartesian3.distance(pos2, pos3);
180 | var c = Cesium.Cartesian3.distance(pos3, pos1);
181 | var S = (a + b + c) / 2;
182 | return Math.sqrt(S * (S - a) * (S - b) * (S - c));
183 | }
184 |
185 | export default util;
186 |
187 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './Index.vue'
3 | import Antd from "ant-design-vue";
4 | import "ant-design-vue/dist/antd.css";
5 | const app = createApp(App)
6 | app.use(Antd);
7 | app.mount('#app')
8 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node18/tsconfig.json",
3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
4 | "compilerOptions": {
5 | "composite": true,
6 | "module": "ESNext",
7 | "types": ["node"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [vue()],
9 | resolve: {
10 | alias: {
11 | '@': fileURLToPath(new URL('./src', import.meta.url))
12 | }
13 | }
14 | })
15 |
--------------------------------------------------------------------------------