bri-components 1.6.18 → 1.6.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bri-components",
3
- "version": "1.6.18",
3
+ "version": "1.6.19",
4
4
  "author": "dengshanghui",
5
5
  "description": "a component lib for vue project",
6
6
  "main": "src/index.js",
@@ -30,11 +30,16 @@
30
30
  "lib:prod": "webpack --config build/webpack.lib.prod.config.js"
31
31
  },
32
32
  "dependencies": {
33
+ "@turf/turf": "^7.4.0",
33
34
  "ali-oss": "^6.13.1",
34
35
  "axios": "^0.23.0",
35
- "bri-datas": "^1.5.4",
36
+ "bri-datas": "^1.5.22",
36
37
  "jshint": "^2.12.0",
37
38
  "jsonlint": "^1.6.3",
39
+ "leaflet": "^1.9.4",
40
+ "mars2d": "^3.4.2",
41
+ "mars3d": "^3.11.5",
42
+ "mars3d-cesium": "^1.143.0",
38
43
  "minio": "7.1.0",
39
44
  "stream": "^0.0.2",
40
45
  "video.js": "^8.23.3",
@@ -1,44 +1,18 @@
1
1
  <template>
2
2
  <!-- 单选模式 编辑 -->
3
- <div
4
- v-if="!multipleMode && canEdit && propsObj._enterType !== 'calculate'"
5
- class="DshCoordinates"
6
- >
7
- <bri-tooltip
8
- :content="showVal"
9
- :transfer="true"
10
- >
3
+ <div v-if="!multipleMode && canEdit && propsObj._enterType !== 'calculate'" class="DshCoordinates">
4
+ <bri-tooltip :content="showVal" :transfer="true">
11
5
  <!-- 编辑 -->
12
- <dsh-control-input
13
- :class="commonClass"
14
- :value="curVal"
15
- :disabled="disabled"
16
- :propsObj="selfPropsObj"
17
- @clear="clickClear"
18
- @click.native="clickInput"
19
- ></dsh-control-input>
6
+ <dsh-control-input :class="commonClass" :value="curVal" :disabled="disabled" :propsObj="selfPropsObj"
7
+ @clear="clickClear" @click.native="clickInput"></dsh-control-input>
20
8
  </bri-tooltip>
21
9
 
22
10
  <!-- 模态框 -->
23
- <Modal
24
- class="DshCoordinates-modal"
25
- v-model="showModal"
26
- :styles="modalStyles"
27
- :footer-hide="true"
28
- >
11
+ <Modal class="DshCoordinates-modal" v-model="showModal" :styles="modalStyles" :footer-hide="true">
29
12
  <!-- 顶部 -->
30
- <div
31
- slot="header"
32
- class="DshCoordinates-modal-header"
33
- >
13
+ <div slot="header" class="DshCoordinates-modal-header">
34
14
  <!-- 可编辑 -->
35
- <Row
36
- v-if="!disabled"
37
- class="header-edit"
38
- type="flex"
39
- justify="center"
40
- align="bottom"
41
- >
15
+ <Row v-if="!disabled" class="header-edit" type="flex" justify="center" align="bottom">
42
16
  <i-col span="8">
43
17
  <radio-group v-model="formData.kind">
44
18
  <Radio label="keyword">按关键字搜索</Radio>
@@ -47,20 +21,13 @@
47
21
 
48
22
  <Row class="header-edit-row">
49
23
  <i-col span="16">
50
- <Input
51
- v-model="formData.searchText"
52
- placeholder="请输入..."
53
- :clearable="true"
54
- @on-enter="clickSearch"
55
- />
24
+ <Input v-model="formData.searchText" placeholder="请输入..." :clearable="true"
25
+ @on-enter="clickSearch" />
56
26
  <span class="header-edit-message">{{ searchMessage }}</span>
57
27
  </i-col>
58
28
 
59
29
  <i-col span="8">
60
- <Button
61
- type="primary"
62
- @click="clickSearch"
63
- >搜索</Button>
30
+ <Button type="primary" @click="clickSearch">搜索</Button>
64
31
  </i-col>
65
32
  </Row>
66
33
  </i-col>
@@ -69,43 +36,25 @@
69
36
  <div class="header-edit-text">坐标获取结果</div>
70
37
  <Row class="header-edit-row">
71
38
  <i-col span="16">
72
- <Input
73
- :value="newVal.name"
74
- :readonly="true"
75
- :clearable="true"
76
- @on-clear="clickModalClear"
77
- @click.native="clickMapFitView"
78
- />
39
+ <Input :value="newVal.name" :readonly="true" :clearable="true"
40
+ @on-clear="clickModalClear" @click.native="clickMapFitView" />
79
41
  </i-col>
80
42
 
81
43
  <i-col span="8">
82
- <Button
83
- type="primary"
84
- @click="clickConfirm"
85
- >确认</Button>
44
+ <Button type="primary" @click="clickConfirm">确认</Button>
86
45
  </i-col>
87
46
  </Row>
88
47
  </i-col>
89
48
  </Row>
90
49
 
91
50
  <!-- 不可编辑 -->
92
- <Row
93
- v-else
94
- class="header-show"
95
- >
96
- <i-col
97
- span="6"
98
- class="header-show-text"
99
- >
51
+ <Row v-else class="header-show">
52
+ <i-col span="6" class="header-show-text">
100
53
  当前位置:
101
54
  </i-col>
102
55
 
103
56
  <i-col span="12">
104
- <Input
105
- :value="newVal.name"
106
- :readonly="true"
107
- @click.native="clickMapFitView"
108
- />
57
+ <Input :value="newVal.name" :readonly="true" @click.native="clickMapFitView" />
109
58
  </i-col>
110
59
 
111
60
  <i-col span="6"></i-col>
@@ -114,45 +63,28 @@
114
63
 
115
64
  <!-- 地图 -->
116
65
  <transition>
117
- <div
118
- v-if="showModal"
119
- id="DshCoordinatesMap"
120
- class="DshCoordinates-modal-map"
121
- ></div>
66
+ <div v-if="showModal" id="DshCoordinatesMap" class="DshCoordinates-modal-map"></div>
122
67
  </transition>
123
68
 
124
69
  <!-- 实时坐标 -->
125
- <div
126
- ref="lnglatRef"
127
- class="DshCoordinates-modal-lnglat"
128
- ></div>
70
+ <div ref="lnglatRef" class="DshCoordinates-modal-lnglat"></div>
129
71
  </Modal>
130
72
  </div>
131
73
 
132
74
  <!-- 单选模式 查看 -->
133
- <div
134
- v-else-if="!multipleMode && !canEdit"
135
- class="DshCoordinates-show"
136
- >
75
+ <div v-else-if="!multipleMode && !canEdit" class="DshCoordinates-show">
137
76
  <!-- 有值 -->
138
77
  <template v-if="!$isEmptyData(curVal)">
139
78
  <!-- 高度自由时 -->
140
- <div
141
- v-if="isHeightAuto"
142
- :class="{
143
- ...commonClass,
144
- 'DshCoordinates-show-auto': true
145
- }"
146
- >
79
+ <div v-if="isHeightAuto" :class="{
80
+ ...commonClass,
81
+ 'DshCoordinates-show-auto': true
82
+ }">
147
83
  {{ showVal }}
