controlPanel.vue 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321
  1. <template>
  2. <div id="controlPanelBox">
  3. <div>
  4. <div class="sceneNameBox">
  5. <div class="">
  6. 场景名称:
  7. <el-cascader
  8. :disabled="$route.query.sceneId != undefined"
  9. v-model="SceneValue"
  10. placeholder="试试搜索:距离"
  11. :options="SceneList"
  12. :props="{ expandTrigger: 'hover' }"
  13. @change="handleChange"
  14. filterable
  15. ></el-cascader>
  16. <el-tooltip
  17. v-if="SceneValue && dmsServerItem.functionalDefinition"
  18. :content="dmsServerItem.functionalDefinition"
  19. placement="bottom"
  20. :popper-style="{ maxWidth: '300px' }"
  21. >
  22. <el-icon
  23. style="margin-left: 1rem; width: 1rem; height: 1rem"
  24. v-show="SceneValue && dmsServerItem.functionalDefinition"
  25. ><QuestionFilled
  26. /></el-icon>
  27. </el-tooltip>
  28. </div>
  29. <div>
  30. <el-button type="primary" @click="sendGeometriesToBackend"
  31. >发送<i class="el-icon-s-promotion el-icon--right"></i
  32. ></el-button>
  33. </div>
  34. </div>
  35. <el-divider></el-divider>
  36. <div>
  37. 元素个数:{{ SceneValue && dmsServerItem ? dmsServerItem.numberOf : "0" }}
  38. </div>
  39. <div>
  40. 参数类型:{{ SceneValue && dmsServerItem ? dmsServerItem.elementTypes : "[]" }}
  41. </div>
  42. <div>接口地址:{{ SceneValue && dmsServerItem ? dmsServerItem.apiUrl : "/" }}</div>
  43. </div>
  44. <el-divider></el-divider>
  45. <div>
  46. <!-- 元素文本渲染和操作区域 -->
  47. <el-tabs tab-position="left" style="height: calc(100vh - 250px)" class="demo-tabs">
  48. <el-tab-pane label="入参">
  49. <div v-if="SceneValue && dmsServerItem && ifHasType('file')">
  50. <el-upload
  51. v-model:file-list="fileList"
  52. class="upload-demo"
  53. action=""
  54. :limit="1"
  55. :on-change="handleFileChange"
  56. :on-exceed="handleExceed"
  57. :on-remove="uploadRemove"
  58. :auto-upload="false"
  59. >
  60. <el-button type="primary">上传文件</el-button>
  61. </el-upload>
  62. </div>
  63. <div v-if="SceneValue && dmsServerItem">
  64. <div v-for="item in dmsServerItem.elementTypes" :key="item">
  65. <div
  66. v-if="SceneValue && dmsServerItem && selectOptions[item]"
  67. style="margin: 0.5rem 0"
  68. >
  69. {{ item }}:<el-select
  70. @change="handleSelectChange(item, $event)"
  71. v-if="selectOptions[item]"
  72. v-model="params[item]"
  73. :placeholder="'请选择' + item"
  74. style="width: 240px"
  75. >
  76. <el-option
  77. v-for="item2 in selectOptions[item]"
  78. :key="item2.value"
  79. :label="item2.label"
  80. :value="item2.value"
  81. />
  82. </el-select>
  83. </div>
  84. <div
  85. v-if="
  86. [
  87. 'lon',
  88. 'lat',
  89. 'lonKey',
  90. 'latKey',
  91. 'filePath',
  92. 'EPSILON',
  93. 'distance',
  94. ].includes(item)
  95. "
  96. >
  97. {{ item }}:
  98. <el-input
  99. v-model="params[item]"
  100. @input="handleSelectChange(item, $event)"
  101. style="width: 240px"
  102. :placeholder="'请输入' + item"
  103. />
  104. </div>
  105. </div>
  106. </div>
  107. <div
  108. class="vueJsonEditor_box"
  109. v-show="
  110. SceneValue &&
  111. dmsServerItem &&
  112. (ifHasType('point') || ifHasType('polyline') || ifHasType('polygon'))
  113. "
  114. >
  115. <div class="vueJsonEditor_tools">
  116. <span
  117. v-if="jsonData && (jsonData.features || jsonData.geometry)"
  118. @click="showToMap(jsonData, 'input')"
  119. >渲染到地图中</span
  120. >
  121. <el-tooltip content="定位到当前入参渲染要素" placement="top">
  122. <span v-if="renderStatus.input" @click="locateRenderedGeojson('input')">
  123. 定位
  124. </span>
  125. </el-tooltip>
  126. <span @click="copyJsonData(jsonData)">copy</span>
  127. </div>
  128. <vue-json-editor
  129. :key="'json-input-editor-' + inputEditorKey"
  130. v-model="jsonData"
  131. :value="jsonData"
  132. :show-btns="false"
  133. :mode="'code'"
  134. :lang="'zh'"
  135. @json-change="handleJsonChange"
  136. >
  137. </vue-json-editor>
  138. </div>
  139. </el-tab-pane>
  140. <el-tab-pane label="返回">
  141. <div
  142. v-if="backData.message || backData.error"
  143. :style="{
  144. backgroundColor: backData.code === 200 ? '#67C23A' : '#F56C6C',
  145. color: '#fff',
  146. padding: '5px 10px',
  147. fontSize: '14px',
  148. }"
  149. >
  150. {{ backData.message || backData.error }}
  151. </div>
  152. <div class="vueJsonEditor_box" v-show="SceneValue && dmsServerItem">
  153. <div class="vueJsonEditor_tools">
  154. <span
  155. v-if="
  156. backData.content &&
  157. (backData.content.features || backData.content.geometry)
  158. "
  159. @click="showToMap(backData.content, 'output')"
  160. >渲染到地图中</span
  161. >
  162. <el-tooltip content="定位到当前返回渲染要素" placement="top">
  163. <span v-if="renderStatus.output" @click="locateRenderedGeojson('output')">
  164. 定位
  165. </span>
  166. </el-tooltip>
  167. <span @click="copyJsonData(backData.content)">copy</span>
  168. </div>
  169. <vue-json-editor
  170. v-if="backData.content"
  171. :key="'json-output-editor-' + outputEditorKey"
  172. v-model="backData.content"
  173. :value="backData.content"
  174. @json-change="handleJsonChange2"
  175. :show-btns="false"
  176. :mode="'code'"
  177. :lang="'zh'"
  178. >
  179. </vue-json-editor></div
  180. ></el-tab-pane>
  181. </el-tabs>
  182. </div>
  183. <el-dialog
  184. v-model="propertyDialog.visible"
  185. title="要素属性"
  186. width="420px"
  187. draggable
  188. :modal="false"
  189. append-to-body
  190. class="feature-property-dialog"
  191. >
  192. <div class="feature-property-content">
  193. <div class="feature-property-tip">支持直接编辑属性值,失焦后自动同步 JSON。</div>
  194. <div
  195. class="feature-property-item"
  196. v-for="item in propertyDialog.list"
  197. :key="'feature-property-' + item.id"
  198. >
  199. <div class="feature-property-row">
  200. <el-input
  201. v-model="item.editKey"
  202. class="feature-property-input feature-property-key-input"
  203. placeholder="属性名"
  204. @change="handlePropertyKeyChange(item)"
  205. />
  206. <el-button
  207. type="danger"
  208. plain
  209. size="small"
  210. class="feature-property-delete-btn"
  211. @click="deleteProperty(item)"
  212. >
  213. 删除
  214. </el-button>
  215. </div>
  216. <el-input
  217. v-model="item.editValue"
  218. class="feature-property-input"
  219. placeholder="属性值"
  220. @change="handlePropertyValueChange(item)"
  221. />
  222. </div>
  223. <el-empty
  224. v-if="!propertyDialog.list.length"
  225. description="当前要素无属性信息"
  226. :image-size="80"
  227. />
  228. </div>
  229. <template #footer>
  230. <el-button type="primary" plain @click="addProperty">+ 新增属性</el-button>
  231. </template>
  232. </el-dialog>
  233. <!-- 绘制工具栏 -->
  234. <div
  235. class="toolbar"
  236. v-if="
  237. SceneValue &&
  238. dmsServerItem &&
  239. (ifHasType('point') || ifHasType('polyline') || ifHasType('polygon'))
  240. "
  241. >
  242. <div
  243. class="tool-item"
  244. @click="activateDraw('point')"
  245. :class="{ active: currentTool === 'point' }"
  246. >
  247. 绘制点
  248. <el-tooltip
  249. content="绘制开关,蓝色状态代表开启,再次点击结束绘制,地图左键点击选点,"
  250. placement="right"
  251. >
  252. <el-icon style="width: 1rem; height: 1rem"><QuestionFilled /></el-icon>
  253. </el-tooltip>
  254. </div>
  255. <div
  256. class="tool-item"
  257. @click="activateDraw('polyline')"
  258. :class="{ active: currentTool === 'polyline' }"
  259. >
  260. 绘制线<el-tooltip
  261. content="绘制线最少需要两个点,鼠标左键点击选点,点之间自动连线,鼠标右键结束绘制"
  262. placement="right"
  263. >
  264. <el-icon style="width: 1rem; height: 1rem"><QuestionFilled /></el-icon>
  265. </el-tooltip>
  266. </div>
  267. <div
  268. class="tool-item"
  269. @click="activateDraw('polygon')"
  270. :class="{ active: currentTool === 'polygon' }"
  271. >
  272. 绘制面<el-tooltip
  273. content="绘制面最少需要三个点,鼠标左键点击选点,面自动必合,鼠标右键结束绘制"
  274. placement="right"
  275. >
  276. <el-icon style="width: 1rem; height: 1rem"><QuestionFilled /></el-icon>
  277. </el-tooltip>
  278. </div>
  279. <!-- <div class="tool-item" @click="startHoleDrawing" :class="{ active: isDrawingHole }">
  280. 绘制镂空
  281. </div> -->
  282. <div class="tool-item" @click="clearAll">清除所有</div>
  283. </div>
  284. </div>
  285. </template>
  286. <script>
  287. import wgn from "../../api/wgn";
  288. // 控制面板
  289. export default {
  290. name: "ControlPanel",
  291. // 2. 接收父组件传递的 props(单向数据流,子组件不能直接修改)
  292. props: {
  293. btnText: {
  294. type: String, // 类型限制
  295. default: "默认按钮", // 默认值
  296. required: false, // 是否必传
  297. },
  298. showCount: {
  299. type: Boolean,
  300. default: false,
  301. },
  302. },
  303. data() {
  304. return {
  305. currentFile: null, // 当前选中的文件
  306. fileList: [],
  307. // 其他参数
  308. params: {
  309. unit: "",
  310. outFileType: "",
  311. outPrj: "",
  312. inPrj: "",
  313. compressionRatio: "",
  314. lon: "",
  315. lat: "",
  316. },
  317. // 请求参数
  318. jsonData: {},
  319. // 返回参数
  320. backData: {},
  321. // 当前场景值
  322. SceneValue: "",
  323. // 场景列表
  324. SceneList: [
  325. {
  326. value: "1.5.1",
  327. label: "1.5.1拓扑计算",
  328. children: [
  329. {
  330. value: "1.5.1.1",
  331. label: "点线面拓扑关系",
  332. },
  333. ],
  334. },
  335. {
  336. value: "1.5.2",
  337. label: "1.5.2空间量算",
  338. children: [
  339. {
  340. value: "1.5.2.1",
  341. label: "两点欧氏距离",
  342. },
  343. {
  344. value: "1.5.2.2",
  345. label: "点到线的最短距离",
  346. },
  347. {
  348. value: "1.5.2.3",
  349. label: "点到面的最短距离",
  350. },
  351. {
  352. value: "1.5.2.4",
  353. label: "面到面的最短距离",
  354. },
  355. {
  356. value: "1.5.2.5",
  357. label: "折线距离(累加线段长度)",
  358. },
  359. {
  360. value: "1.5.2.6",
  361. label: "平面面积、不规则面面积",
  362. },
  363. {
  364. value: "1.5.2.7",
  365. label: "曲面面积,考虑地形起伏",
  366. },
  367. {
  368. value: "1.5.2.8",
  369. label: "单点高程查询",
  370. },
  371. {
  372. value: "1.5.2.9",
  373. label: "两点高程差",
  374. },
  375. {
  376. value: "1.5.2.10",
  377. label: "区域高程统计",
  378. },
  379. ],
  380. },
  381. {
  382. value: "1.5.3",
  383. label: "1.5.3几何运算",
  384. children: [
  385. {
  386. value: "1.5.3.1",
  387. label: "并集运算",
  388. },
  389. {
  390. value: "1.5.3.2",
  391. label: "交集运算",
  392. },
  393. {
  394. value: "1.5.3.3",
  395. label: "差集运算",
  396. },
  397. {
  398. value: "1.5.3.4",
  399. label: "几何参数计算",
  400. },
  401. ],
  402. },
  403. {
  404. value: "1.5.4",
  405. label: "1.5.4关系分析",
  406. children: [
  407. {
  408. value: "1.5.4.1",
  409. label: "邻接关系分析",
  410. },
  411. {
  412. value: "1.5.4.2",
  413. label: "包含关系分析",
  414. },
  415. {
  416. value: "1.5.4.3",
  417. label: "相交关系分析",
  418. },
  419. {
  420. value: "1.5.4.4",
  421. label: "相离关系分析",
  422. },
  423. ],
  424. },
  425. {
  426. value: "1.5.5",
  427. label: "1.5.5非空间数据转换",
  428. children: [
  429. {
  430. value: "1.5.5.1",
  431. label: "非空间数据转空间数据",
  432. },
  433. ],
  434. },
  435. {
  436. value: "1.5.6",
  437. label: "1.5.6坐标转换",
  438. children: [
  439. {
  440. value: "1.5.6.1",
  441. label: "单点的坐标转换接口",
  442. },
  443. {
  444. value: "1.5.6.2",
  445. label: "geojson坐标转换接口",
  446. },
  447. ],
  448. },
  449. {
  450. value: "1.5.7",
  451. label: "1.5.7时空数据格式转换",
  452. children: [
  453. {
  454. value: "1.5.7.1",
  455. label: "文件格式转换",
  456. },
  457. {
  458. value: "1.5.7.2",
  459. label: "转换文件下载",
  460. },
  461. ],
  462. },
  463. {
  464. value: "1.5.8",
  465. label: "1.5.8自定义工具",
  466. children: [
  467. {
  468. value: "1.5.8.1",
  469. label: "obj三维模型轻量化",
  470. },
  471. ],
  472. },
  473. // 时空算子库
  474. {
  475. value: "1.2.5",
  476. label: "1.2.5时空算子库",
  477. children: [
  478. {
  479. value: "1.2.5.1",
  480. label: "墨卡托计算距离面积",
  481. },
  482. {
  483. value: "1.2.5.2",
  484. label: "设置障碍的路径计算",
  485. },
  486. {
  487. value: "1.2.5.3",
  488. label: "点线面体的相互状态",
  489. },
  490. {
  491. value: "1.2.5.4",
  492. label: "缓冲区计算",
  493. },
  494. {
  495. value: "1.2.5.5",
  496. label: "线面体分割",
  497. },
  498. {
  499. value: "1.2.5.6",
  500. label: "时空差异分析",
  501. },
  502. ],
  503. },
  504. ],
  505. dmsServerItem: {},
  506. selectOptions: {
  507. // 单位
  508. unit: [
  509. {
  510. value: "METER",
  511. label: "米",
  512. },
  513. {
  514. value: "KILOMETER",
  515. label: "千米",
  516. },
  517. ],
  518. inPrj: [
  519. {
  520. value: "WGS84",
  521. label: "WGS84",
  522. },
  523. {
  524. value: "SH2000",
  525. label: "SH2000",
  526. },
  527. {
  528. value: "UTM",
  529. label: "UTM",
  530. },
  531. {
  532. value: "WEB_Mercator",
  533. label: "WEB_Mercator",
  534. },
  535. ],
  536. compressionRatio: [
  537. {
  538. value: "100%",
  539. label: "100%",
  540. },
  541. {
  542. value: "80%",
  543. label: "80%",
  544. },
  545. {
  546. value: "50%",
  547. label: "50%",
  548. },
  549. {
  550. value: "25%",
  551. label: "25%",
  552. },
  553. ],
  554. outPrj: [
  555. {
  556. value: "WGS84",
  557. label: "WGS84",
  558. },
  559. {
  560. value: "SH2000",
  561. label: "SH2000",
  562. },
  563. {
  564. value: "UTM",
  565. label: "UTM",
  566. },
  567. {
  568. value: "WEB_Mercator",
  569. label: "WEB_Mercator",
  570. },
  571. ],
  572. outFileType: [
  573. {
  574. value: "geoJson",
  575. label: "geoJson",
  576. },
  577. {
  578. value: "shp",
  579. label: "shp",
  580. },
  581. {
  582. value: "csv",
  583. label: "csv",
  584. },
  585. {
  586. value: "xlsx",
  587. label: "xlsx",
  588. },
  589. ],
  590. },
  591. currentTool: null,
  592. drawingMode: null,
  593. handler: null,
  594. drawnEntities: [],
  595. geometries: [], // 存储绘制的几何对象
  596. // 绘制状态相关
  597. currentEntity: null, // 当前正在绘制的实体
  598. currentPositions: [], // 当前正在绘制的位置数组
  599. isDrawingHole: false, // 是否正在绘制镂空
  600. currentPolygonEntity: null, // 当前要添加镂空的多边形实体
  601. currentPolygonGeometry: null, // 当前要添加镂空的多边形几何对象
  602. tempEntity: null, // 临时预览实体
  603. // 地图渲染状态(入参/返回)
  604. renderStatus: {
  605. input: false,
  606. output: false,
  607. },
  608. // 缓存两侧最近一次成功渲染的geojson,用于定位时恢复
  609. renderedGeojsonCache: {
  610. input: null,
  611. output: null,
  612. },
  613. currentRenderedSource: "",
  614. // 地图点击属性弹窗
  615. featurePickHandler: null,
  616. inputEditorKey: 0,
  617. outputEditorKey: 0,
  618. propertyIdSeed: 1,
  619. propertyDialog: {
  620. visible: false,
  621. list: [],
  622. source: "",
  623. propertiesRef: null,
  624. featureIndex: -1,
  625. },
  626. };
  627. },
  628. mounted() {
  629. this.SceneValue = this.$route.query.sceneId ? this.$route.query.sceneId : "";
  630. },
  631. beforeUnmount() {
  632. // 组件卸载前清理资源
  633. this.deactivateDraw();
  634. if (this.handler) {
  635. this.handler.destroy();
  636. }
  637. if (this.featurePickHandler) {
  638. this.featurePickHandler.destroy();
  639. this.featurePickHandler = null;
  640. }
  641. },
  642. watch: {
  643. SceneValue(newVal, oldVal) {
  644. if (newVal && newVal != oldVal) {
  645. // 请求DMS
  646. this.searchServerList(this.SceneValue);
  647. }
  648. },
  649. },
  650. methods: {
  651. ifHasType(type) {
  652. if (this.dmsServerItem.elementTypes && this.dmsServerItem.elementTypes.length > 0) {
  653. return (
  654. this.dmsServerItem.elementTypes.includes(type) ||
  655. this.dmsServerItem.elementTypes.includes(type + "Z")
  656. );
  657. } else {
  658. return false;
  659. }
  660. },
  661. handleJsonChange(jsonStr) {
  662. this.jsonData = jsonStr;
  663. },
  664. handleJsonChange2(jsonStr) {
  665. this.backData.content = jsonStr;
  666. },
  667. // 搜索微功能服务
  668. searchServerList() {
  669. let requestParams = {
  670. columnId: systemConfig.columnIds[5],
  671. states: 0,
  672. pageSize: 999,
  673. page: 0,
  674. };
  675. if (this.SceneValue) {
  676. requestParams.search = JSON.stringify([
  677. {
  678. field: "c_scene_name",
  679. searchType: 1,
  680. content: { value: this.SceneValue },
  681. },
  682. ]);
  683. // 获取微功能服务列表
  684. wgn
  685. .getDmsData(requestParams)
  686. .then((res) => {
  687. if (res.code === 200 && res.content.data[0]) {
  688. let dmsServiceData = res.content.data[0];
  689. this.dmsServerItem = {
  690. // 功能描述
  691. functionalDefinition: dmsServiceData.content,
  692. // 元素类型
  693. elementTypes: JSON.parse(dmsServiceData.c_input_parameter_rules),
  694. // 元素个数
  695. numberOf: JSON.parse(dmsServiceData.c_number_of_elements),
  696. // 后台接口路径
  697. apiUrl: dmsServiceData.c_url,
  698. };
  699. } else {
  700. this.$message({
  701. message: "搜索微功能服务失败,请检查场景名称是否正确",
  702. type: "warning",
  703. });
  704. }
  705. })
  706. .catch((e) => {
  707. this.$message({
  708. message: "搜索微功能服务失败" + e,
  709. type: "error",
  710. });
  711. });
  712. }
  713. },
  714. getSourceGeojsonData(source) {
  715. return source === "output" ? this.backData.content : this.jsonData;
  716. },
  717. // 将用户输入或后台返回的geojson渲染到地图中
  718. showToMap(geojson, source = "input") {
  719. if (!geojson || (!geojson.features && !geojson.geometry)) {
  720. this.$message({
  721. message: "当前JSON缺少有效几何信息,无法渲染",
  722. type: "warning",
  723. });
  724. return;
  725. }
  726. this.currentRenderedSource = source;
  727. // 1. 清除所有地图中的元素
  728. this.clearAllMap();
  729. // 2. 将geojson添加到地图中
  730. this.addToMap(geojson);
  731. this.currentRenderedSource = source;
  732. this.renderStatus[source] = true;
  733. // 仅缓存纯数据,避免响应式对象带来的引用副作用
  734. this.renderedGeojsonCache[source] = JSON.parse(JSON.stringify(geojson));
  735. this.flyToGeojson(geojson);
  736. },
  737. // 定位到已渲染要素
  738. locateRenderedGeojson(source) {
  739. if (!this.renderStatus[source]) {
  740. return;
  741. }
  742. // 当前地图不是该来源数据时,优先恢复对应渲染结果,再飞行定位
  743. if (this.currentRenderedSource !== source && this.renderedGeojsonCache[source]) {
  744. this.showToMap(this.renderedGeojsonCache[source], source);
  745. return;
  746. }
  747. const sourceGeojson =
  748. this.getSourceGeojsonData(source) || this.renderedGeojsonCache[source];
  749. this.flyToGeojson(sourceGeojson);
  750. },
  751. collectGeometryCoordinates(geometry) {
  752. if (!geometry || !geometry.coordinates) {
  753. return [];
  754. }
  755. const { type, coordinates } = geometry;
  756. const points = [];
  757. if (type === "Point") {
  758. points.push([coordinates[0], coordinates[1]]);
  759. } else if (type === "LineString") {
  760. coordinates.forEach((coord) => points.push([coord[0], coord[1]]));
  761. } else if (type === "Polygon") {
  762. coordinates.forEach((ring) => {
  763. ring.forEach((coord) => points.push([coord[0], coord[1]]));
  764. });
  765. } else if (type === "MultiPolygon") {
  766. coordinates.forEach((polygon) => {
  767. polygon.forEach((ring) => {
  768. ring.forEach((coord) => points.push([coord[0], coord[1]]));
  769. });
  770. });
  771. }
  772. return points;
  773. },
  774. collectGeojsonCoordinates(geojson) {
  775. if (!geojson) {
  776. return [];
  777. }
  778. if (geojson.features && Array.isArray(geojson.features)) {
  779. return geojson.features.flatMap((feature) =>
  780. this.collectGeometryCoordinates(feature.geometry)
  781. );
  782. }
  783. if (geojson.geometry) {
  784. return this.collectGeometryCoordinates(geojson.geometry);
  785. }
  786. return [];
  787. },
  788. // 飞行定位到geojson范围
  789. flyToGeojson(geojson) {
  790. if (!viewer || !geojson) {
  791. return;
  792. }
  793. const points = this.collectGeojsonCoordinates(geojson);
  794. if (!points.length) {
  795. return;
  796. }
  797. if (points.length === 1) {
  798. viewer.camera.flyTo({
  799. destination: SkyScenery.Cartesian3.fromDegrees(
  800. points[0][0],
  801. points[0][1],
  802. 1200
  803. ),
  804. duration: 1.1,
  805. orientation: {
  806. heading: viewer.camera.heading,
  807. pitch: SkyScenery.Math.toRadians(-65),
  808. roll: 0,
  809. },
  810. });
  811. viewer.scene.requestRender();
  812. return;
  813. }
  814. let minLon = Infinity;
  815. let maxLon = -Infinity;
  816. let minLat = Infinity;
  817. let maxLat = -Infinity;
  818. points.forEach((point) => {
  819. minLon = Math.min(minLon, point[0]);
  820. maxLon = Math.max(maxLon, point[0]);
  821. minLat = Math.min(minLat, point[1]);
  822. maxLat = Math.max(maxLat, point[1]);
  823. });
  824. const lonPad = Math.max((maxLon - minLon) * 0.25, 0.0008);
  825. const latPad = Math.max((maxLat - minLat) * 0.25, 0.0008);
  826. viewer.camera.flyTo({
  827. destination: SkyScenery.Rectangle.fromDegrees(
  828. minLon - lonPad,
  829. minLat - latPad,
  830. maxLon + lonPad,
  831. maxLat + latPad
  832. ),
  833. duration: 1.1,
  834. });
  835. viewer.scene.requestRender();
  836. },
  837. // 把属性对象格式化为可编辑列表
  838. formatFeatureProperties(properties) {
  839. if (!properties || typeof properties !== "object") {
  840. return [];
  841. }
  842. return Object.keys(properties).map((key) => {
  843. const rawValue = properties[key];
  844. const editValue =
  845. rawValue === null || rawValue === undefined
  846. ? ""
  847. : typeof rawValue === "object"
  848. ? JSON.stringify(rawValue)
  849. : String(rawValue);
  850. return {
  851. id: this.propertyIdSeed++,
  852. originalKey: key,
  853. editKey: key,
  854. editValue,
  855. };
  856. });
  857. },
  858. parsePropertyValue(inputValue) {
  859. if (typeof inputValue !== "string") {
  860. return inputValue;
  861. }
  862. const value = inputValue.trim();
  863. if (value === "") {
  864. return "";
  865. }
  866. if (value === "true") {
  867. return true;
  868. }
  869. if (value === "false") {
  870. return false;
  871. }
  872. if (value === "null") {
  873. return null;
  874. }
  875. if (!isNaN(Number(value))) {
  876. return Number(value);
  877. }
  878. if (
  879. (value.startsWith("{") && value.endsWith("}")) ||
  880. (value.startsWith("[") && value.endsWith("]"))
  881. ) {
  882. try {
  883. return JSON.parse(value);
  884. } catch (e) {
  885. return inputValue;
  886. }
  887. }
  888. return inputValue;
  889. },
  890. syncJsonEditorData(source) {
  891. if (source === "output") {
  892. if (!this.backData.content) {
  893. return;
  894. }
  895. this.backData = {
  896. ...this.backData,
  897. content: JSON.parse(JSON.stringify(this.backData.content)),
  898. };
  899. } else {
  900. this.jsonData = JSON.parse(JSON.stringify(this.jsonData));
  901. }
  902. if (source === "output") {
  903. this.outputEditorKey += 1;
  904. } else {
  905. this.inputEditorKey += 1;
  906. }
  907. this.renderedGeojsonCache[source] = JSON.parse(
  908. JSON.stringify(this.getSourceGeojsonData(source))
  909. );
  910. const latestGeojson = this.getSourceGeojsonData(source);
  911. if (
  912. latestGeojson &&
  913. this.propertyDialog.visible &&
  914. this.propertyDialog.source === source
  915. ) {
  916. if (
  917. this.propertyDialog.featureIndex >= 0 &&
  918. latestGeojson.features &&
  919. latestGeojson.features[this.propertyDialog.featureIndex]
  920. ) {
  921. if (!latestGeojson.features[this.propertyDialog.featureIndex].properties) {
  922. latestGeojson.features[this.propertyDialog.featureIndex].properties = {};
  923. }
  924. this.propertyDialog.propertiesRef =
  925. latestGeojson.features[this.propertyDialog.featureIndex].properties;
  926. } else {
  927. if (!latestGeojson.properties) {
  928. latestGeojson.properties = {};
  929. }
  930. this.propertyDialog.propertiesRef = latestGeojson.properties;
  931. }
  932. }
  933. },
  934. handlePropertyValueChange(item) {
  935. if (!this.propertyDialog.propertiesRef || !item) {
  936. return;
  937. }
  938. const currentKey = (item.editKey || "").trim();
  939. if (!currentKey) {
  940. this.$message({
  941. message: "属性名不能为空",
  942. type: "warning",
  943. });
  944. item.editKey = item.originalKey || "";
  945. return;
  946. }
  947. this.propertyDialog.propertiesRef[currentKey] = this.parsePropertyValue(
  948. item.editValue
  949. );
  950. item.originalKey = currentKey;
  951. item.editKey = currentKey;
  952. this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
  953. this.syncJsonEditorData(
  954. this.propertyDialog.source || this.currentRenderedSource || "input"
  955. );
  956. },
  957. handlePropertyKeyChange(item) {
  958. if (!this.propertyDialog.propertiesRef || !item) {
  959. return;
  960. }
  961. const oldKey = item.originalKey;
  962. const newKey = (item.editKey || "").trim();
  963. if (!newKey) {
  964. this.$message({
  965. message: "属性名不能为空",
  966. type: "warning",
  967. });
  968. item.editKey = oldKey;
  969. return;
  970. }
  971. if (
  972. newKey !== oldKey &&
  973. Object.prototype.hasOwnProperty.call(this.propertyDialog.propertiesRef, newKey)
  974. ) {
  975. this.$message({
  976. message: "属性名已存在,请更换",
  977. type: "warning",
  978. });
  979. item.editKey = oldKey;
  980. return;
  981. }
  982. const oldValue = this.propertyDialog.propertiesRef[oldKey];
  983. if (newKey !== oldKey) {
  984. delete this.propertyDialog.propertiesRef[oldKey];
  985. this.propertyDialog.propertiesRef[newKey] = oldValue;
  986. }
  987. item.originalKey = newKey;
  988. item.editKey = newKey;
  989. this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
  990. this.syncJsonEditorData(
  991. this.propertyDialog.source || this.currentRenderedSource || "input"
  992. );
  993. },
  994. deleteProperty(item) {
  995. if (!this.propertyDialog.propertiesRef || !item) {
  996. return;
  997. }
  998. const key = (item.originalKey || "").trim();
  999. if (
  1000. key &&
  1001. Object.prototype.hasOwnProperty.call(this.propertyDialog.propertiesRef, key)
  1002. ) {
  1003. delete this.propertyDialog.propertiesRef[key];
  1004. }
  1005. this.propertyDialog.list = this.propertyDialog.list.filter(
  1006. (property) => property.id !== item.id
  1007. );
  1008. this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
  1009. this.syncJsonEditorData(
  1010. this.propertyDialog.source || this.currentRenderedSource || "input"
  1011. );
  1012. },
  1013. addProperty() {
  1014. if (!this.propertyDialog.propertiesRef) {
  1015. this.$message({
  1016. message: "请先点击地图中的已渲染要素",
  1017. type: "warning",
  1018. });
  1019. return;
  1020. }
  1021. let index = 1;
  1022. let newKey = "newKey";
  1023. while (
  1024. Object.prototype.hasOwnProperty.call(this.propertyDialog.propertiesRef, newKey)
  1025. ) {
  1026. newKey = `newKey${index}`;
  1027. index += 1;
  1028. }
  1029. this.propertyDialog.propertiesRef[newKey] = "";
  1030. this.propertyDialog.list.push({
  1031. id: this.propertyIdSeed++,
  1032. originalKey: newKey,
  1033. editKey: newKey,
  1034. editValue: "",
  1035. });
  1036. this.syncPropertyToEntity(this.propertyDialog.propertiesRef);
  1037. this.syncJsonEditorData(
  1038. this.propertyDialog.source || this.currentRenderedSource || "input"
  1039. );
  1040. },
  1041. syncPropertyToEntity(propertiesRef) {
  1042. if (!propertiesRef || !viewer || !this.currentRenderedSource) {
  1043. return;
  1044. }
  1045. const targetSource = this.propertyDialog.source || this.currentRenderedSource;
  1046. const targetIndex = this.propertyDialog.featureIndex;
  1047. this.drawnEntities.forEach((entity) => {
  1048. if (!entity || !entity.__featureRef) {
  1049. return;
  1050. }
  1051. if (
  1052. entity.__featureRef.source === targetSource &&
  1053. entity.__featureRef.featureIndex === targetIndex
  1054. ) {
  1055. entity.__featureProperties = propertiesRef;
  1056. }
  1057. });
  1058. },
  1059. // 将geojson添加到地图中
  1060. addToMap(geojson) {
  1061. const source = this.currentRenderedSource || "input";
  1062. if (!geojson.features && geojson.geometry) {
  1063. const { type, coordinates } = geojson.geometry;
  1064. const featureProperties = geojson.properties || {};
  1065. const featureRefInfo = {
  1066. source,
  1067. featureIndex: -1,
  1068. };
  1069. switch (type) {
  1070. case "Point":
  1071. // 点
  1072. this.addPoint(coordinates, featureProperties, featureRefInfo);
  1073. break;
  1074. case "LineString":
  1075. // 线
  1076. this.addLine(coordinates, featureProperties, featureRefInfo);
  1077. break;
  1078. case "Polygon":
  1079. case "MultiPolygon":
  1080. // 面
  1081. this.addPolygon(coordinates, featureProperties, featureRefInfo);
  1082. break;
  1083. default:
  1084. break;
  1085. }
  1086. } else if (geojson.features) {
  1087. const features = geojson.features;
  1088. // 2. 遍历features,根据type添加到地图中
  1089. console.log("features", features);
  1090. features.forEach((feature, featureIndex) => {
  1091. const { type, coordinates } = feature.geometry;
  1092. const featureProperties = feature.properties || {};
  1093. const featureRefInfo = {
  1094. source,
  1095. featureIndex,
  1096. };
  1097. switch (type) {
  1098. case "Point":
  1099. // 点
  1100. this.addPoint(coordinates, featureProperties, featureRefInfo);
  1101. break;
  1102. case "LineString":
  1103. // 线
  1104. this.addLine(coordinates, featureProperties, featureRefInfo);
  1105. break;
  1106. case "Polygon":
  1107. case "MultiPolygon":
  1108. // 面
  1109. this.addPolygon(coordinates, featureProperties, featureRefInfo);
  1110. break;
  1111. default:
  1112. break;
  1113. }
  1114. });
  1115. }
  1116. setTimeout(() => {
  1117. viewer.scene.requestRender();
  1118. });
  1119. },
  1120. // 添加点到地图中
  1121. addPoint(coordinates, featureProperties = null, featureRefInfo = null) {
  1122. // 1. 解析点的坐标
  1123. console.log("addPoint coordinates", coordinates);
  1124. // 2. 创建点实体
  1125. const pointEntity = viewer.entities.add({
  1126. name: "point",
  1127. position:
  1128. coordinates.length > 2
  1129. ? SkyScenery.Cartesian3.fromDegrees(
  1130. coordinates[0],
  1131. coordinates[1],
  1132. coordinates[2]
  1133. )
  1134. : SkyScenery.Cartesian3.fromDegrees(coordinates[0], coordinates[1]),
  1135. point: {
  1136. show: true,
  1137. color: SkyScenery.Color.RED,
  1138. pixelSize: 10,
  1139. outlineColor: SkyScenery.Color.WHITE,
  1140. outlineWidth: 2,
  1141. },
  1142. });
  1143. pointEntity.__featureProperties = featureProperties;
  1144. pointEntity.__featureRef = featureRefInfo;
  1145. // 3. 将点实体添加到drawnEntities中
  1146. this.drawnEntities.push(pointEntity);
  1147. },
  1148. // 添加线到地图中
  1149. addLine(coordinates, featureProperties = null, featureRefInfo = null) {
  1150. // 1. 解析线的坐标
  1151. console.log("addLine coordinates", coordinates);
  1152. // 2. 处理坐标格式:如果是二维数组则取第一个元素,否则直接使用
  1153. const lineCoordinates =
  1154. Array.isArray(coordinates[0]) && Array.isArray(coordinates[0][0])
  1155. ? coordinates[0]
  1156. : coordinates;
  1157. // 3. 检测坐标是否包含Z值
  1158. const hasZValues = lineCoordinates.some(
  1159. (coord) => coord.length > 2 && typeof coord[2] === "number"
  1160. );
  1161. // 4. 根据是否有Z值选择合适的坐标转换方法
  1162. let positions;
  1163. if (hasZValues) {
  1164. // 包含Z值,使用带高度的坐标转换方法
  1165. const flatCoordinatesWithHeights = [];
  1166. lineCoordinates.forEach((coord) => {
  1167. flatCoordinatesWithHeights.push(coord[0], coord[1], coord[2] || 0);
  1168. });
  1169. positions = SkyScenery.Cartesian3.fromDegreesArrayHeights(
  1170. flatCoordinatesWithHeights
  1171. );
  1172. } else {
  1173. // 不包含Z值,使用普通坐标转换方法
  1174. const flatCoordinates = [];
  1175. lineCoordinates.forEach((coord) => {
  1176. flatCoordinates.push(coord[0], coord[1]);
  1177. });
  1178. positions = SkyScenery.Cartesian3.fromDegreesArray(flatCoordinates);
  1179. }
  1180. // 5. 创建线实体
  1181. const lineEntity = viewer.entities.add({
  1182. name: "line",
  1183. polyline: {
  1184. show: true,
  1185. positions: positions,
  1186. material: SkyScenery.Color.BLUE.withAlpha(0.8),
  1187. width: 3,
  1188. },
  1189. });
  1190. lineEntity.__featureProperties = featureProperties;
  1191. lineEntity.__featureRef = featureRefInfo;
  1192. // 6. 将线实体添加到drawnEntities中
  1193. this.drawnEntities.push(lineEntity);
  1194. },
  1195. // 添加面到地图中
  1196. addPolygon(coordinates, featureProperties = null, featureRefInfo = null) {
  1197. // 检测是否为MultiPolygon类型(MultiPolygon的坐标是三维数组)
  1198. if (
  1199. Array.isArray(coordinates[0]) &&
  1200. Array.isArray(coordinates[0][0]) &&
  1201. Array.isArray(coordinates[0][0][0])
  1202. ) {
  1203. console.log("MultiPolygon coordinates", coordinates);
  1204. // 是MultiPolygon类型,遍历每个Polygon
  1205. coordinates.forEach((polygonCoordinates) => {
  1206. this.renderSinglePolygon(polygonCoordinates, featureProperties, featureRefInfo);
  1207. });
  1208. } else {
  1209. // 是单个Polygon类型
  1210. this.renderSinglePolygon(coordinates, featureProperties, featureRefInfo);
  1211. }
  1212. },
  1213. // 渲染单个Polygon
  1214. renderSinglePolygon(coordinates, featureProperties = null, featureRefInfo = null) {
  1215. // 1. 处理坐标格式:确保获取外部环坐标
  1216. const outerRingCoordinates =
  1217. Array.isArray(coordinates[0]) && Array.isArray(coordinates[0][0])
  1218. ? coordinates[0]
  1219. : coordinates;
  1220. // 2. 检测坐标是否包含Z值
  1221. const hasZValues = outerRingCoordinates.some(
  1222. (coord) => coord.length > 2 && typeof coord[2] === "number"
  1223. );
  1224. // 3. 根据是否有Z值选择合适的坐标转换方法
  1225. let positions;
  1226. let baseHeight = 0;
  1227. let extrudedHeight = 0; // 默认挤压高度
  1228. if (hasZValues) {
  1229. // 包含Z值,使用带高度的坐标转换方法
  1230. const flatCoordinatesWithHeights = [];
  1231. let minZ = Infinity;
  1232. let maxZ = -Infinity;
  1233. // 计算Z值的范围
  1234. outerRingCoordinates.forEach((coord) => {
  1235. const z = coord[2] || 0;
  1236. minZ = Math.min(minZ, z);
  1237. maxZ = Math.max(maxZ, z);
  1238. flatCoordinatesWithHeights.push(coord[0], coord[1], minZ); // 使用最小Z值作为基础高度
  1239. });
  1240. positions = SkyScenery.Cartesian3.fromDegreesArrayHeights(
  1241. flatCoordinatesWithHeights
  1242. );
  1243. baseHeight = minZ;
  1244. extrudedHeight = maxZ;
  1245. } else {
  1246. // 不包含Z值,使用普通坐标转换方法
  1247. const flatCoordinates = [];
  1248. outerRingCoordinates.forEach((coord) => {
  1249. flatCoordinates.push(coord[0], coord[1]);
  1250. });
  1251. positions = SkyScenery.Cartesian3.fromDegreesArray(flatCoordinates);
  1252. }
  1253. // 4. 创建面实体
  1254. const polygonEntity = viewer.entities.add({
  1255. name: "polygon",
  1256. polygon: {
  1257. hierarchy: {
  1258. positions: positions,
  1259. },
  1260. height: baseHeight, // 多边形底部高度
  1261. extrudedHeight: extrudedHeight, // 多边形顶部高度,实现3D挤压效果
  1262. heightReference: hasZValues
  1263. ? SkyScenery.HeightReference.NONE
  1264. : SkyScenery.HeightReference.CLAMP_TO_GROUND,
  1265. material: SkyScenery.Color.GREEN.withAlpha(0.5),
  1266. outline: true,
  1267. outlineColor: SkyScenery.Color.WHITE,
  1268. outlineWidth: 2,
  1269. // 设置侧面材质
  1270. extrudedMaterial: SkyScenery.Color.GREEN.withAlpha(0.8),
  1271. },
  1272. });
  1273. polygonEntity.__featureProperties = featureProperties;
  1274. polygonEntity.__featureRef = featureRefInfo;
  1275. // 5. 将面实体添加到drawnEntities中
  1276. this.drawnEntities.push(polygonEntity);
  1277. },
  1278. handleSelectChange(item, value) {
  1279. this.jsonData[item] = value;
  1280. },
  1281. // 复制json数据
  1282. copyJsonData(data) {
  1283. let textarea = document.createElement("textarea");
  1284. try {
  1285. textarea.value = JSON.stringify(data, null, 2);
  1286. document.body.appendChild(textarea);
  1287. // 3. 选中文本(兼容移动端)
  1288. textarea.select();
  1289. // 兼容移动端:设置选择范围覆盖全部文本
  1290. textarea.setSelectionRange(0, textarea.value.length);
  1291. // 4. 执行复制命令
  1292. const isSuccess = document.execCommand("copy");
  1293. if (isSuccess) {
  1294. this.$message({
  1295. message: "复制成功",
  1296. type: "success",
  1297. });
  1298. }
  1299. } catch (err) {
  1300. this.$message({
  1301. message: err,
  1302. type: "error",
  1303. });
  1304. } finally {
  1305. document.body.removeChild(textarea);
  1306. }
  1307. },
  1308. handleExceed(file) {
  1309. this.$message({
  1310. message: "最多只能上传一个文件",
  1311. type: "warning",
  1312. });
  1313. },
  1314. handleFileChange(file, fileList) {
  1315. this.currentFile = file; // 注意:Element UI Plus 无需 .raw
  1316. },
  1317. uploadRemove() {
  1318. this.currentFile = null;
  1319. },
  1320. handleChange(emit) {
  1321. this.params.unit = "";
  1322. this.currentFile = null;
  1323. // 清除所有地图中的元素
  1324. this.clearAll();
  1325. this.jsonData = {};
  1326. this.backData = {};
  1327. this.renderStatus = {
  1328. input: false,
  1329. output: false,
  1330. };
  1331. this.renderedGeojsonCache = {
  1332. input: null,
  1333. output: null,
  1334. };
  1335. this.currentRenderedSource = "";
  1336. this.propertyDialog.visible = false;
  1337. this.propertyDialog.list = [];
  1338. this.propertyDialog.source = "";
  1339. this.propertyDialog.propertiesRef = null;
  1340. this.propertyDialog.featureIndex = -1;
  1341. this.SceneValue = emit[emit.length - 1];
  1342. },
  1343. // 初始化绘制处理器
  1344. initDrawHandler() {
  1345. // 创建绘制处理器
  1346. if (!this.handler) {
  1347. this.handler = new SkyScenery.ScreenSpaceEventHandler(viewer.canvas);
  1348. }
  1349. this.initFeaturePickHandler();
  1350. },
  1351. // 初始化地图要素拾取处理器(用于属性弹框)
  1352. initFeaturePickHandler() {
  1353. if (this.featurePickHandler || !viewer || !viewer.canvas) {
  1354. return;
  1355. }
  1356. this.featurePickHandler = new SkyScenery.ScreenSpaceEventHandler(viewer.canvas);
  1357. this.featurePickHandler.setInputAction((movement) => {
  1358. // 绘制模式中不触发属性弹框,避免用户操作冲突
  1359. if (this.currentTool || this.isDrawingHole) {
  1360. return;
  1361. }
  1362. const pickedObject = viewer.scene.pick(movement.position);
  1363. if (!SkyScenery.defined(pickedObject) || !SkyScenery.defined(pickedObject.id)) {
  1364. this.propertyDialog.visible = false;
  1365. this.propertyDialog.list = [];
  1366. this.propertyDialog.source = "";
  1367. this.propertyDialog.propertiesRef = null;
  1368. this.propertyDialog.featureIndex = -1;
  1369. return;
  1370. }
  1371. const entity = pickedObject.id;
  1372. const featureIndex =
  1373. entity.__featureRef && typeof entity.__featureRef.featureIndex === "number"
  1374. ? entity.__featureRef.featureIndex
  1375. : -1;
  1376. const propertySource =
  1377. (entity.__featureRef && entity.__featureRef.source) ||
  1378. this.currentRenderedSource ||
  1379. "input";
  1380. const geojsonData =
  1381. this.getSourceGeojsonData(propertySource) ||
  1382. this.renderedGeojsonCache[propertySource];
  1383. let propertiesRef = null;
  1384. if (geojsonData) {
  1385. if (
  1386. entity.__featureRef &&
  1387. typeof entity.__featureRef.featureIndex === "number" &&
  1388. entity.__featureRef.featureIndex >= 0 &&
  1389. geojsonData.features &&
  1390. geojsonData.features[entity.__featureRef.featureIndex]
  1391. ) {
  1392. if (!geojsonData.features[entity.__featureRef.featureIndex].properties) {
  1393. geojsonData.features[entity.__featureRef.featureIndex].properties = {};
  1394. }
  1395. propertiesRef =
  1396. geojsonData.features[entity.__featureRef.featureIndex].properties;
  1397. } else {
  1398. if (!geojsonData.properties) {
  1399. geojsonData.properties = {};
  1400. }
  1401. propertiesRef = geojsonData.properties;
  1402. }
  1403. }
  1404. const propertyList = this.formatFeatureProperties(
  1405. propertiesRef || entity.__featureProperties || {}
  1406. );
  1407. if (
  1408. propertiesRef ||
  1409. (entity.__featureProperties && typeof entity.__featureProperties === "object")
  1410. ) {
  1411. this.propertyDialog.source = propertySource;
  1412. this.propertyDialog.propertiesRef =
  1413. propertiesRef || entity.__featureProperties || {};
  1414. this.propertyDialog.featureIndex = featureIndex;
  1415. this.propertyDialog.list = propertyList;
  1416. this.propertyDialog.visible = true;
  1417. } else {
  1418. this.propertyDialog.visible = false;
  1419. this.propertyDialog.list = [];
  1420. this.propertyDialog.source = "";
  1421. this.propertyDialog.propertiesRef = null;
  1422. this.propertyDialog.featureIndex = -1;
  1423. }
  1424. }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1425. },
  1426. // 激活绘制工具
  1427. activateDraw(type) {
  1428. // 取消镂空绘制模式
  1429. this.isDrawingHole = false;
  1430. // 如果已经是当前激活的工具,则取消激活
  1431. if (this.currentTool === type) {
  1432. this.deactivateDraw();
  1433. return;
  1434. }
  1435. // 先取消之前的绘制模式
  1436. this.deactivateDraw();
  1437. // 设置当前工具
  1438. this.currentTool = type;
  1439. this.drawingMode = type;
  1440. // 重置当前绘制状态
  1441. this.resetDrawingState();
  1442. switch (type) {
  1443. case "point":
  1444. this.drawPoint();
  1445. break;
  1446. case "polyline":
  1447. this.drawPolyline();
  1448. break;
  1449. case "polygon":
  1450. this.drawPolygon();
  1451. break;
  1452. }
  1453. },
  1454. // 重置绘制状态
  1455. resetDrawingState() {
  1456. this.currentPositions = [];
  1457. this.currentEntity = null;
  1458. // 移除临时预览实体
  1459. if (this.tempEntity) {
  1460. viewer.entities.remove(this.tempEntity);
  1461. viewer.scene.requestRender();
  1462. this.tempEntity = null;
  1463. }
  1464. },
  1465. // 取消绘制模式
  1466. deactivateDraw() {
  1467. if (this.handler) {
  1468. // 移除所有事件监听器
  1469. this.handler.removeInputAction(SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1470. this.handler.removeInputAction(SkyScenery.ScreenSpaceEventType.MOUSE_MOVE);
  1471. this.handler.removeInputAction(SkyScenery.ScreenSpaceEventType.RIGHT_CLICK);
  1472. }
  1473. // 重置状态
  1474. this.resetDrawingState();
  1475. this.currentTool = null;
  1476. this.drawingMode = null;
  1477. this.isDrawingHole = false;
  1478. },
  1479. // 绘制点
  1480. drawPoint() {
  1481. const that = this;
  1482. this.handler.setInputAction(function (event) {
  1483. const ray = viewer.camera.getPickRay(event.position);
  1484. const position = viewer.scene.globe.pick(ray, viewer.scene);
  1485. if (position) {
  1486. const cartographic = SkyScenery.Cartographic.fromCartesian(position);
  1487. const longitude = SkyScenery.Math.toDegrees(cartographic.longitude);
  1488. const latitude = SkyScenery.Math.toDegrees(cartographic.latitude);
  1489. // 创建点实体(实时渲染)
  1490. const entity = viewer.entities.add({
  1491. position: position,
  1492. point: {
  1493. show: true,
  1494. color: SkyScenery.Color.RED,
  1495. pixelSize: 10,
  1496. outlineColor: SkyScenery.Color.WHITE,
  1497. outlineWidth: 2,
  1498. },
  1499. });
  1500. viewer.scene.requestRender();
  1501. that.drawnEntities.push(entity);
  1502. // 转换为geometry格式并保存
  1503. const geometry = {
  1504. type: "Point",
  1505. coordinates: [longitude, latitude],
  1506. };
  1507. that.geometries.push(geometry);
  1508. that.changeGeometries();
  1509. console.log("绘制了点:", geometry);
  1510. }
  1511. }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1512. },
  1513. // 绘制线
  1514. drawPolyline() {
  1515. const that = this;
  1516. // 鼠标移动时更新临时线(实时渲染预览)
  1517. this.handler.setInputAction(function (event) {
  1518. if (that.currentPositions.length > 0) {
  1519. const ray = viewer.camera.getPickRay(event.endPosition);
  1520. const endPosition = viewer.scene.globe.pick(ray, viewer.scene);
  1521. if (endPosition) {
  1522. const tempPositions = [...that.currentPositions, endPosition];
  1523. // 更新临时预览实体
  1524. if (!that.tempEntity) {
  1525. that.tempEntity = viewer.entities.add({
  1526. polyline: {
  1527. show: true,
  1528. positions: tempPositions,
  1529. material: SkyScenery.Color.WHITE.withAlpha(0.5),
  1530. width: 3,
  1531. },
  1532. });
  1533. viewer.scene.requestRender();
  1534. } else {
  1535. that.tempEntity.polyline.positions = tempPositions;
  1536. }
  1537. }
  1538. }
  1539. }, SkyScenery.ScreenSpaceEventType.MOUSE_MOVE);
  1540. // 左键点击添加点
  1541. this.handler.setInputAction(function (event) {
  1542. const ray = viewer.camera.getPickRay(event.position);
  1543. const position = viewer.scene.globe.pick(ray, viewer.scene);
  1544. if (position) {
  1545. that.currentPositions.push(position.clone());
  1546. // 如果是第一个点,不需要更新实体
  1547. if (that.currentPositions.length > 1) {
  1548. // 如果已有实体,更新它
  1549. if (that.currentEntity) {
  1550. viewer.entities.remove(that.currentEntity);
  1551. }
  1552. // 创建当前实体(实时渲染)
  1553. that.currentEntity = viewer.entities.add({
  1554. polyline: {
  1555. show: true,
  1556. positions: [...that.currentPositions],
  1557. material: SkyScenery.Color.RED,
  1558. width: 3,
  1559. },
  1560. });
  1561. viewer.scene.requestRender();
  1562. }
  1563. }
  1564. }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1565. // 右键点击完成绘制
  1566. this.handler.setInputAction(function () {
  1567. if (that.currentPositions.length > 1) {
  1568. // 移除临时预览实体
  1569. if (that.tempEntity) {
  1570. viewer.entities.remove(that.tempEntity);
  1571. that.tempEntity = null;
  1572. }
  1573. // 否则创建线
  1574. // 确保当前实体存在
  1575. if (!that.currentEntity) {
  1576. that.currentEntity = viewer.entities.add({
  1577. polyline: {
  1578. show: true,
  1579. positions: [...that.currentPositions],
  1580. material: SkyScenery.Color.RED,
  1581. width: 3,
  1582. },
  1583. });
  1584. }
  1585. that.drawnEntities.push(that.currentEntity);
  1586. viewer.scene.requestRender();
  1587. // 转换为geometry格式并保存
  1588. const coordinates = [];
  1589. that.currentPositions.forEach((pos) => {
  1590. const cartographic = SkyScenery.Cartographic.fromCartesian(pos);
  1591. coordinates.push([
  1592. SkyScenery.Math.toDegrees(cartographic.longitude),
  1593. SkyScenery.Math.toDegrees(cartographic.latitude),
  1594. ]);
  1595. });
  1596. const geometry = {
  1597. type: "LineString",
  1598. coordinates: coordinates,
  1599. };
  1600. that.geometries.push(geometry);
  1601. that.changeGeometries();
  1602. console.log("绘制了线:", geometry);
  1603. // 取消当前绘制模式
  1604. that.deactivateDraw();
  1605. }
  1606. }, SkyScenery.ScreenSpaceEventType.RIGHT_CLICK);
  1607. },
  1608. // 绘制面
  1609. drawPolygon() {
  1610. const that = this;
  1611. // 鼠标移动时更新临时面(实时渲染预览)
  1612. this.handler.setInputAction(function (event) {
  1613. if (that.currentPositions.length > 0) {
  1614. const ray = viewer.camera.getPickRay(event.endPosition);
  1615. const endPosition = viewer.scene.globe.pick(ray, viewer.scene);
  1616. if (endPosition) {
  1617. // 为了预览效果,临时闭合多边形
  1618. const tempPositions = [
  1619. ...that.currentPositions,
  1620. endPosition,
  1621. that.currentPositions[0],
  1622. ];
  1623. // 更新临时预览实体
  1624. if (!that.tempEntity) {
  1625. that.tempEntity = viewer.entities.add({
  1626. polygon: {
  1627. show: true,
  1628. hierarchy: new SkyScenery.PolygonHierarchy(tempPositions),
  1629. material: SkyScenery.Color.RED.withAlpha(0.3),
  1630. outline: true,
  1631. outlineColor: SkyScenery.Color.RED.withAlpha(0.8),
  1632. outlineWidth: 2,
  1633. },
  1634. });
  1635. viewer.scene.requestRender();
  1636. } else {
  1637. that.tempEntity.polygon.hierarchy = new SkyScenery.PolygonHierarchy(
  1638. tempPositions
  1639. );
  1640. }
  1641. }
  1642. }
  1643. }, SkyScenery.ScreenSpaceEventType.MOUSE_MOVE);
  1644. // 左键点击添加点
  1645. this.handler.setInputAction(function (event) {
  1646. const ray = viewer.camera.getPickRay(event.position);
  1647. const position = viewer.scene.globe.pick(ray, viewer.scene);
  1648. if (position) {
  1649. // 检查是否点击了第一个点附近(自动闭合)
  1650. if (
  1651. that.currentPositions.length > 2 &&
  1652. that.isPositionNearFirst(position, that.currentPositions[0])
  1653. ) {
  1654. // 完成多边形绘制
  1655. that.finalizePolygonDrawing();
  1656. that.deactivateDraw();
  1657. return;
  1658. }
  1659. that.currentPositions.push(position.clone());
  1660. // 如果有足够的点,更新多边形实体(实时渲染)
  1661. if (that.currentPositions.length > 2) {
  1662. // 闭合多边形
  1663. const closedPositions = [...that.currentPositions, that.currentPositions[0]];
  1664. // 如果已有实体,更新它
  1665. if (that.currentEntity) {
  1666. viewer.entities.remove(that.currentEntity);
  1667. }
  1668. // 创建当前实体
  1669. that.currentEntity = viewer.entities.add({
  1670. polygon: {
  1671. show: true,
  1672. hierarchy: new SkyScenery.PolygonHierarchy(closedPositions),
  1673. material: SkyScenery.Color.RED.withAlpha(0.5),
  1674. outline: true,
  1675. outlineColor: SkyScenery.Color.BLUE,
  1676. outlineWidth: 2,
  1677. },
  1678. });
  1679. viewer.scene.requestRender();
  1680. }
  1681. }
  1682. }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1683. // 右键点击完成绘制
  1684. this.handler.setInputAction(function () {
  1685. if (that.currentPositions.length > 2) {
  1686. that.finalizePolygonDrawing();
  1687. // 取消当前绘制模式
  1688. that.deactivateDraw();
  1689. }
  1690. }, SkyScenery.ScreenSpaceEventType.RIGHT_CLICK);
  1691. },
  1692. // 完成多边形绘制
  1693. finalizePolygonDrawing() {
  1694. // 移除临时预览实体
  1695. if (this.tempEntity) {
  1696. viewer.entities.remove(this.tempEntity);
  1697. this.tempEntity = null;
  1698. }
  1699. // 确保多边形是闭合的
  1700. let closedPositions = [...this.currentPositions];
  1701. // 创建最终的多边形实体
  1702. if (this.currentEntity) {
  1703. viewer.entities.remove(this.currentEntity);
  1704. }
  1705. this.currentEntity = viewer.entities.add({
  1706. polygon: {
  1707. show: true,
  1708. hierarchy: new SkyScenery.PolygonHierarchy(closedPositions),
  1709. material: SkyScenery.Color.RED.withAlpha(0.5),
  1710. outline: true,
  1711. outlineColor: SkyScenery.Color.BLUE,
  1712. outlineWidth: 2,
  1713. },
  1714. });
  1715. viewer.scene.requestRender();
  1716. this.drawnEntities.push(this.currentEntity);
  1717. // 转换为geometry格式并保存(不包含重复的最后一个点)
  1718. const coordinates = [];
  1719. const mainRing = [];
  1720. for (let i = 0; i < closedPositions.length; i++) {
  1721. const cartographic = SkyScenery.Cartographic.fromCartesian(closedPositions[i]);
  1722. mainRing.push([
  1723. SkyScenery.Math.toDegrees(cartographic.longitude),
  1724. SkyScenery.Math.toDegrees(cartographic.latitude),
  1725. ]);
  1726. }
  1727. const cartographic = SkyScenery.Cartographic.fromCartesian(closedPositions[0]);
  1728. mainRing.push([
  1729. SkyScenery.Math.toDegrees(cartographic.longitude),
  1730. SkyScenery.Math.toDegrees(cartographic.latitude),
  1731. ]);
  1732. coordinates.push(mainRing);
  1733. const geometry = {
  1734. type: "Polygon",
  1735. coordinates: coordinates, // GeoJSON格式
  1736. holes: [], // 用于存储镂空区域
  1737. };
  1738. // 保存实体和几何对象的关联
  1739. this.currentEntity.geometryRef = geometry;
  1740. this.geometries.push(geometry);
  1741. this.changeGeometries();
  1742. console.log("绘制了面:", geometry);
  1743. },
  1744. // 开始绘制镂空
  1745. startHoleDrawing() {
  1746. // 如果已经在绘制镂空,则取消
  1747. if (this.isDrawingHole) {
  1748. this.isDrawingHole = false;
  1749. this.deactivateDraw();
  1750. return;
  1751. }
  1752. // 取消其他绘制模式
  1753. this.deactivateDraw();
  1754. // 设置为镂空绘制模式
  1755. this.isDrawingHole = true;
  1756. console.log("请点击一个多边形以添加镂空");
  1757. // 设置点击事件选择多边形
  1758. const that = this;
  1759. this.handler.setInputAction(function (event) {
  1760. // 拾取实体
  1761. const pickedObject = viewer.scene.pick(event.position);
  1762. if (pickedObject && pickedObject.id && pickedObject.id.polygon) {
  1763. const polygonEntity = pickedObject.id;
  1764. // 检查是否有对应的几何对象
  1765. if (polygonEntity.geometryRef && polygonEntity.geometryRef.type === "Polygon") {
  1766. that.currentPolygonEntity = polygonEntity;
  1767. that.currentPolygonGeometry = polygonEntity.geometryRef;
  1768. console.log("已选择多边形,现在绘制镂空区域");
  1769. // 开始绘制镂空区域(使用多边形绘制逻辑,但最后添加为镂空)
  1770. that.resetDrawingState();
  1771. that.drawHole();
  1772. } else {
  1773. console.log("选择的不是有效的多边形");
  1774. }
  1775. } else {
  1776. console.log("未选中任何多边形");
  1777. }
  1778. }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1779. },
  1780. // 绘制镂空区域
  1781. drawHole() {
  1782. const that = this;
  1783. // 清除之前的事件
  1784. this.handler.removeInputAction(SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1785. // 鼠标移动时更新临时镂空区域
  1786. this.handler.setInputAction(function (event) {
  1787. if (that.currentPositions.length > 0) {
  1788. const ray = viewer.camera.getPickRay(event.endPosition);
  1789. const endPosition = viewer.scene.globe.pick(ray, viewer.scene);
  1790. if (endPosition) {
  1791. // 为了预览效果,临时闭合多边形
  1792. const tempPositions = [
  1793. ...that.currentPositions,
  1794. endPosition,
  1795. that.currentPositions[0],
  1796. ];
  1797. // 更新临时预览实体
  1798. if (!that.tempEntity) {
  1799. that.tempEntity = viewer.entities.add({
  1800. polygon: {
  1801. show: true,
  1802. hierarchy: new SkyScenery.PolygonHierarchy(tempPositions),
  1803. material: new SkyScenery.ColorMaterialProperty(
  1804. new SkyScenery.Color(1, 0, 0, 0.3)
  1805. ),
  1806. outline: true,
  1807. outlineColor: SkyScenery.Color.RED,
  1808. outlineWidth: 2,
  1809. },
  1810. });
  1811. viewer.scene.requestRender();
  1812. } else {
  1813. that.tempEntity.polygon.hierarchy = new SkyScenery.PolygonHierarchy(
  1814. tempPositions
  1815. );
  1816. }
  1817. }
  1818. }
  1819. }, SkyScenery.ScreenSpaceEventType.MOUSE_MOVE);
  1820. // 左键点击添加点
  1821. this.handler.setInputAction(function (event) {
  1822. const ray = viewer.camera.getPickRay(event.position);
  1823. const position = viewer.scene.globe.pick(ray, viewer.scene);
  1824. if (position) {
  1825. // 检查是否点击了第一个点附近(自动闭合)
  1826. if (
  1827. that.currentPositions.length > 2 &&
  1828. that.isPositionNearFirst(position, that.currentPositions[0])
  1829. ) {
  1830. // 完成镂空绘制
  1831. that.finalizeHoleDrawing();
  1832. return;
  1833. }
  1834. that.currentPositions.push(position.clone());
  1835. }
  1836. }, SkyScenery.ScreenSpaceEventType.LEFT_CLICK);
  1837. // 右键点击完成绘制
  1838. this.handler.setInputAction(function () {
  1839. if (that.currentPositions.length > 2) {
  1840. that.finalizeHoleDrawing();
  1841. }
  1842. }, SkyScenery.ScreenSpaceEventType.RIGHT_CLICK);
  1843. },
  1844. // 完成镂空绘制
  1845. finalizeHoleDrawing() {
  1846. // 移除临时预览实体
  1847. if (this.tempEntity) {
  1848. viewer.entities.remove(this.tempEntity);
  1849. viewer.scene.requestRender();
  1850. this.tempEntity = null;
  1851. }
  1852. // 确保镂空区域是闭合的
  1853. let closedPositions = [...this.currentPositions];
  1854. // 转换为几何坐标
  1855. const holeCoordinates = [];
  1856. for (let i = 0; i < closedPositions.length; i++) {
  1857. const cartographic = SkyScenery.Cartographic.fromCartesian(closedPositions[i]);
  1858. holeCoordinates.push([
  1859. SkyScenery.Math.toDegrees(cartographic.longitude),
  1860. SkyScenery.Math.toDegrees(cartographic.latitude),
  1861. ]);
  1862. }
  1863. const cartographic = SkyScenery.Cartographic.fromCartesian(closedPositions[0]);
  1864. holeCoordinates.push([
  1865. SkyScenery.Math.toDegrees(cartographic.longitude),
  1866. SkyScenery.Math.toDegrees(cartographic.latitude),
  1867. ]);
  1868. // 添加到几何对象的镂空数组
  1869. if (!this.currentPolygonGeometry.holes) {
  1870. this.currentPolygonGeometry.holes = [];
  1871. }
  1872. this.currentPolygonGeometry.holes.push(holeCoordinates);
  1873. // 更新多边形的层级结构以包含镂空
  1874. const polygonHierarchy = this.buildPolygonHierarchyWithHoles(
  1875. this.currentPolygonGeometry.coordinates[0],
  1876. this.currentPolygonGeometry.holes
  1877. );
  1878. // 更新实体显示
  1879. this.currentPolygonEntity.polygon.hierarchy = polygonHierarchy;
  1880. console.log("添加了镂空区域:", holeCoordinates);
  1881. console.log("更新后的多边形几何:", this.currentPolygonGeometry);
  1882. // 重置状态
  1883. this.resetDrawingState();
  1884. this.isDrawingHole = false;
  1885. this.currentPolygonEntity = null;
  1886. this.currentPolygonGeometry = null;
  1887. // 取消绘制模式
  1888. this.deactivateDraw();
  1889. },
  1890. // 构建包含镂空的多边形层级结构
  1891. buildPolygonHierarchyWithHoles(outerRing, holes) {
  1892. // 将外部环转换为Cartesian3数组
  1893. const outerRingPositions = [];
  1894. outerRing.forEach((coord) => {
  1895. const cartesian = SkyScenery.Cartesian3.fromDegrees(coord[0], coord[1]);
  1896. outerRingPositions.push(cartesian);
  1897. });
  1898. // 闭合外部环
  1899. outerRingPositions.push(outerRingPositions[0]);
  1900. // 创建层级结构
  1901. const hierarchy = new SkyScenery.PolygonHierarchy(outerRingPositions);
  1902. // 添加镂空
  1903. if (holes && holes.length > 0) {
  1904. hierarchy.holes = holes.map((hole) => {
  1905. const holePositions = [];
  1906. hole.forEach((coord) => {
  1907. const cartesian = SkyScenery.Cartesian3.fromDegrees(coord[0], coord[1]);
  1908. holePositions.push(cartesian);
  1909. });
  1910. // 闭合镂空环
  1911. holePositions.push(holePositions[0]);
  1912. return new SkyScenery.PolygonHierarchy(holePositions);
  1913. });
  1914. }
  1915. return hierarchy;
  1916. },
  1917. // 检查点是否靠近第一个点
  1918. isPositionNearFirst(position, firstPosition, tolerance = 10) {
  1919. // 单位:米
  1920. const distance = SkyScenery.Cartesian3.distance(position, firstPosition);
  1921. return distance < tolerance;
  1922. },
  1923. changeGeometries() {
  1924. let requestData = {};
  1925. const geometriesToSend = this.geometries;
  1926. let FeatureCollectionFeatures = [];
  1927. geometriesToSend.forEach((item) => {
  1928. FeatureCollectionFeatures.push({
  1929. type: "Feature",
  1930. properties: {},
  1931. geometry: {
  1932. type: item.type,
  1933. coordinates: item.coordinates,
  1934. },
  1935. });
  1936. });
  1937. // 构造请求数据
  1938. requestData = {
  1939. type: "FeatureCollection",
  1940. features: FeatureCollectionFeatures,
  1941. timestamp: new Date().getTime(),
  1942. };
  1943. if (
  1944. this.SceneValue &&
  1945. this.dmsServerItem &&
  1946. this.dmsServerItem.elementTypes &&
  1947. this.ifHasType("unit") &&
  1948. this.params.unit
  1949. ) {
  1950. requestData.unit = this.params.unit;
  1951. }
  1952. if (
  1953. this.SceneValue &&
  1954. this.dmsServerItem &&
  1955. this.dmsServerItem.elementTypes &&
  1956. this.ifHasType("EPSILON") &&
  1957. this.params.EPSILON
  1958. ) {
  1959. requestData.EPSILON = this.params.EPSILON;
  1960. }
  1961. if (
  1962. this.SceneValue &&
  1963. this.dmsServerItem &&
  1964. this.dmsServerItem.elementTypes &&
  1965. this.ifHasType("inPrj") &&
  1966. this.params.inPrj
  1967. ) {
  1968. requestData.inPrj = this.params.inPrj;
  1969. }
  1970. if (
  1971. this.SceneValue &&
  1972. this.dmsServerItem &&
  1973. this.dmsServerItem.elementTypes &&
  1974. this.ifHasType("compressionRatio") &&
  1975. this.params.compressionRatio
  1976. ) {
  1977. requestData.compressionRatio = this.params.compressionRatio;
  1978. }
  1979. if (
  1980. this.SceneValue &&
  1981. this.dmsServerItem &&
  1982. this.dmsServerItem.elementTypes &&
  1983. this.ifHasType("outPrj") &&
  1984. this.params.outPrj
  1985. ) {
  1986. requestData.outPrj = this.params.outPrj;
  1987. }
  1988. if (
  1989. this.SceneValue &&
  1990. this.dmsServerItem &&
  1991. this.dmsServerItem.elementTypes &&
  1992. this.ifHasType("distance") &&
  1993. this.params.distance
  1994. ) {
  1995. requestData.distance = this.params.distance;
  1996. }
  1997. this.jsonData = requestData;
  1998. },
  1999. // 发送几何数据到后台接口
  2000. sendGeometriesToBackend() {
  2001. // 这里使用axios发送请求,需要确保项目中已安装并导入axios
  2002. let requestData;
  2003. this.backData = {};
  2004. let requestUrl = this.dmsServerItem.apiUrl;
  2005. if (
  2006. this.ifHasType("point") ||
  2007. this.ifHasType("polyline") ||
  2008. this.ifHasType("polygon")
  2009. ) {
  2010. requestData = this.jsonData;
  2011. } else {
  2012. requestData = new FormData();
  2013. this.dmsServerItem.elementTypes.forEach((key) => {
  2014. if (key == "file") {
  2015. if (!this.currentFile) {
  2016. return this.$message({
  2017. message: "请选择文件",
  2018. type: "error",
  2019. });
  2020. }
  2021. requestData.append("file", this.currentFile.raw);
  2022. } else {
  2023. requestData.append(key, this.params[key]);
  2024. }
  2025. });
  2026. }
  2027. let that = this;
  2028. // 实际项目中使用以下代码发送请求
  2029. wgn
  2030. .topology(requestUrl, requestData)
  2031. .then((res) => {
  2032. if (requestUrl.indexOf("downloadFile") == -1) {
  2033. that.backData = res;
  2034. if (res.code && res.code == 200) {
  2035. that.$message({
  2036. message: res.message,
  2037. type: "success",
  2038. });
  2039. } else {
  2040. that.$message({
  2041. message: res.content,
  2042. type: "error",
  2043. });
  2044. }
  2045. } else {
  2046. const blob = res; // 响应体是 Blob 类型
  2047. if (!blob) {
  2048. that.$message.error("下载失败:文件流为空");
  2049. reject("文件流为空");
  2050. return;
  2051. }
  2052. let _downloadFile = that.params.filePath + "";
  2053. let fileName = _downloadFile.substring(
  2054. _downloadFile.indexOf("down_files") + 11
  2055. );
  2056. // D:\work\backCode\one_map_portal_server\down_files\0378b1c2e92a4c36ab447f552c0c7888.xlsx
  2057. // 替换原代码中创建 url 的逻辑,先加校验
  2058. console.log("blob 类型:", Object.prototype.toString.call(blob)); // 正常应输出 [object Blob]
  2059. console.log("blob 大小:", blob.size); // 正常应大于 0
  2060. if (!(blob instanceof Blob) || blob.size === 0) {
  2061. this.$message.error("文件流解析失败,无法生成下载链接");
  2062. return;
  2063. }
  2064. const url = window.URL.createObjectURL(blob); // 将 Blob 转为临时 URL
  2065. const link = document.createElement("a");
  2066. link.href = url;
  2067. link.download = fileName; // 设置下载文件名
  2068. link.style.display = "none";
  2069. document.body.appendChild(link);
  2070. link.click(); // 触发点击下载
  2071. document.body.removeChild(link);
  2072. window.URL.revokeObjectURL(url); // 销毁临时 URL,避免内存泄漏
  2073. }
  2074. })
  2075. .catch((error) => {
  2076. console.error("后台接口调用失败:", error);
  2077. });
  2078. },
  2079. // 清除所有绘制的元素,并更新geometries
  2080. clearAll() {
  2081. this.clearAllMap();
  2082. this.changeGeometries();
  2083. },
  2084. // 清除所有绘制的元素
  2085. clearAllMap() {
  2086. // 移除所有实体
  2087. this.drawnEntities.forEach((entity) => {
  2088. viewer.entities.remove(entity);
  2089. });
  2090. viewer.scene.requestRender();
  2091. // 清空数组
  2092. this.drawnEntities = [];
  2093. this.geometries = [];
  2094. this.currentRenderedSource = "";
  2095. this.propertyDialog.visible = false;
  2096. this.propertyDialog.list = [];
  2097. this.propertyDialog.source = "";
  2098. this.propertyDialog.propertiesRef = null;
  2099. this.propertyDialog.featureIndex = -1;
  2100. // 取消当前绘制模式
  2101. this.deactivateDraw();
  2102. console.log("已清除所有绘制的元素");
  2103. },
  2104. },
  2105. };
  2106. </script>
  2107. <style lang="less" scoped>
  2108. // 操作栏样式
  2109. #controlPanelBox {
  2110. width: calc(30vw - 2rem);
  2111. min-width: 500px;
  2112. height: calc(100vh - 2rem);
  2113. display: flex;
  2114. flex-direction: column;
  2115. padding: 1rem;
  2116. position: fixed;
  2117. top: 0px;
  2118. right: 0;
  2119. background: #08224a;
  2120. }
  2121. // 绘制按钮区域
  2122. .toolbar {
  2123. position: absolute;
  2124. top: 10px;
  2125. left: -120px;
  2126. z-index: 1000;
  2127. background: #08224a;
  2128. border-radius: 8px;
  2129. padding: 3px 5px;
  2130. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
  2131. display: flex;
  2132. flex-direction: column;
  2133. gap: 8px;
  2134. }
  2135. .tool-item {
  2136. padding: 10px 20px;
  2137. cursor: pointer;
  2138. border-radius: 4px;
  2139. transition: all 0.3s ease;
  2140. user-select: none;
  2141. font-size: 14px;
  2142. }
  2143. .tool-item:hover {
  2144. background: #ffffff32;
  2145. }
  2146. .tool-item.active {
  2147. background: #409eff;
  2148. color: white;
  2149. }
  2150. .sceneNameBox {
  2151. display: flex;
  2152. align-items: center;
  2153. justify-content: space-between;
  2154. }
  2155. .vueJsonEditor_box {
  2156. width: 100%;
  2157. height: 100%;
  2158. position: relative;
  2159. }
  2160. .vueJsonEditor_tools {
  2161. position: absolute;
  2162. top: 8px;
  2163. right: 10px;
  2164. font-size: 14px;
  2165. z-index: 1;
  2166. color: #fff;
  2167. }
  2168. .vueJsonEditor_tools span {
  2169. padding: 2px 5px;
  2170. opacity: 0.8;
  2171. cursor: pointer;
  2172. border: 1px solid #ffffff00;
  2173. border-radius: 2px;
  2174. margin-right: 5px;
  2175. &:hover {
  2176. background-color: rgba(255, 255, 255, 0.2);
  2177. border-color: rgba(255, 255, 255, 0.4);
  2178. }
  2179. }
  2180. .feature-property-content {
  2181. max-height: 360px;
  2182. overflow: auto;
  2183. padding-right: 4px;
  2184. }
  2185. .feature-property-tip {
  2186. color: #9ed2ff;
  2187. margin-bottom: 12px;
  2188. font-size: 13px;
  2189. }
  2190. .feature-property-item {
  2191. padding: 10px 12px;
  2192. margin-bottom: 8px;
  2193. border-radius: 8px;
  2194. background: rgba(61, 132, 205, 0.14);
  2195. border: 1px solid rgba(111, 186, 255, 0.35);
  2196. }
  2197. .feature-property-row {
  2198. display: flex;
  2199. align-items: center;
  2200. gap: 8px;
  2201. margin-bottom: 8px;
  2202. }
  2203. .feature-property-delete-btn {
  2204. flex-shrink: 0;
  2205. }
  2206. .feature-property-key-input {
  2207. width: 180px;
  2208. }
  2209. :deep(.feature-property-input .el-input__wrapper) {
  2210. background: rgba(8, 34, 74, 0.68);
  2211. box-shadow: inset 0 0 0 1px rgba(130, 198, 255, 0.4);
  2212. }
  2213. :deep(.feature-property-input .el-input__inner) {
  2214. color: #ffffff;
  2215. }
  2216. :deep(.feature-property-dialog .el-dialog) {
  2217. background: rgba(7, 24, 48, 0.95);
  2218. border: 1px solid rgba(111, 186, 255, 0.5);
  2219. }
  2220. :deep(.feature-property-dialog .el-dialog__title) {
  2221. color: #e8f4ff;
  2222. font-weight: 700;
  2223. }
  2224. :deep(.feature-property-dialog .el-dialog__headerbtn .el-dialog__close) {
  2225. color: #d8ecff;
  2226. }
  2227. :deep(.feature-property-dialog .el-dialog__body) {
  2228. padding-top: 12px;
  2229. }
  2230. :deep(.ace_editor) {
  2231. height: 600px !important;
  2232. max-height: calc(100vh - 300px) !important;
  2233. }
  2234. :deep(.jsoneditor-modes),
  2235. :deep(.jsoneditor-poweredBy) {
  2236. display: none;
  2237. }
  2238. </style>