148
84
  </div>
149
85
 
150
86
  <!-- 高度不自由(不换行) -->
151
- <bri-tooltip
152
- v-else
153
- :content="showVal"
154
- :transfer="true"
155
- >
87
+ <bri-tooltip v-else :content="showVal" :transfer="true">
156
88
  <div :class="{
157
89
  ...commonClass,
158
90
  'DshCoordinates-show-ellipsis': true
@@ -163,38 +95,57 @@
163
95
  </template>
164
96
 
165
97
  <!-- 无值 -->
166
- <div
167
- v-else
168
- :class="commonClass"
169
- >
98
+ <div v-else :class="commonClass">
170
99
  {{ emptyShowVal }}
171
100
  </div>
172
101
  </div>
173
102
 
174
- <div
175
- v-else-if="propsObj._enterType === 'calculate'"
176
- class="DshCoordinates-show"
177
- >
178
- <span :class="{'nodata': !showVal}">{{ showVal || '暂无内容' }}</span>
103
+ <div v-else-if="propsObj._enterType === 'calculate'" class="DshCoordinates-show">
104
+ <span :class="{ 'nodata': !showVal }">{{ showVal || '暂无内容' }}</span>
179
105
  </div>
180
106
  </template>
181
107
 
182
108
  <script>
109
+ import axios from "axios";
183
110
  import controlMixin from "../mixins/controlMixin.js";
184
-
185
- const plotStrokeColor = "#4575EF";
186
- const plotFillColor = "#4575EF";
187
- let polygonOpt = {
188
- fillColor: plotFillColor,
111
+ import "leaflet/dist/leaflet.css";
112
+ import "mars2d/mars2d.css";
113
+ import * as mars2d from "mars2d";
114
+ import "mars3d/mars3d.css";
115
+ import * as mars3d from "mars3d";
116
+
117
+ const plotColor = "#4575EF";
118
+ const polygonStyle = {
119
+ fillColor: plotColor,
189
120
  fillOpacity: 0.4,
190
- strokeColor: plotStrokeColor,
191
- strokeWeight: 5
121
+ outlineColor: plotColor,
122
+ outlineWidth: 5
123
+ };
124
+ const polygonStyle3d = {
125
+ color: plotColor,
126
+ opacity: 0.4,
127
+ outline: true,
128
+ outlineColor: plotColor,
129
+ outlineWidth: 5
130
+ };
131
+ const polylineStyle = {
132
+ color: plotColor,
133
+ width: 5
192
134
  };
193
- let lineOpt = {
194
- strokeColor: plotStrokeColor,
195
- strokeWeight: 5
135
+ const markerStyle = {
136
+ image: require("./mark_bs.png"),
137
+ width: 19,
138
+ height: 32,
139
+ horizontalOrigin: mars2d.HorizontalOrigin.CENTER,
140
+ verticalOrigin: mars2d.VerticalOrigin.BOTTOM
141
+ };
142
+ const markerStyle3d = {
143
+ image: require("./mark_bs.png"),
144
+ width: 19,
145
+ height: 32,
146
+ horizontalOrigin: mars3d.Cesium.HorizontalOrigin.CENTER,
147
+ verticalOrigin: mars3d.Cesium.VerticalOrigin.BOTTOM
196
148
  };
197
- let markerOpt = {};
198
149
 
199
150
  export default {
200
151
  name: "DshCoordinates",
@@ -220,10 +171,11 @@
220
171
 
221
172
  // 地图相应控件
222
173
  map: null,
223
- placeSearch: null,
224
- mouseTool: null,
174
+ mapResizeObserver: null, // 3D地图:监听容器尺寸变化,同步 Cesium canvas 尺寸
175
+ queryPOI: null, // POI查询/逆地理编码工具
225
176
  marker: null, // 标记点
226
177
  plot: null, // 标记图形
178
+ suppressDrawCreated: false, // 主动调用 stopDraw 期间,屏蔽 mars2d 内部可能重放的 drawCreated 事件
227
179
  searchMessage: "" // 提示信息
228
180
  };
229
181
  },
@@ -244,6 +196,12 @@
244
196
  showMode () {
245
197
  return this.selfPropsObj._showType;
246
198
  },
199
+ is3d () {
200
+ return this.subType === "3D";
201
+ },
202
+ mapEngine () {
203
+ return this.is3d ? mars3d : mars2d;
204
+ },
247
205
 
248
206
  curVal: {
249
207
  get () {
@@ -276,7 +234,9 @@
276
234
  // 点击确认
277
235
  clickConfirm () {
278
236
  this.showModal = false;
279
- this.curVal = this.newVal;
237
+ // 标记坐标系:getMap 里 mars2d.Map/mars3d.Map 均未显式配置 chinaCRS,默认按WGS84运作,
238
+ // 高德瓦片只是被自动纠偏对齐到这个WGS84坐标网格上显示,这里选点产出的坐标实际是WGS84
239
+ this.curVal = { ...this.newVal, crs: "WGS84" };
280
240
  },
281
241
  // 清除已选内容
282
242
  clickModalClear () {
@@ -284,7 +244,7 @@
284
244
  name: "",
285
245
  lnglat: []
286
246
  }; // 清除当前标记
287
- this.handleRmoveMarker("default"); // 清除地图内标记
247
+ this.handleRemoveMarker(); // 清除地图内标记
288
248
  this.handleOpenDraw(); // 打开绘图功能
289
249
  },
290
250
  // 点击搜索
@@ -300,288 +260,510 @@
300
260
  if (isNaN(lng) || isNaN(lat) || lng < -180 || lng > 180 || lat < -90 || lat > 90) {
301
261
  this.searchMessage = "请输入正确的经纬度坐标!";
302
262
  } else {
303
- lnglat = new AMap.LngLat(lng, lat);
304
263
  this.searchMessage = "";
305
- this.handleAddMarker([lnglat.lng, lnglat.lat], true, "default");
264
+ this.handleAddMarker(lnglat);
306
265
  }
307
266
  } else {
308
- this.placeSearch.search(this.formData.searchText, this.handlePlaceSearchCallback);
267
+ this.queryPOI.queryText({ text: this.formData.searchText }).then(result => {
268
+ if (result && result.list && result.list.length) {
269
+ this.searchMessage = "";
270
+ let poi = result.list[0];
271
+ this.handleAddMarker([poi.lng, poi.lat], true, poi.name);
272
+ } else {
273
+ this.searchMessage = "没有搜索到结果!";
274
+ }
275
+ }).catch((err) => {
276
+ console.log(err);
277
+ this.searchMessage = "搜索失败,请稍后重试!";
278
+ });
309
279
  }
310
280
  },
311
281
  // 地图自适应点
312
- clickMapFitView (view = this.plot) {
313
- this.map.setFitView(view);
282
+ clickMapFitView () {
283
+ if (!this.map.graphicLayer.getGraphics().length) {
284
+ return;
285
+ }
286
+ // 绘制中还没落点的预览图形也算在 graphicLayer 里,所以这个按钮点到时绘制会话往往还是打开的;
287
+ // flyTo 平移地图期间如果绘制会话还在监听鼠标位置,结束时可能把和当前鼠标位置无关的过期预览
288
+ // 当成一次真实落点重放出来,多绘制出一个没点过的点位。这里借用 graphicLayer 上已有的
289
+ // isDrawing 状态,flyTo 前先用验证过安全的 handleCloseDraw 暂停绘制,动画结束后再按原状态恢复
290
+ let wasDrawing = this.map.graphicLayer.isDrawing;
291
+ if (wasDrawing) {
292
+ this.handleCloseDraw();
293
+ }
294
+ // rectangle 需要落两个角点才算绘制完成,若只落了一个角就点了这个按钮,handleCloseDraw
295
+ // 会把这个还没被标记为"已落点"的预览图形当无效图形清掉(见 handleDrawCreated 里的说明),
296
+ // 图层因此变空。对空图层调用 graphicLayer.flyTo() 找不到定位目标,会飞向一个固定的
297
+ // 兜底坐标,表现为视角划过地球背面——这里直接跳过定位
298
+ if (!this.map.graphicLayer.getGraphics().length) {
299
+ if (wasDrawing) {
300
+ this.$nextTick(() => {
301
+ !this.disabled && this.handleOpenDraw();
302
+ });
303
+ }
304
+ return;
305
+ }
306
+ if (this.is3d && this.showMode === "rectangle" && this.newVal.lnglat.length > 1) {
307
+ let lngs = this.newVal.lnglat.map(position => position[0]);
308
+ let lats = this.newVal.lnglat.map(position => position[1]);
309
+ this.map.flyToExtent({
310
+ xmin: Math.min(...lngs),
311
+ xmax: Math.max(...lngs),
312
+ ymin: Math.min(...lats),
313
+ ymax: Math.max(...lats)
314
+ });
315
+ } else {
316
+ this.map.graphicLayer.flyTo();
317
+ }
318
+ if (wasDrawing) {
319
+ this.$nextTick(() => {
320
+ !this.disabled && this.handleOpenDraw();
321
+ });
322
+ }
314
323
  },
315
324
  getMap () {
316
325
  this.$nextTick(() => {
317
- let lnglatRef = this.$refs.lnglatRef;
318
- this.map = new AMap.Map("DshCoordinatesMap", {
319
- rotateEnable: true,
320
- pitchEnable: true,
321
- zoom: this.propsObj._zoom || 12,
322
- pitch: 50,
323
- viewMode: this.subType,
324
- center: this.propsObj._center && this.propsObj._center.split(",").map(i => Number(i)) || undefined
326
+ // modal 每次打开都会重新走这里创建地图,mars2d(Leaflet)实例很轻,销毁重建没有代价;
327
+ // mars3d(Cesium)实例持有 WebGL 上下文,浏览器对同时存活的上下文数量有上限(约16个),
328
+ // 不主动销毁旧实例会导致反复打开关闭该弹窗后 3D 地图渲染失效
329
+ if (this.map) {
330
+ this.map.destroy && this.map.destroy();
331
+ this.map = null;
332
+ }
333
+ if (this.mapResizeObserver) {
334
+ this.mapResizeObserver.disconnect();
335
+ this.mapResizeObserver = null;
336
+ }
337
+
338
+ // Modal 内部弹窗主体用的是 v-show(配合过渡动画),不是 v-if:DshCoordinates 一挂载,
339
+ // .ivu-modal-header/.ivu-modal-body 就已经创建好了,只是 display:none 隐藏着;只有第一次
340
+ // 打开时,它们才第一次真正从隐藏切到可见,浏览器要为这一整块内容做首次布局+绘制+合成,
341
+ // 而地图容器是我们自己用 v-if 现建的,恰好在同一帧一起创建、开始绘制瓦片,容易撞上这个
342
+ // "外层容器刚显示、绘制还没稳定"的时机,表现为地图瓦片的合成图层盖住了 header(header 自身
343
+ // 布局和交互都正常,只是绘制没跟上)。关闭重开时 .ivu-modal-content 并未被销毁,浏览器早已
344
+ // 对它做过一次布局绘制,只是简单切换 display,不会再撞上这个时机。这里用两次
345
+ // requestAnimationFrame,等浏览器至少完成一次真实的显示帧之后再创建地图,避开这个绘制竞态
346
+ requestAnimationFrame(() => {
347
+ requestAnimationFrame(() => {
348
+ if (!this.showModal) {
349
+ return;
350
+ }
351
+ this.createMap();
352
+ });
353
+ });
354
+ });
355
+ },
356
+ // 实际创建地图实例(详见 getMap 里等待首帧绘制稳定的说明)
357
+ createMap () {
358
+ let lnglatRef = this.$refs.lnglatRef;
359
+ let center = this.propsObj._center && this.propsObj._center.split(",").map(i => Number(i)) || [];
360
+ let lng = center[0] || 116.396852;
361
+ let lat = center[1] || 39.914748;
362
+
363
+ if (this.is3d) {
364
+ this.map = new mars3d.Map("DshCoordinatesMap", {
365
+ scene: {
366
+ // mars3d 没有 zoom 层级概念,用相机高度(alt)表现,这里按 Web墨卡托瓦片金字塔的
367
+ // 层级-分辨率换算关系,把 2D 的 zoom 近似转换成一个可用的相机高度
368
+ center: {
369
+ lng,
370
+ lat,
371
+ alt: 591657550.5 / Math.pow(2, (this.propsObj._zoom || 12) - 1)
372
+ }
373
+ },
374
+ basemaps: [{ name: "高德地图", type: "gaode", layer: "vec", show: true }],
375
+ control: {
376
+ compass: { style: { top: "10px", left: "5px" } }
377
+ }
325
378
  });
326
379
 
327
- // 滑动显示坐标点
328
- this.map.on("mouseover", function (e) {
380
+ // 滑动显示实时坐标:不用 mars3d 的 mouseMove 事件(负载字段未有文档确认),
381
+ // 也不用 Cesium 原生 screenSpaceEventHandler(会覆盖 mars3d 内部绘制预览用的鼠标监听),
382
+ // 改用原生 DOM 事件 + 标准 Cesium 拾取 API 换算经纬度,不影响绘制交互
383
+ this.map.scene.canvas.addEventListener("mousemove", e => {
384
+ let windowPosition = new mars3d.Cesium.Cartesian2(e.offsetX, e.offsetY);
385
+ let cartesian = this.map.scene.pickPosition(windowPosition) ||
386
+ this.map.scene.camera.pickEllipsoid(windowPosition, this.map.scene.globe.ellipsoid);
387
+ if (!cartesian) {
388
+ lnglatRef.style.display = "none";
389
+ return;
390
+ }
391
+ let [mLng, mLat] = mars3d.PointTrans.cartesian2lonlat(cartesian, true);
329
392
  lnglatRef.style.display = "block";
393
+ lnglatRef.innerHTML = mLng.toFixed(6) + ", " + mLat.toFixed(6);
394
+ lnglatRef.style.top = (e.offsetY + 20) + "px";
395
+ lnglatRef.style.left = (e.offsetX + 6) + "px";
396
+ });
397
+ this.map.scene.canvas.addEventListener("mouseout", () => {
398
+ lnglatRef.style.display = "none";
399
+ });
400
+
401
+ // Cesium 只在创建时按容器当时的尺寸初始化 canvas,之后仅监听 window resize 事件自动重算;
402
+ // 弹窗展开动画期间容器尺寸变化不会触发 window resize,导致 canvas 一直停留在创建时的
403
+ // 初始尺寸(未布局完成时退化为浏览器 canvas 默认的 300x150)。用 ResizeObserver 监听
404
+ // 容器本身的尺寸变化,让 canvas 尺寸始终跟随 #DshCoordinatesMap 容器
405
+ this.mapResizeObserver = new ResizeObserver(() => {
406
+ if (!this.map) {
407
+ return;
408
+ }
409
+ this.map.viewer.resize();
410
+ this.map.scene.requestRender();
411
+ });
412
+ this.mapResizeObserver.observe(this.map.container);
413
+ } else {
414
+ this.map = new mars2d.Map("DshCoordinatesMap", {
415
+ zoom: this.propsObj._zoom || 12, // 地图层级
416
+ center: { lng, lat },
417
+ basemaps: [{ name: "高德地图", type: "gaode", layer: "vec", show: true }]
330
418
  });
419
+
420
+ // 滑动显示实时坐标
331
421
  this.map.on("mousemove", function (e) {
332
- lnglatRef.innerHTML = e.lnglat.toString();
333
- lnglatRef.style.top = (e.pixel.y + 120) + "px";
334
- lnglatRef.style.left = (e.pixel.x + 6) + "px";
422
+ lnglatRef.style.display = "block";
423
+ lnglatRef.innerHTML = e.latlng.lng.toFixed(6) + ", " + e.latlng.lat.toFixed(6);
424
+ lnglatRef.style.top = (e.containerPoint.y + 20) + "px";
425
+ lnglatRef.style.left = (e.containerPoint.x + 6) + "px";
335
426
  });
336
427
  this.map.on("mouseout", function (e) {
337
428
  lnglatRef.style.display = "none";
338
429
  });
339
430
 
340
- // 集成插件
341
- AMap.plugin(["AMap.PlaceSearch", "AMap.Scale", "AMap.ControlBar", "AMap.MouseTool"], this.handlePluginCallback);
431
+ // Leaflet 同样只在创建时按容器当时的尺寸初始化地图,之后不会自动感知容器尺寸变化;
432
+ // 弹窗打开的过渡/transfer body 等过程可能让 header 实际高度晚于地图初始化才稳定,
433
+ // 地图会按当时(header 尚未占位)的容器尺寸铺满,覆盖到 header 区域。用 ResizeObserver
434
+ // 监听容器尺寸变化,调用 invalidateSize 让地图尺寸始终跟随 #DshCoordinatesMap 容器
435
+ this.mapResizeObserver = new ResizeObserver(() => {
436
+ if (!this.map) {
437
+ return;
438
+ }
439
+ this.map.invalidateSize();
440
+ });
441
+ this.mapResizeObserver.observe(this.map.container);
442
+ }
342
443
 
444
+ // 构造POI查询/逆地理编码工具
445
+ this.queryPOI = new this.mapEngine.query.QueryPOI({
446
+ service: "tdt",
447
+ key: "0050eb1a5c837128f694a9022055fa54"
343
448
  });
344
- },
345
- handlePluginCallback () {
346
- // 构造地点查询类
347
- this.placeSearch = new AMap.PlaceSearch();
348
- this.placeSearch.on("complete", this.handlePlaceSearchCallback);
349
- this.placeSearch.on("error", this.handlePlaceSearchCallback);
350
-
351
- // 加载集成工具
352
- this.map.addControl(new AMap.Scale());
353
- this.map.addControl(new AMap.ControlBar());
354
- // this.map.addControl(new AMap.ToolBar());
355
-
356
- // 监听draw事件可获取画好的覆盖物
357
- this.mouseTool = new AMap.MouseTool(this.map);
358
- this.mouseTool.on("draw", this.handleMouseToolCallback);
359
- // 设置默认坐标
360
- if (!this.$isEmptyData(this.newVal)) {
361
- this.handleAddMarker(this.newVal.lnglat, true);
362
449
 
363
- if (this.showMode === "multipleMarker") { // 多选打开绘图功能
364
- !this.disabled && this.handleOpenDraw();
365
- }
450
+ // 设置默认坐标 / 打开绘图功能
451
+ if (!this.$isEmptyData(this.newVal)) {
452
+ this.handleRestoreValue();
366
453
  } else {
367
454
  !this.disabled && this.handleOpenDraw();
368
455
  }
369
456
  },
370
- handleMouseToolCallback (e) {
371
- if (e.obj.CLASS_NAME === "AMap.Marker") {
372
- if (this.showMode === "marker") {
373
- this.plot = e.obj;
374
- this.handleCloseDraw();
375
- } else if (this.showMode === "multipleMarker") {
376
- if (this.plot) {
377
- this.plot.push(e.obj);
378
- } else {
379
- this.plot = [e.obj];
380
- }
381
- }
382
- // 设置坐标
383
- let lnglat = e.obj.getPosition();
384
- this.newVal.lnglat.push([lnglat.lng, lnglat.lat]);
385
- this.getRegeoCode();
386
-
457
+ // 还原已保存的坐标/图形数据
458
+ handleRestoreValue () {
459
+ // 老数据未标记crs时按GCJ02处理并转换:地图本身按WGS84运作(见clickConfirm里的说明),
460
+ // 不转换会导致高德选点产生的老数据回显时出现同城市内小范围、方向固定的偏移
461
+ let lnglat = this.newVal.lnglat.map(position => this.toWgs84(position, this.newVal.crs));
462
+ if (this.showMode === "multipleMarker") {
463
+ this.plot = lnglat.map(position => {
464
+ let marker = this.createGraphic("marker", [position]);
465
+ this.map.graphicLayer.addGraphic(marker);
466
+ return marker;
467
+ });
468
+ !this.disabled && this.handleOpenDraw();
387
469
  } else {
388
- this.plot = e.obj;
389
- // 设置坐标
390
- this.newVal.lnglat.push(...e.obj.getPath().map(lnglat => [lnglat.lng, lnglat.lat]));
391
- this.getRegeoCode();
392
- this.handleCloseDraw();
393
- }
394
- },
395
- handlePlaceSearchCallback (result) {
396
- if (result == "complete") {
397
- return;
470
+ // rectangle 只存了对角两个点,不能当多边形顶点还原,要用 Rectangle 按 bounds 还原;
471
+ // circle 也统一以多边形还原展示
472
+ this.plot = this.createGraphic(this.showMode, lnglat);
473
+ this.map.graphicLayer.addGraphic(this.plot);
398
474
  }
399
- if (result.poiList && result.poiList.pois && result.poiList.pois.length) {
400
- this.searchMessage = "";
401
- let poi = result.poiList.pois[0];
402
- this.handleAddMarker([poi.location.lng, poi.location.lat], true, "default");
403
- this.marker.setTitle([poi.name, poi.address].join(poi.name && poi.address ? "\n" : ""));
475
+
476
+ // 3D下 multipleMarker 是若干个独立的billboard点,图标刚创建时还没异步加载完成,
477
+ // Cesium 计算这些entity的包围球会是pending状态,graphicLayer.flyTo()内部据此判定
478
+ // "当前无法定位到目标"而静默不飞行,相机停在地图初始默认视角,看起来就像直接显示了
479
+ // 整个地球。这里已经有还原用的经纬度数组,不依赖graphic是否加载完成,直接算出矩形
480
+ // 范围飞过去
481
+ if (this.is3d && this.showMode === "multipleMarker" && lnglat.length > 1) {
482
+ let lngs = lnglat.map(position => position[0]);
483
+ let lats = lnglat.map(position => position[1]);
484
+ this.map.flyToExtent({
485
+ xmin: Math.min(...lngs),
486
+ xmax: Math.max(...lngs),
487
+ ymin: Math.min(...lats),
488
+ ymax: Math.max(...lats)
489
+ });
404
490
  } else {
405
- this.searchMessage = "没有搜索到结果!";
491
+ this.map.graphicLayer.flyTo();
406
492
  }
407
493
  },
408
- // 打开绘图
409
- handleOpenDraw (type = this.showMode) {
410
- switch (type) {
411
- case "marker": {
412
- this.mouseTool.marker(markerOpt);
413
- break;
494
+ // 根据绘制类型创建对应引擎的矢量图形
495
+ createGraphic (type, lnglat) {
496
+ let style = this.getDrawStyle(type);
497
+ if (this.is3d) {
498
+ if (type === "marker" || type === "multipleMarker") {
499
+ return new mars3d.graphic.BillboardEntity({ position: lnglat[0], style });
500
+ }
501
+ if (type === "polyline") {
502
+ return new mars3d.graphic.PolylineEntity({ positions: lnglat, style });
503
+ }
504
+ if (type === "rectangle") {
505
+ return new mars3d.graphic.RectangleEntity({ positions: lnglat, style });
506
+ }
507
+ // polygon、circle 统一以多边形还原展示
508
+ return new mars3d.graphic.PolygonEntity({ positions: lnglat, style });
414
509
  }
415
- case "multipleMarker": {
416
- this.mouseTool.marker(markerOpt);
417
- break;
510
+ if (type === "marker" || type === "multipleMarker") {
511
+ return new mars2d.graphic.Marker({ latlng: [lnglat[0][1], lnglat[0][0]], style });
418
512
  }
419
- case "polyline": {
420
- this.mouseTool.polyline(lineOpt);
421
- break;
513
+ let latlngs = lnglat.map(item => [item[1], item[0]]);
514
+ if (type === "polyline") {
515
+ return new mars2d.graphic.Polyline({ latlngs, style });
422
516
  }
423
- case "polygon": {
424
- this.mouseTool.polygon(polygonOpt);
425
- break;
517
+ if (type === "rectangle") {
518
+ return new mars2d.graphic.Rectangle({ latlngs, style });
426
519
  }
427
- case "rectangle": {
428
- this.mouseTool.rectangle(polygonOpt);
429
- break;
520
+ return new mars2d.graphic.Polygon({ latlngs, style });
521
+ },
522
+ // 根据绘制类型获取对应的矢量数据样式
523
+ getDrawStyle (type) {
524
+ if (["polygon", "rectangle", "circle"].includes(type)) {
525
+ return this.is3d ? polygonStyle3d : polygonStyle;
430
526
  }
431
- case "circle": {
432
- this.mouseTool.circle(polygonOpt);
433
- break;
527
+ if (type === "polyline") {
528
+ return polylineStyle;
434
529
  }
530
+ return this.is3d ? markerStyle3d : markerStyle;
531
+ },
532
+ // 打开绘图
533
+ handleOpenDraw (type = this.showMode) {
534
+ // startDraw 内部会先 stopDraw 关掉正在进行的绘制会话,如果那个会话已经有了一个
535
+ // 鼠标移入产生的预览图形(哪怕用户实际没有点击过),disable 时会把它当成真实落点重放出来,
536
+ // 这里主动先走一遍 handleCloseDraw 把这种重放屏蔽掉,避免每个调用方都要自己处理
537
+ this.handleCloseDraw();
538
+ let isMarker = type === "marker" || type === "multipleMarker";
539
+ // mars3d 绘制图标点用的类型名是 "billboard",不是 "marker"
540
+ let drawType = isMarker ? (this.is3d ? "billboard" : "marker") : type;
541
+ let style = this.getDrawStyle(type);
542
+ // 绘制点位时,鼠标移动过程中会实时用该样式画一个跟随鼠标的预览图标,
543
+ // 这里不需要这个预览,落点后再在 handleDrawCreated 里恢复成可见样式
544
+ if (isMarker) {
545
+ style = { ...style, opacity: 0 };
435
546
  }
547
+ this.map.graphicLayer.startDraw({
548
+ type: drawType,
549
+ style,
550
+ success: graphic => {
551
+ this.handleDrawCreated(graphic, type);
552
+ }
553
+ });
436
554
  },
437
555
  // 关闭绘图
438
556
  handleCloseDraw () {
439
- this.mouseTool.close();
557
+ this.suppressDrawCreated = true;
558
+ this.map.graphicLayer.stopDraw();
559
+ this.suppressDrawCreated = false;
440
560
  },
441
- // 添加图形
442
- handleAddMarker (location, centerView = true, type = this.showMode) {
443
- location = JSON.parse(JSON.stringify(location));
444
- // 分类型添加
445
- if (["polygon", "rectangle", "circle"].includes(type)) {
446
- // 图形类
447
- this.plot = new AMap.Polygon({
448
- ...polygonOpt,
449
- path: location
450
- });
451
- this.map.add(this.plot);
452
- centerView && this.map.setFitView(this.plot);// 视口自适应
453
- } else if (["default"].includes(type)) {
454
- !this.marker && (this.marker = new AMap.Marker());
455
- this.marker.setPosition(location);
456
- this.marker.setMap(this.map);
457
- if (this.showMode === "marker") {
458
- this.handleRmoveMarker();
459
- this.handleCloseDraw();
460
- this.newVal.lnglat = [location];
461
- this.getRegeoCode();
462
- } else if (this.showMode === "multipleMarker") {
463
- this.newVal.lnglat.push(location);
464
- this.getRegeoCode();
561
+ // 绘制完成回调
562
+ // 注意:stopDraw 触发 disable 时,如果当前绘制实例还留着一个图形(不管是已经真正落点过的,
563
+ // 还是鼠标移动产生的、还没落点的透明预览),都会被当成一次落点重新广播出来。
564
+ // 用 graphic._placed 区分:已经标记过的是之前真实落过的点,重复广播直接忽略;没标记过的
565
+ // 说明只是预览图形(比如鼠标移出地图前最后停留的位置),要从地图上移除,不能留下变成一个
566
+ // 用户没点过的点位(之前只拦截了 newVal 更新,没清理图形本身,导致地图上多出一个点)
567
+ handleDrawCreated (graphic, type = this.showMode) {
568
+ let coordinates = this.getGraphicCoordinates(graphic, type);
569
+ if (this.suppressDrawCreated) {
570
+ if (!graphic._placed) {
571
+ this.map.graphicLayer.removeGraphic(graphic);
465
572
  }
466
- centerView && this.map.setFitView(this.marker);
467
- } else if (["polyline"].includes(type)) {
468
- this.plot = new AMap.Polyline({
469
- ...polygonOpt,
470
- path: location
471
- });
472
- this.map.add(this.plot);
473
- centerView && this.map.setFitView(this.plot);// 视口自适应
474
- } else if (["multipleMarker", "marker"].includes(type)) {
475
- this.plot = location.map(position => {
476
- return new AMap.Marker({
477
- map: this.map,
478
- position: position
479
- });
573
+ return;
574
+ }
575
+ graphic._placed = true;
576
+ if (type === "marker" || type === "multipleMarker") {
577
+ // 落点后恢复成可见样式;setStyle 内部是和之前的样式合并(merge),
578
+ // markerStyle 里没写 opacity,合并后不会覆盖掉绘制时设的 opacity:0,这里要显式传 opacity:1
579
+ graphic.style = { ...this.getDrawStyle(type), opacity: 1 };
580
+ }
581
+ if (type === "multipleMarker") {
582
+ this.plot ? this.plot.push(graphic) : (this.plot = [graphic]);
583
+ this.newVal.lnglat.push(...coordinates);
584
+ this.getRegeoCode(coordinates[0]);
585
+ // 此时仍处于 mars2d 当前这次绘制点击事件的调用栈中,
586
+ // 若在这里同步调用 startDraw 重新开始下一个点的绘制,会递归触发同一个绘制实例的 disable,
587
+ // 导致其内部代码执行完 disable 后访问 this.options 时报错(repeatMode 读取 undefined)
588
+ // 延后到下一个 tick 再开始新一轮绘制,避开这个调用栈
589
+ this.$nextTick(() => {
590
+ !this.disabled && this.handleOpenDraw(type); // 继续绘制下一个点
480
591
  });
481
- centerView && this.map.setFitView(this.plot);
592
+ } else {
593
+ this.plot = graphic;
594
+ this.newVal.lnglat = coordinates;
595
+ this.getRegeoCode(coordinates[0]);
482
596
  }
483
597
  },
484
- // 移除图形
485
- handleRmoveMarker (type = this.showMode) {
486
- if (["default"].includes(type)) {
487
- this.marker && this.map.remove(this.marker);
488
- this.plot && this.map.remove(this.plot);
489
- } else {
490
- this.plot && this.map.remove(this.plot);
598
+ // 获取绘制/回显图形对应的经纬度坐标数组(mars2d 各类图形统一暴露 .coordinates;
599
+ // mars3d 点状图形(billboard)用 .point,线/面图形用 .points,圆用 getOutlineCoordinates 取环上点位近似)
600
+ getGraphicCoordinates (graphic, type) {
601
+ if (!this.is3d) {
602
+ return graphic.coordinates;
603
+ }
604
+ if (type === "marker" || type === "multipleMarker") {
605
+ return [graphic.point.toArray(true)];
491
606
  }
607
+ if (type === "circle") {
608
+ return graphic.getOutlineCoordinates(false, undefined, true);
609
+ }
610
+ return graphic.points.map(point => point.toArray(true));
492
611
  },
493
- // 获取逆地理编码
494
- getRegeoCode (value, scuessFun) {
495
- let params = {
496
- key: window.config.amapKey,
497
- location: this.newVal.lnglat[0],
498
- radius: 1000,
499
- extensions: "base",
500
- batch: false,
501
- roadlevel: 1
502
- };
612
+ // 添加/更新搜索结果标记点
613
+ handleAddMarker (location, centerView = true, title = "") {
614
+ if (this.marker) {
615
+ this.setGraphicPosition(this.marker, location);
616
+ } else {
617
+ this.marker = this.createGraphic("marker", [location]);
618
+ this.map.graphicLayer.addGraphic(this.marker);
619
+ }
503
620
 
504
- let paramStr = "";
505
- for (let attr in params) {
506
- paramStr += attr + "=" + params[attr] + "&";
621
+ if (this.showMode === "marker") {
622
+ // 单点模式下,搜索结果即为最终坐标,清除之前绘制的图形
623
+ this.handleCloseDraw();
624
+ if (this.plot) {
625
+ this.map.graphicLayer.removeGraphic(this.plot);
626
+ this.plot = null;
627
+ }
628
+ this.newVal.lnglat = [[location[0], location[1]]];
629
+ if (title) {
630
+ this.newVal.name = title;
631
+ } else {
632
+ this.getRegeoCode(location);
633
+ }
634
+ } else if (this.showMode === "multipleMarker") {
635
+ this.newVal.lnglat.push([location[0], location[1]]);
636
+ if (title) {
637
+ this.newVal.name = title;
638
+ } else {
639
+ this.getRegeoCode(location);
640
+ }
507
641
  }
508
642
 
509
- fetch("https://restapi.amap.com/v3/geocode/regeo?" + paramStr)
510
- .then(res => res.json())
511
- .then(response => {
512
- if (response.status === "1" && response.regeocode) {
513
- let address = response.regeocode.formatted_address;
514
- this.newVal.name = address;
515
- }
643
+ centerView && this.map.graphicLayer.flyTo();
644
+ },
645
+ // 更新已有图形的位置(mars2d latlng为[lat,lng],mars3d position为[lng,lat])
646
+ setGraphicPosition (graphic, lnglat) {
647
+ if (this.is3d) {
648
+ graphic.position = lnglat;
649
+ } else {
650
+ graphic.latlng = [lnglat[1], lnglat[0]];
651
+ }
652
+ },
653
+ // 移除图形
654
+ handleRemoveMarker () {
655
+ // 3D下绘制(rectangle等支持编辑的图形)时,mars3d 内部会自动把正在编辑的图形设为
656
+ // map.trackedEntity 以实现跟随/贴合效果。直接 removeGraphic 并不会同步清掉这个引用,
657
+ // 之后 Cesium 每帧仍尝试跟踪一个已被销毁的实体,其位置退化为坐标原点,相机会在后续
658
+ // 数帧里持续朝着地球(0,0)附近平滑漂移,表现为清除后视角划过地球背面。
659
+ // 这里移除图形前先清掉 trackedEntity,避免残留追踪引发这个漂移
660
+ if (this.is3d && this.map.trackedEntity) {
661
+ this.map.trackedEntity = undefined;
662
+ }
663
+ if (this.marker) {
664
+ this.map.graphicLayer.removeGraphic(this.marker);
665
+ this.marker = null;
666
+ }
667
+ if (this.plot) {
668
+ if (Array.isArray(this.plot)) {
669
+ this.plot.forEach(graphic => this.map.graphicLayer.removeGraphic(graphic));
670
+ } else {
671
+ this.map.graphicLayer.removeGraphic(this.plot);
672
+ }
673
+ this.plot = null;
674
+ }
675
+ },
676
+ // 获取逆地理编码(不用 queryPOI.getAddress:mars2d 按高德的返回结构判断成功与否,天地图接口一直被误判为失败)
677
+ getRegeoCode (location) {
678
+ axios.get("http://api.tianditu.gov.cn/geocoder", {
679
+ params: {
680
+ type: "geocode",
681
+ tk: "0050eb1a5c837128f694a9022055fa54",
682
+ postStr: JSON.stringify({ lon: location[0], lat: location[1], ver: 1 })
683
+ }
684
+ }).then(({ data }) => {
685
+ if (data && data.result && data.result.formatted_address) {
686
+ this.newVal.name = data.result.formatted_address;
687
+ }
688
+ }).catch((err) => {
689
+ console.log(err);
516
690
  });
691
+ },
692
+ toWgs84 (lnglat, crs) {
693
+ return lnglat && crs !== "WGS84" ? this.mapEngine.PointTrans.gcj2wgs(lnglat) : lnglat;
517
694
  }
518
695
  }
519
696
  };
520
697
  </script>
521
698
 
522
699
  <style lang="less">
523
- @import url("../control.less");
524
-
525
- .DshCoordinates {
526
- #control-show();
527
-
528
- &-modal {
529
- &-header {
530
- padding: 5px 0px;
531
-
532
- .header {
533
- &-edit {
534
- &-message {
535
- position: absolute;
536
- color: red;
537
- font-size: 12px;
538
- bottom: -16px;
539
- left: 0px;
540
- }
700
+ @import url("../control.less");
701
+
702
+ .DshCoordinates {
703
+ #control-show();
704
+
705
+ &-modal {
706
+ &-header {
707
+ padding: 5px 0px;
708
+
709
+ .header {
710
+ &-edit {
711
+ &-message {
712
+ position: absolute;
713
+ color: red;
714
+ font-size: 12px;
715
+ bottom: -16px;
716
+ left: 0px;
717
+ }
541
718
 
542
- &-text {
543
- font-size: @textSize;
544
- line-height: 18px;
545
- }
719
+ &-text {
720
+ font-size: @textSize;
721
+ line-height: 18px;
722
+ }
546
723
 
547
- &-row {
548
- margin-top: 5px;
549
- }
724
+ &-row {
725
+ margin-top: 5px;
550
726
  }
727
+ }
551
728
 
552
- &-show {
553
- &-text {
554
- text-align: right;
555
- .dsh-padding-top10();
556
- }
729
+ &-show {
730
+ &-text {
731
+ text-align: right;
732
+ .dsh-padding-top10();
557
733
  }
558
734
  }
559
735
  }
736
+ }
560
737
 
561
- &-map {
562
- width: 100%;
563
- height: 100%;
564
- }
738
+ &-map {
739
+ width: 100%;
740
+ height: 100%;
741
+ }
565
742
 
566
- &-lnglat {
567
- padding: 5px 10px;
568
- background:#fff;
569
- display: none;
570
- position:absolute;
571
- }
743
+ &-lnglat {
744
+ padding: 5px 10px;
745
+ background: #fff;
746
+ display: none;
747
+ position: absolute;
748
+ z-index: 999;
749
+ }
572
750
 
573
- .ivu-modal-content {
574
- overflow: hidden;
575
- height: 100%;
751
+ .ivu-modal-content {
752
+ overflow: hidden;
753
+ height: 100%;
754
+ display: flex;
755
+ flex-direction: column;
576
756
 
577
- .ivu-modal-body {
578
- padding: 0px;
579
- height: 100%;
580
- }
757
+ .ivu-modal-body {
758
+ padding: 0px;
759
+ flex: 1;
760
+ height: 0;
581
761
  }
582
762
  }
583
763
  }
584
- .nodata{
585
- color: #999;
586
- }
764
+ }
765
+
766
+ .nodata {
767
+ color: #999;
768
+ }
587
769
  </style>