男女做爽爽爽网站-男女做羞羞高清-男女做爰高清无遮挡免费视频-男女做爰猛烈-男女做爰猛烈吃奶啪啪喷水网站-内射白浆一区

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

制作數(shù)字農(nóng)場(chǎng)3D可視化大屏

admin
2025年4月11日 15:57 本文熱度 178

1.介紹

數(shù)字農(nóng)業(yè)可視化是一種將農(nóng)業(yè)生產(chǎn)過(guò)程中的各類數(shù)據(jù),通過(guò)先進(jìn)的信息技術(shù)手段進(jìn)行采集、整合、分析,并以直觀的可視化形式呈現(xiàn)出來(lái)的技術(shù)應(yīng)用模式。它利用大數(shù)據(jù)、物聯(lián)網(wǎng)、人工智能、GIS等技術(shù),為農(nóng)業(yè)生產(chǎn)經(jīng)營(yíng)管理提供了全新的、高效的決策支持工具,使農(nóng)業(yè)從業(yè)者能夠更加清晰、準(zhǔn)確地了解農(nóng)業(yè)生產(chǎn)的各個(gè)環(huán)節(jié),從而實(shí)現(xiàn)精準(zhǔn)決策、精細(xì)管理和高效運(yùn)營(yíng)。

最近對(duì)數(shù)字農(nóng)業(yè)有點(diǎn)感興趣,于是就有了接下來(lái)的探索和嘗試,本文的內(nèi)容比較有綜合性,基本上用到了之前在技術(shù)社區(qū)分享的大部分經(jīng)驗(yàn),不僅包括高德開發(fā)平臺(tái)的技術(shù),也集成了具體業(yè)務(wù)分析、GIS數(shù)據(jù)生成、3D模型制作等內(nèi)容。附演示頁(yè)面地址,源代碼地址見(jiàn)文末。

2. 需求分析

本次做可視化大屏的開發(fā),我希望最終的開發(fā)成果是可以在后續(xù)的產(chǎn)品或者項(xiàng)目中復(fù)用、至少能發(fā)揮一定的參考價(jià)值,因此需要做一些業(yè)務(wù)需求分析。由于我在這方面的業(yè)務(wù)涉獵比較淺顯,于是先看了幾個(gè)智慧農(nóng)業(yè)解決方案方便的PPT,然后詢問(wèn)AI助手,整理為下面幾個(gè)專題的內(nèi)容:

2.1 基礎(chǔ)配套

  1. 地形:以三維地形圖的形式呈現(xiàn),通過(guò)不同顏色和高度標(biāo)識(shí)展示區(qū)域內(nèi)的山地、沼澤、平原等地形分布。可以使用等高線、陰影等效果增強(qiáng)立體感,讓用戶直觀了解地形的起伏。由于增加地形起伏會(huì)直接增加其他貼合地形圖層的實(shí)現(xiàn)復(fù)雜度,為降低閱讀難度本次示例選了塊地形相對(duì)平整的沖擊平原,因此規(guī)避地形問(wèn)題。
  2. 影像:展示高分辨率的衛(wèi)星影像圖,全面覆蓋智慧農(nóng)業(yè)所涉及的區(qū)域范圍,讓用戶能夠以宏觀視角清晰了解整個(gè)區(qū)域的全貌,包括地形、河流、村居、植被等基礎(chǔ)配套元素的分布及相互關(guān)系。
  3. 水域:在地圖上清晰標(biāo)注河流的走向、河道寬度以及與其他水體的連接關(guān)系
  4. 水質(zhì):如酸堿度、溶解氧、污染物含量等指標(biāo),并以不同顏色或圖表形式在大屏上直觀展示,以保障農(nóng)業(yè)用水安全。
  5. 村居建筑:展示村莊的分布位置和范圍,以建筑模型或圖標(biāo)形式呈現(xiàn)村居的布局。

2.2 農(nóng)業(yè)生產(chǎn)

  1. 農(nóng)田:以高精度地圖展示農(nóng)田地塊的邊界和面積,對(duì)不同的農(nóng)田進(jìn)行編號(hào)和分類管理,例如按照種植作物類型、當(dāng)前使用狀態(tài)等進(jìn)行劃分
  2. 魚塘:標(biāo)注魚塘的位置和范圍,顯示魚塘的面積和水深等基本信息。展示魚塘的養(yǎng)殖情況,包括養(yǎng)殖的魚類品種、生長(zhǎng)階段、投喂記錄等,方便養(yǎng)殖戶進(jìn)行科學(xué)管理和養(yǎng)殖計(jì)劃制定。
  3. 作物識(shí)別:利用圖像識(shí)別技術(shù),通過(guò)攝像頭或衛(wèi)星影像對(duì)農(nóng)田中的作物進(jìn)行實(shí)時(shí)識(shí)別和分類。在大屏上以不同顏色或圖標(biāo)標(biāo)注出不同作物的種植區(qū)域,方便用戶快速了解農(nóng)田的作物布局
  4. 災(zāi)害預(yù)測(cè):通過(guò)監(jiān)測(cè)田間的病蟲害發(fā)生情況、氣象條件、作物生長(zhǎng)狀況等因素,運(yùn)用病蟲害預(yù)測(cè)模型,預(yù)測(cè)病蟲害的發(fā)生趨勢(shì)和流行范圍。

2.3 安全監(jiān)管

  1. 無(wú)人機(jī)巡查:在地圖上展示無(wú)人機(jī)的巡查路線和實(shí)時(shí)位置,用戶可以直觀地看到無(wú)人機(jī)的飛行軌跡。
  2. 入侵告警:在地圖上劃定重點(diǎn)安全區(qū)域,如農(nóng)田保護(hù)區(qū)、魚塘養(yǎng)殖區(qū)、倉(cāng)庫(kù)等,當(dāng)有人員或車輛未經(jīng)授權(quán)進(jìn)入這些區(qū)域時(shí),系統(tǒng)自動(dòng)觸發(fā)入侵告警。
  3. 重點(diǎn)位置POI:在地圖上標(biāo)注所有攝像頭的位置,形成 POI(Point of Interest)圖層。用戶可以點(diǎn)擊每個(gè)攝像頭圖標(biāo),查看該攝像頭的實(shí)時(shí)監(jiān)控畫面和相關(guān)信息,如攝像頭編號(hào)、安裝位置、監(jiān)控范圍等。

2.4 經(jīng)濟(jì)效益

  1. 區(qū)塊產(chǎn)量預(yù)測(cè):對(duì)比不同年份或不同種植季節(jié)的產(chǎn)量預(yù)測(cè)數(shù)據(jù),分析產(chǎn)量變化趨勢(shì)和影響因素,為農(nóng)業(yè)生產(chǎn)規(guī)劃和資源配置提供決策依據(jù)
  2. 投入產(chǎn)出比分析:詳細(xì)展示農(nóng)業(yè)生產(chǎn)過(guò)程中的各項(xiàng)投入成本,包括土地租賃費(fèi)用、農(nóng)資采購(gòu)成本、人工成本、水電費(fèi)、運(yùn)輸費(fèi)用等,并以圖表形式呈現(xiàn)各項(xiàng)成本在總成本中的占比情況,幫助用戶清晰了解成本結(jié)構(gòu)。

3. 技術(shù)分析

經(jīng)過(guò)上面的業(yè)務(wù)需求分析,我們就可以開始將它們轉(zhuǎn)為技術(shù)上的需求模塊進(jìn)行逐個(gè)實(shí)現(xiàn),其中部分圖層可視化效果,使用高德平臺(tái)提供的可視化類Loca可以滿足了,其他部分圖層則需要自行開發(fā),這里我將自己平時(shí)積累的可視化圖層整理為的gl-layers圖層庫(kù),核心代碼是基于three JS和高德自定義圖層類CustomLayer、GLCustomLayer進(jìn)行開發(fā)。

3.1 技術(shù)棧說(shuō)明

工具名稱版本用途
高德地圖 JSAPI2.0為GIS平臺(tái)提供基礎(chǔ)底圖和服務(wù)
three.js0.157主流webGL引擎之一,負(fù)責(zé)實(shí)現(xiàn)展示層面的功能
QGIS3.32.3GIS數(shù)據(jù)處理工具,用于處理本文的矢量化數(shù)據(jù)
cesiumlab3.1.11三維數(shù)據(jù)處理工具集,用于將模型轉(zhuǎn)換為互聯(lián)網(wǎng)可用的3DTiles
blender3.6模型處理工具,用于對(duì)BIM模型進(jìn)行最簡(jiǎn)單的預(yù)處理
CityEngine2023.0arcGIS團(tuán)隊(duì)開發(fā)的程序化 3D 城市生成器 ,支持通過(guò)腳本將GIS轉(zhuǎn)換為3D模型
vue3.2.25實(shí)現(xiàn)可視化大屏UI的語(yǔ)言框架,特點(diǎn)是數(shù)據(jù)雙向綁定
vite2.9.15便捷的前端工程構(gòu)建工具
AI Earth
達(dá)摩學(xué)院提供的AIE-SEM影像識(shí)別、分割、提取服務(wù),可以幫忙我們從遙感影像圖片中提取GIS數(shù)據(jù)

3.2 圖層說(shuō)明

專題內(nèi)容GIS數(shù)據(jù)類型表現(xiàn)形式代碼層
基礎(chǔ)配套衛(wèi)星影像底圖圖片瓦片地圖AMap.TileLayer
基礎(chǔ)配套村居建筑polygon三維建筑模型GlLayer.TilesLayer
基礎(chǔ)配套綠化區(qū)域point實(shí)例模型GlLayer.TilesLayer
基礎(chǔ)配套水域polygon水面多邊形GlLayer.WaterLayer
農(nóng)業(yè)生產(chǎn)農(nóng)田地塊polygon帶紋理多邊形,可區(qū)分當(dāng)前使用狀態(tài)GlLayer.PolygonLayer
農(nóng)業(yè)生產(chǎn)魚塘地塊polygon帶紋理多邊形,可區(qū)分當(dāng)前水體狀態(tài)GlLayer.PolygonLayer
農(nóng)業(yè)生產(chǎn)農(nóng)作物識(shí)別結(jié)果point作物類型點(diǎn)圖標(biāo)AMap.MassMarker
農(nóng)業(yè)生產(chǎn)農(nóng)田災(zāi)害風(fēng)險(xiǎn)AI預(yù)測(cè)圖point熱力圖Loca.HeatMapLayer
安全監(jiān)管區(qū)域邊界polyline三維發(fā)光墻面體,如果有監(jiān)控目標(biāo)進(jìn)入?yún)^(qū)域內(nèi)則會(huì)出現(xiàn)告警GlLayer.BorderLayer
安全監(jiān)管無(wú)人機(jī)導(dǎo)航polyline無(wú)人機(jī)模型在空中飛行移動(dòng)GlLayer.DrivingLayer
安全監(jiān)管巡查路線polyline無(wú)人機(jī)移動(dòng)軌跡GlLayer.FlowlineLayer
安全監(jiān)管示范區(qū)服務(wù)點(diǎn)point帶名稱點(diǎn)標(biāo)記,點(diǎn)擊可切換到專屬視角Loca.LabelsLayer
經(jīng)濟(jì)效益產(chǎn)量AI預(yù)測(cè)圖層point網(wǎng)格蜂窩柱狀圖,產(chǎn)量越大柱狀越紅且越高Loca.HexagonLayer

4. 實(shí)現(xiàn)步驟

4.1 主體框架開發(fā)

  1. 使用vite創(chuàng)建工程,安裝前文技術(shù)棧提及的各種依賴包
  2. 在入口模塊編寫主體邏輯,引入主要模塊、聲明變量

    html

    <script setup> import { getMap, initMap } from '@/utils/mainMap2.js' import GLlayer from '#/gl-layers/src/index' import * as THREE from 'three' import * as dat from 'dat.gui' //... // 高德可視化類 let loca // 容器 const container = ref(null) // 圖層管理 const layerManger = new LayerManager() // 信息提示浮層 let normalMarker //... onMounted(async () => { // 初始化地圖 await init() // 初始化各種圖層 await initLayers() // 逐幀函數(shù),用于更新模型動(dòng)畫等內(nèi)容 animateFn() }) </script> <template> <div ref="container" class="container"></div> <div class="tool"> <div class="btn" @click="gotoCenter()">回到中心</div> <div class="btn" @click="toggleCross()">越界告警</div> <div class="btn" @click="toggleDronView()">無(wú)人機(jī)巡航</div> </div> </template>

  3. 初始化基礎(chǔ)地圖,并添加衛(wèi)星影像圖

    jsx

    async function init() { // 將高德地圖Map實(shí)例化做了一次封裝 const map = await initMap({ viewMode: '3D', dom: container.value, showBuildingBlock: false, center: SETTING.center, zoom: 15.5, pitch: 42.0, rotation: 4.9, mapStyle: 'amap://styles/light', skyColor: '#c8edff' }) // 添加衛(wèi)星地圖 const satelliteLayer = new AMap.TileLayer.Satellite(); map.add([satelliteLayer]); // 監(jiān)聽地圖縮放和點(diǎn)擊,用于開發(fā)調(diào)試 map.on('zoomend', (e) => { console.log(map.getZoom()) }) map.on('click', (e) => { const { lng, lat } = e.lnglat console.log([lng, lat]) }) // 高德可視化類 loca = new Loca.Container({ map, }); // 鼠標(biāo)懸浮于圖層元素上時(shí),出現(xiàn)信息浮層 normalMarker = new AMap.Marker({ offset: [70, -15], zooms: [1, 22] }); }

4.2 村居/綠化圖層

村居是指農(nóng)業(yè)示范區(qū)內(nèi)的建筑面生成模型,綠化圖層則是綠樹等植物的覆蓋區(qū)域,原本應(yīng)該是兩個(gè)圖層,因?yàn)樵诒緢?chǎng)景中僅僅作為地圖三維底座,均無(wú)交互性,我就直接把它們合并為一個(gè)3Dtiles以提升性能了。

4.2.1 制作村居數(shù)據(jù)

  1. 村居數(shù)據(jù)的建筑面獲取方法有兩種,我們可以通過(guò)一些GIS數(shù)據(jù)工具下載指定區(qū)域內(nèi)建筑面數(shù)據(jù),也可以通過(guò)AI Earth進(jìn)行衛(wèi)星影像圖建筑物提取,最終生成geoJSON文件,導(dǎo)入QGIS進(jìn)行數(shù)據(jù)清洗和加工。
  2. 如果建筑面沒(méi)有高度數(shù)據(jù),我們根據(jù)目標(biāo)場(chǎng)景的實(shí)際情況,可以在QGIS中生成一定范圍內(nèi)的隨機(jī)值

4.2.2 制作綠化區(qū)域數(shù)據(jù)

  1. 使用QGIS新建多邊形面圖層,在目標(biāo)場(chǎng)景區(qū)域內(nèi)將綠化區(qū)域圈選出來(lái)。在過(guò)程中可能會(huì)涉及到帶孔多邊形的制作,我們可以利用矢量多邊形的布爾運(yùn)算獲得。
  2. 在QGIS工具箱找到“矢量創(chuàng)建-多邊形內(nèi)部的隨機(jī)點(diǎn)”即可生成隨機(jī)點(diǎn)功能,即可在綠化區(qū)域生成均勻分布的隨機(jī)點(diǎn),后續(xù)每個(gè)點(diǎn)我們都可以種上一棵樹。

4.2.3 轉(zhuǎn)換為3D瓦片

  1. 新建cityEngine工程,并將制作好的村居和綠化數(shù)據(jù)另存為SHP格式,置入到工程中
  2. 將目標(biāo)場(chǎng)景的矩形范圍也導(dǎo)出一張TIF格式的圖片,置入到工程中,作為本工程場(chǎng)景的底圖
  3. 將村居數(shù)據(jù)Polygons拖入場(chǎng)景編輯面板中,選中元素對(duì)象并配置規(guī)則文件,我們就可以快速生成建筑模型,并通過(guò)配置將建筑高度與建筑面高度數(shù)據(jù)關(guān)聯(lián)上,選擇合適的房屋造型和風(fēng)格。

  1. 同理將綠化區(qū)域數(shù)據(jù)Points拖拽入場(chǎng)景編輯面板,并配置植物生成規(guī)則文件,我們就可以快速得到效果非常不錯(cuò)的植物綠化區(qū)域

  1. 選中兩個(gè)圖層的模型并導(dǎo)出為FBX,注意配置面板中的設(shè)置,中心一項(xiàng)關(guān)系到所有模型在地圖上的位置是否正確,需要格外關(guān)注

  2. 開啟cesiumlab,進(jìn)入通用模型切片,直接轉(zhuǎn)換為3Dtiles,可以在ceisumlab的預(yù)覽頁(yè)面中看到建筑和植物都落在地球的地面上,可能原點(diǎn)的地理位置是錯(cuò)誤的。這個(gè)不用擔(dān)心,我們?cè)趯⑵浣尤敫叩碌貓D時(shí)做再做調(diào)整。更細(xì)節(jié)的步驟可以看我之前寫的低成本創(chuàng)建數(shù)字孿生場(chǎng)景

4.2.4 在高德地圖呈現(xiàn)

  1. 部署3dtiles靜態(tài)服務(wù),在高德地圖中需要重新定義3dtiles的原點(diǎn)坐標(biāo),因此需要?jiǎng)?chuàng)建一個(gè)tileset.json入口文件副本,并將其初始轉(zhuǎn)置矩陣歸零

  1. 編寫代碼,這里使用之前開發(fā)的TilesLayer圖層做加載,關(guān)于如何在高德地圖中實(shí)現(xiàn)3dtiles,想了解具體實(shí)現(xiàn)可以看看這里

    csharp

    async function initBuildingLayer() { const map = getMap() const layer = new TilesLayer({ id: 'buildingLayer', title: '村居建筑圖層', alone: SETTING.alone, map, center: [113.531905, 22.737473], // 圖層中心點(diǎn) zooms: [4, 30], interact: false, tilesURL: 'http://localhost:9003/model/twQ1mVSwQ/tileset.0.json', // 村居模型 needShadow: true }) layerManger.add(layer) }

  1. 為保證視覺(jué)效果,加載完成后還對(duì)模型打光調(diào)亮、添加陰影,關(guān)于如何在地圖的平面上添加陰影,需要開個(gè)單獨(dú)的小節(jié)在后文詳敘。

    jsx

    layer.on('complete', ({ scene, renderer }) => { // 調(diào)整模型的亮度 const aLight = new THREE.AmbientLight(0xffffff, 0.5) scene.add(aLight) //... // 平行光,增加投影 var dLight = new THREE.DirectionalLight(0xffffff, intetity); dLight.position.set(lightPositionX, lightPositionY, lightPositionZ); dLight.castShadow = true; // 開啟陰影投射 dLight.shadow.mapSize.width = mapSize; // 增加陰影分辨率 dLight.shadow.mapSize.height = mapSize; dLight.shadow.camera.near = cameraNear; dLight.shadow.camera.far = caremaFar; dLight.shadow.camera.left = cameraLeft; dLight.shadow.camera.right = cameraRight; dLight.shadow.camera.top = cameraTop; dLight.shadow.camera.bottom = cameraBottom; dLight.shadow.bias = -0.0001; // 負(fù)值將陰影稍微向外偏移 scene.add(dLight); directionalLight = dLight // 平面陰影 const geometry1 = new THREE.PlaneGeometry(5000, 5000); const material1 = new THREE.ShadowMaterial({ opacity: 1.0 }) const plane = new THREE.Mesh(geometry1, material1); plane.position.z = 0; plane.receiveShadow = true; scene.add(plane); })?
  2. 最終的效果如下

4.3 水域圖層

  1. 我們同樣可以使用QGIS自行繪制、或者使用GIS工具獲取水域范圍數(shù)據(jù)

  1. 水面的實(shí)現(xiàn)方式是在指定的多邊形平面上添加水紋材質(zhì),這里使用到了ShaderMaterial編寫自定義著色器材質(zhì),我們封裝為WaterLayer圖層,詳細(xì)步驟可以看這里

    jsx

    async function initWaterLayer() { const map = getMap() const data = await fetchMockData('water.geojson') const layer = new GLlayers.WaterLayer({ id: 'waterLayer', map, data, // 水域GIS數(shù)據(jù) alone: SETTING.alone, zooms: [16, 22], animate: true, waterColor: '#CFEACD', // 水體顏色 altitude: -5 // 水面Mesh高度 }) layerManger.add(layer) }

  1. 最終效果如下,動(dòng)靜結(jié)合這樣一來(lái)村居看起來(lái)更靈動(dòng)了

4.4 農(nóng)田地塊

  1. 農(nóng)田和魚塘地塊具有共同的特性,實(shí)現(xiàn)方法類似可以合起來(lái)講,在QGIS上我們就可以通過(guò)屬性表對(duì)polygone按屬性做分類

  1. 獲取數(shù)據(jù),實(shí)例化Polylone,其實(shí)這種常規(guī)的Polygon,高德地圖Loca也有提供,之所以用自己開發(fā)的polygon是想給Polygon添加圖片紋理,比如正在使用的地塊使用水稻田紋理 ,而養(yǎng)護(hù)中的地塊則使用土地紋理,簡(jiǎn)單一點(diǎn)就是用顏色做區(qū)分。

    jsx

    async function initFarmLayer() { const map = getMap() const data = await fetchMockData('farm.geojson') console.log(data) data.features.forEach(item => { const { used } = item.properties // 根據(jù)地塊不同的使用狀態(tài),賦予不同的顏色 item.properties.color = used == 1 ? "#33a02c" : (used == 0 ? "#b2df8a" : "#ceb89e") }) const layer = new GlLayer.PolygonLayer({ id: 'farmLayer', alone: SETTING.alone, map, data, lineWidth: 0, opacity: 0.4, interact: true, //可鼠標(biāo)互動(dòng) zIndex: 100, altitude: 2 }) // 放入圖層管理器 layerManger.add(layer) }

  1. 單個(gè)PolygonLayer生成Mesh的核心代碼如下,將空間坐標(biāo)數(shù)組轉(zhuǎn)為Mesh的頂點(diǎn)三角面,并賦予材質(zhì),更詳細(xì)的的實(shí)現(xiàn)步驟可以看看之前分享的在高德地圖上實(shí)現(xiàn)Polylone圖層

    jsx

    /** * 繪制多邊形 * @private * @param {Array} path 路徑 * @param {Object} properties 屬性 */ drawPolygon ({ path, properties }) { const { altitude, opacity } = this._conf // 將路徑數(shù)據(jù)扁平化 const flatArr = path.map(v => { return [v[0], v[1], altitude] }).flat() // 三角剖分 const triangles = Earcut.triangulate(flatArr, null, 3) // 創(chuàng)建一個(gè)THREE.Geometry對(duì)象 const geometry = new THREE.BufferGeometry() // 將三角形的頂點(diǎn)添加到geometry對(duì)象 let faceList = [] for (let i = 0; i < triangles.length; i++) { const [x, y, z] = path[triangles[i]] faceList = [...faceList, x, y, altitude] } // 頂點(diǎn)三角面 geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(faceList), 3)) // 計(jì)算法線和頂點(diǎn)的面連接關(guān)系 geometry.computeVertexNormals() // 創(chuàng)建材質(zhì) const material = new THREE.MeshBasicMaterial({ color: properties.color || '#0674F1', transparent: true, opacity: properties.opacity || opacity }) // 創(chuàng)建多邊形的網(wǎng)格對(duì)象 const polygon = new THREE.Mesh(geometry, material) // 將多邊形網(wǎng)格對(duì)象添加到場(chǎng)景中 const _scene = this.group || this.scene _scene.add(polygon) }

  1. 最終效果如下

4.5 作物識(shí)別圖層

  1. 作物識(shí)別圖層的作用是展示AI遙感識(shí)別技術(shù)對(duì)農(nóng)田作物的識(shí)別結(jié)果,以及展示AI技術(shù)對(duì)魚塘產(chǎn)量做出的預(yù)測(cè)數(shù)據(jù),用AMap.MassMarker就可以滿足了
  2. 需要注意的是點(diǎn)標(biāo)記的坐標(biāo)位置是如何生成的,總不可能手動(dòng)創(chuàng)建效率太低了,我們可以使用QGIS自帶的矢量數(shù)據(jù)處理功能自動(dòng)創(chuàng)建質(zhì)心,直接為每個(gè)polygon生成中心坐標(biāo)點(diǎn)。右鍵圖層打開屬性表添加識(shí)別結(jié)果,導(dǎo)出geojson格式備用。

  1. 在高德地圖中添加圖層實(shí)現(xiàn),為保證與其他圖層的接口統(tǒng)一,我對(duì)MassMark和MassMakers進(jìn)行了封裝,統(tǒng)一基礎(chǔ)屬性、初始化配置參數(shù)和顯示隱藏方法。

    jsx

    import BaseUtils from './BaseUtils'; class CropLayer extends BaseUtils { data = []; markers = []; id = null layer = null iconMap = { '香蕉': { icon: 'xiangjiao.png', style: 0}, '火龍果': { icon: 'huolongguo.png', style: 1}, // ... } constructor(config) { super(config); this.getData(config.data); this.map = config.map; this.zooms = config.zooms ?? [10, 22]; this._zIndex = config.zIndex this.id = config.id this.init(); } /** * 處理具體的圖層顯示邏輯 * @param val */ _handleVisible(val) { const {layer} = this; const fn = val ? 'show' : 'hide'; if(layer){ layer[fn]() } } // 整理數(shù)據(jù) getData(geoJSON) { const arr = [] const {iconMap} = this geoJSON.features.forEach(item=>{ const {geometry, properties} = item const {crop} = properties const match = iconMap[crop] const [lng, lat] = geometry.coordinates if(match){ arr.push({ lnglat: [lng, lat, 50], crop, style: match.style }) } }) this.data = arr } async init() { const {data, map, iconMap, zooms, _zIndex} = this; const style = Object.keys(this.iconMap).map(key=>{ const {icon, style} = iconMap[key] return { url: `./static/icons/${icon}`, size: new AMap.Size(30,30), name: key } }) const layer = new AMap.MassMarks(data, { opacity: 1, zIndex: _zIndex, cursor: 'pointer', style, zooms }); layer.setMap(map) layer.on('mouseover', (e) => { this.dispatchEvent('mouseover', e) }); this.layer = layer this.visible = true; } //... } export default CropLayer;

  1. 這樣一來(lái)就可以輕松調(diào)用了,直接將農(nóng)田和魚塘數(shù)據(jù)合并使用一個(gè)圖層展示

    jsx

    async function initCropLayer() { const map = getMap() const data1 = await fetchMockData('crop.geojson') const data2 = await fetchMockData('poolCenter.geojson') data1.features = data1.features.concat(data2.features) const layer = new CropLayer({ id: 'cropLayer', data: data1, zooms: [16, 22], zIndex: 200, map }) // 鼠標(biāo)懸浮時(shí)彈出信息浮層 layer.on('mouseover', (e) => { const { crop, style } = e.data normalMarker.setPosition(e.data.lnglat); normalMarker.setOffset(new AMap.Pixel(90, -10)) let content = '' if (style <= 4) { //農(nóng)作物 content = `<div class="amap-info-window"> <p>作物: ${crop}</p> <p>識(shí)別匹配度: ${parseInt(Math.random() * 20) + 80}%</p> <p>產(chǎn)量預(yù)計(jì): ${parseInt(Math.random() * 30) + 20}噸</p> </div>` } else { //水產(chǎn)品 content = `<div class="amap-info-window"> <p>作物: ${crop}</p> <p>產(chǎn)量預(yù)計(jì): ${parseInt(Math.random() * 20) + 10}噸</p> </div>` } normalMarker.setContent(content) normalMarker.setMap(map) }) layer.on('mouseout', (e) => { map.remove(normalMarker); }) // 放入圖層管理器 layerManger.add(layer) }

  2. 最終效果如下

4.6 區(qū)域邊界

  1. 區(qū)域邊界的數(shù)據(jù)繪制很簡(jiǎn)單,就是一個(gè)常規(guī)的封閉線圖形polyline。

  1. 我使用之前開發(fā)的GlLayer.BorderLayer進(jìn)行實(shí)例化渲染,方便定制各種動(dòng)畫。

    jsx

    async function initBorderLayer() { const map = getMap() const data = await fetchMockData('border.geojson') const layer = new GlLayer.BorderLayer({ id: 'borderLayer', alone: SETTING.alone, map, wallColor: '#3dfcfc', // 墻體顏色 wallHeight: 100, // 墻體高度 data, speed: 0.3, animate: true, zooms: [11, 22], altitude: 0 }) layerManger.add(layer) }

  1. 區(qū)域入侵監(jiān)控這部分操作正常來(lái)說(shuō)是由物聯(lián)網(wǎng)設(shè)備檢測(cè)到,推送消息給服務(wù)端,再由服務(wù)端推送給前端一條消息。為方便演示我直接在前端模擬了,定時(shí)檢測(cè)指定目標(biāo)位置,如果在polygon內(nèi)部,則區(qū)域邊界圖層出現(xiàn)告警狀態(tài),整體變?yōu)榧t色;目標(biāo)離開,則解除告警狀態(tài)。為此新增了setColor方法用于切換顏色狀態(tài)。

    jsx

    /** * 設(shè)置區(qū)域邊界顏色 * @param {String} newColor 顏色值,比如'#ffffff' */ setColor(newColor){ // 創(chuàng)建新紋理 const newTexture = this.generateTexture (128, newColor) newTexture.wrapS = THREE.RepeatWrapping // 水平重復(fù)平鋪 newTexture.wrapT = THREE.RepeatWrapping // 垂直重復(fù)平鋪 this._color = newColor this._texture_offset = 0 this.mainMesh.material.color = newColor this.animateMesh.material.map = newTexture this._texture = newTexture } // 創(chuàng)建材質(zhì) generateTexture (size = 64, color = '#ff0000') { const canvas = document.createElement('canvas') canvas.width = size canvas.height = size const ctx = canvas.getContext('2d') const linearGradient = ctx.createLinearGradient(0, 0, 0, size) linearGradient.addColorStop(0.2, hexToRgba(color, 0.0)) linearGradient.addColorStop(0.8, hexToRgba(color, 0.5)) linearGradient.addColorStop(1.0, hexToRgba(color, 1.0)) ctx.fillStyle = linearGradient ctx.fillRect(0, 0, size, size) const texture = new THREE.Texture(canvas) texture.needsUpdate = true // 必須 return texture }
  2. 模擬邊界入侵檢測(cè),我們可以使用AMap.GeometryUtils提供的幾何計(jì)算方法,判斷點(diǎn)是否在多邊形內(nèi),是的話則改變邊界狀態(tài)為告警,否則移除告警。

    jsx

    // 是否進(jìn)入入侵檢測(cè)模式 let isInvadeMode = false // 定時(shí)器 let invadeClock = null // 入侵者標(biāo)記 let invadeMarker /** * 切換入侵檢測(cè)模式 */ async function toggleInvade() { const map = getMap() const borderLayer = layerManger.findLayerById('borderLayer') isInvadeMode = !isInvadeMode // 入侵檢測(cè)范圍 let ring = [] // 入侵者路徑 let invadePath // 當(dāng)前步數(shù) let invadeStep = 0 if (isInvadeMode) { const borderPath = await fetchMockData('border.geojson') ring = borderPath.features[0].geometry.coordinates[0] initInvade() invadeClock = setInterval(() => { // 更新目標(biāo)位置 const pos = invadePath[invadeStep] invadeStep = (invadeStep + 1) % invadePath.length invadeMarker.setPosition(pos) // 判斷為入侵,邊界墻修改顏色 const color = isInRing(pos, ring) ? '#ff0000' : '#3dfcfc' if(borderLayer._color !== color){ borderLayer.setColor(color) } }, 1000) } else { clearInvade() borderLayer.setColor('#3dfcfc') } // 創(chuàng)建 async function initInvade(){ // 路徑 const {features} = await fetchMockData('invade-path.geojson') invadePath = features[0].geometry.coordinates[0] // 目標(biāo) invadeMarker = new AMap.Marker({ content: `<img style="width:30px;" src="./static/icons/ico-invade.png">`, anchor: 'bottom-center', offset: new AMap.Pixel(-15, -20) }) map.add(invadeMarker) } // 銷毀 function clearInvade(){ clearInterval(invadeClock) invadeClock = null map.remove(invadeMarker) invadeMarker = null } // 檢測(cè)是否在范圍內(nèi) function isInRing (pos, ring){ const res = AMap.GeometryUtil.isPointInRing(pos, ring) console.log('is in ring ', res) return res } }
  3. 最終效果如下

4.7 無(wú)人機(jī)巡查功能

最近“低空經(jīng)濟(jì)”這個(gè)概念很火,說(shuō)的是是以各種有人駕駛和無(wú)人駕駛航空器的各類低空飛行活動(dòng)為牽引,輻射帶動(dòng)相關(guān)領(lǐng)域融合發(fā)展的綜合性經(jīng)濟(jì)形態(tài),既然如此怎么能少得了無(wú)人機(jī)的出場(chǎng)。在本文中我們實(shí)現(xiàn)的是單架無(wú)人機(jī)模型沿著指定的閉合軌跡飛行移動(dòng),并且可以用無(wú)人機(jī)的第三人稱視角俯瞰地圖。

  1. 關(guān)于自動(dòng)巡航的功能在之前做無(wú)人車巡航的時(shí)候已經(jīng)實(shí)現(xiàn)過(guò)了,這里再講解一下核心代碼,其實(shí)就是在Tween更新函數(shù)中,按照既定的路徑軌跡不斷調(diào)整NPC的位置和朝向,如果需要第三人稱視角,則同步更新相機(jī)的朝向即可,更詳細(xì)的步驟可以看在高德地圖實(shí)現(xiàn)自動(dòng)巡航
  2. jsx
    代碼解讀
    復(fù)制代碼
    // 創(chuàng)建移動(dòng)目標(biāo)NPC 和 移動(dòng)控制器 // NPC 是外部加載的gltf模型 onReady () { if (this._conf.NPC) { this.initNPC() } this.initController() } /** * 初始化主體NPC的狀態(tài) * @private */ initNPC () { const { _PATH_COORDS, scene } = this const { NPC } = this._conf // z軸朝上 NPC.up.set(0, 0, 1) // 初始位置和朝向 if (_PATH_COORDS.length > 1) { NPC.position.copy(_PATH_COORDS[0]) NPC.lookAt(_PATH_COORDS[1]) } // 添加到場(chǎng)景中 scene.add(NPC) } /** * 創(chuàng)建移動(dòng)控制器 * @private */ initController () { // 狀態(tài)記錄器 const target = { t: 0 } // 獲取第一段線段的移動(dòng)時(shí)長(zhǎng) const duration = this.getMoveDuration() // 路線數(shù)據(jù) const { _PATH_COORDS, _PATH_LNG_LAT, map } = this this._rayController = new TWEEN.Tween(target) .to({ t: 1 }, duration) .easing(TWEEN.Easing.Linear.None) .onUpdate(() => { const { NPC, cameraFollow } = this._conf // 終點(diǎn)坐標(biāo)索引 const nextIndex = this.getNextStepIndex() // 獲取當(dāng)前位置在路徑上的位置 const point = new THREE.Vector3().copy(_PATH_COORDS[this.npc_step]) // 計(jì)算下一個(gè)路徑點(diǎn)的位置 const nextPoint = new THREE.Vector3().copy(_PATH_COORDS[nextIndex]) // 計(jì)算物體應(yīng)該移動(dòng)到的位置,并移動(dòng)物體 const position = new THREE.Vector3().copy(point).lerp(nextPoint, target.t) if (NPC) { // 更新NPC的位置 NPC.position.copy(position) } // 需要鏡頭跟隨 if (cameraFollow) { // 計(jì)算兩個(gè)lngLat端點(diǎn)的中間值 const pointLngLat = new THREE.Vector3().copy(_PATH_LNG_LAT[this.npc_step]) const nextPointLngLat = new THREE.Vector3().copy(_PATH_LNG_LAT[nextIndex]) const positionLngLat = new THREE.Vector3().copy(pointLngLat).lerp(nextPointLngLat, target.t) // 更新地圖鏡頭位置 this.updateMapCenter(positionLngLat) } // 更新地圖朝向 if (cameraFollow) { const angle = this.getAngle(position, _PATH_COORDS[(this.npc_step + 3) % _PATH_COORDS.length]) this.updateMapRotation(angle) } }) .onStart(() => { const { NPC } = this._conf // 計(jì)算線段重點(diǎn)的位置和角度 const nextPoint = _PATH_COORDS[(this.npc_step + 3) % _PATH_COORDS.length] // 更新主體的正面朝向 if (NPC) { NPC.lookAt(nextPoint) NPC.up.set(0, 0, 1) } }) .onComplete(() => { // 更新到下一段路線 this.npc_step = this.getNextStepIndex() // 調(diào)整時(shí)長(zhǎng) const duration = this.getMoveDuration() // 重新出發(fā) target.t = 0 this._rayController .stop() .to({ t: 1 }, duration) .start() }) .start() }
  3. 實(shí)例化GlLayer.DrivinLayer圖層,我們將無(wú)人機(jī)巡航和飛行軌跡拆分為兩個(gè)圖層實(shí)現(xiàn)

    jsx

    async function initDroneLayer() { const map = getMap() const data = await fetchMockData('dronWander2.geojson') const NPC = await getDroneModel() // 巡航圖層 const layer = new DrivingLayer({ id: 'dronLayer', map, zooms: [4, 30], path: data, altitude: 50, speed: 50.0, NPC, interact: true }) layer.on('complete', ({ scene }) => { // 調(diào)整模型的亮度 const aLight = new THREE.AmbientLight(0xffffff, 3.5) scene.add(aLight) layer.resume() }) layerManger.add(layer) // 路徑軌跡動(dòng)畫圖層 const dronPathLayer = new FlowlineLayer({ id: 'dronPathLayer', map, zooms: [16, 22], data, speed: 0.5, lineWidth: 10, altitude: 50 }) layerManger.add(dronPathLayer) }
  4. 本實(shí)例最大的難度在于如何讓無(wú)人機(jī)在飛行的時(shí)候4個(gè)螺旋槳旋轉(zhuǎn)擺動(dòng),這里最后選擇了在逐幀函數(shù)更新gltf自帶動(dòng)畫的方法;關(guān)于gltf動(dòng)畫如何制作,在后面有單獨(dú)章節(jié)。

    jsx

    // 加載無(wú)人機(jī) function getDroneModel() { return new Promise((resolve) => { const loader = new GLTFLoader() loader.load('./static/model/drone/drone1.glb', (gltf) => { // 調(diào)整模型尺寸 const model = gltf.scene.children[0] const size = 10.0 model.scale.set(size, size, size) // 播放動(dòng)畫 mixer = new THREE.AnimationMixer(gltf.scene); const action = mixer.clipAction(gltf.animations[0]) // 動(dòng)畫播放速度 action.setEffectiveTimeScale(guiCtrl.mixerPlaySpeed); action.play(); resolve(model) }) }) } // 播放無(wú)人機(jī)動(dòng)畫 function animateFn() { requestAnimationFrame(animateFn); if (mixer) { // 更新無(wú)人機(jī)旋轉(zhuǎn)動(dòng)畫 mixer.update(0.01); //必須加上參數(shù)才有動(dòng)畫 } }
  5. 最終實(shí)現(xiàn)效果如下,第三人稱游戲的代入感出來(lái)了有沒(méi)有。

4.8 災(zāi)害預(yù)測(cè)圖層

  1. 該圖層本質(zhì)上是個(gè)3D熱力圖,源數(shù)據(jù)是帶有權(quán)重屬性的坐標(biāo)點(diǎn)集合,我們可以在QGIS上編輯它們甚至可以查看二維效果
  2. 導(dǎo)出數(shù)據(jù),使用高德自帶的可視化圖層Loca.Heatmap實(shí)現(xiàn)

    jsx

    /** * 災(zāi)害風(fēng)險(xiǎn)檢測(cè)圖層 */ async function initRiskLayer() { const map = getMap() const data = await fetchMockData('fertility.geojson') const geo = new Loca.GeoJSONSource({ data }) const heatmap = new Loca.HeatMapLayer({ zIndex: 10, opacity: 1, visible: false, zooms: [2, 22], }); heatmap.setSource(geo, { id: 'riskLayer', radius: 150, unit: 'meter', height: 300, gradient: { 1: '#FF4C2F', 0.8: '#FAA53F', 0.6: '#FFF100', 0.5: '#7DF675', 0.4: '#5CE182', 0.2: '#29CF6F', }, value: function (index, feature) { return feature.properties.weight ?? 0; }, min: 0, max: 100, visible: true }); loca.add(heatmap); map.on('click', function (e) { const feat = heatmap.queryFeature(e.pixel.toArray()); // 展示更多信息... }); heatmap.id = 'riskLayer' layerManger.add(heatmap) }
  3. 在切換圖層為顯示狀態(tài)時(shí),可以加上動(dòng)畫以達(dá)到更好的視覺(jué)效果

    jsx

    // 給圖層的顯示增加動(dòng)畫效果 function animateLayer(layer){ switch(layer.id){ case 'riskLayer': layer.addAnimate({ key: 'height', value: [0, 1], duration: 2000, easing: 'BackOut', }); layer.addAnimate({ key: 'radius', value: [0, 1], duration: 2000, easing: 'BackOut', transform: 1000, random: true, delay: 5000, }); break; //... }
  4. 最終效果如下,產(chǎn)量AI預(yù)測(cè)圖層的實(shí)現(xiàn)方法類似就不贅述

4.9 使用圖層管理器操作圖層

本示例涉及到圖層數(shù)量已經(jīng)有十幾個(gè),為方便進(jìn)行圖層的統(tǒng)一操作(比如在專題A哪些圖層需要顯示,其他圖層隱藏;或者調(diào)用圖層的某個(gè)功能),我們需要圖層管理器layerManager,且給圖層賦予唯一的id值便于在管理器中獲取。

如下面代碼所示,提供最基礎(chǔ)的添加、查找、清除功能

jsx

/** * 圖層管理器 * @extends null * @author Zhanglinhai <gyrate.sky@qq.com> */ class Manager { /** * @description 創(chuàng)建一個(gè)實(shí)例 * @param {Object} conf * @param {Array} conf.data 圖層數(shù)組 [layer,...] 默認(rèn)為[] */ constructor (config = {}) { this._list = config.data || [] } /** * @description 添加1個(gè)圖層到管理器 * @param {String} id 圖層id * @param {String} title 圖層名稱 * @param {*} layer 圖層實(shí)例 */ add (layer) { if (layer === undefined) { console.error('缺少圖層實(shí)例') return } if (layer.id === undefined) { console.error('缺少圖層id') return } const { id } = layer const match = this.findLayerById(id) if (match) { console.error(`圖層的id ${id} 不是唯一標(biāo)識(shí),請(qǐng)更換`) return } this._list.push(layer) } /** * @description 通過(guò)id查找圖層信息 * @param {String} id 圖層id * @returns {*} 返回匹配的第一個(gè)圖層 */ findLayerById (id) { const match = this._list.find(item => item.id === id) return match } /** * @description 清空當(dāng)前的圖層管理器 */ clear () { this._list.forEach((layer) => { if (layer.destroy) { layer.destroy() } console.log(`銷毀layer ${layer.id}`) }) this._list = [] } }

這樣一來(lái)就方便我們快捷操作圖層,將整個(gè)地圖作為可視化大屏的主體,放置到帶有導(dǎo)航和圖表的低代碼大屏框架中,就完成了初步的搭建工作。

5. 其他問(wèn)題解決方案

5.1 如何在場(chǎng)景中產(chǎn)生投影

如何在高德地圖的底圖上添加模型的投影,我被困擾了一段時(shí)間,后來(lái)請(qǐng)教了高德的技術(shù)大佬WT才得到啟發(fā)解開了這個(gè)問(wèn)題,感謝wt大佬的支持。three.js提供了一種陰影材(ShadowMaterial)此材質(zhì)可以接收陰影,但在其他方面完全透明。

要想在場(chǎng)景中獲得投影,需要下面幾個(gè)步驟都齊全

  1. 渲染器打開投影
jsx
代碼解讀
復(fù)制代碼
// 禁用自動(dòng)清理,以保持地圖底圖可見(jiàn) renderer.autoClear = false; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 重要:會(huì)影響到畫布尺寸 renderer.setSize(window.innerWidth, window.innerHeight);
  1. 創(chuàng)建合適的平行光源,有各種參數(shù)需要設(shè)置

jsx

// 創(chuàng)建平行光 var dLight = new THREE.DirectionalLight(0xffffff, 3); dLight.position.set(lightPositionX, lightPositionY, lightPositionZ); dLight.castShadow = true; // 開啟陰影投射 dLight.shadow.mapSize.width = mapSize; // 增加陰影分辨率 dLight.shadow.mapSize.height = mapSize; dLight.shadow.camera.near = cameraNear; dLight.shadow.camera.far = caremaFar; dLight.shadow.camera.left = cameraLeft; dLight.shadow.camera.right = cameraRight; dLight.shadow.camera.top = cameraTop; dLight.shadow.camera.bottom = cameraBottom; scene.add(dLight);
  1. 各種關(guān)聯(lián)物體也必須將屬性castShadow 、receiveShadow設(shè)置為true

jsx

// 創(chuàng)建幾何體 var geo = new THREE.BoxGeometry(1000, 1000, 1000); for (let i = 0; i < data.length; i++) { const d = data[i]; var mesh = new THREE.Mesh(geo, mat); mesh.position.set(d[0], d[1], 500); mesh.castShadow = true; // 啟用陰影投射! mesh.receiveShadow = true; // 接收陰影! //... }
  1. 給底部平面賦予shadowMaterial材質(zhì)

    jsx
    ?
    // 創(chuàng)建接收陰影的平面 var planeGeo = new THREE.PlaneGeometry(50000, 50000); var shadowMat = new THREE.ShadowMaterial({ opacity: planeMaterialOpacity, }); plane = new THREE.Mesh(planeGeo, shadowMat); plane.receiveShadow = true; // 接收陰影! scene.add(plane);
  2. 最終效果如下,演示代碼鏈接放到這里了

5.2 給模型制作常規(guī)動(dòng)畫

  1. 下載一個(gè)無(wú)人機(jī)模型FBX格式,推薦在sketchfab上找,素材齊全。打開blender,導(dǎo)入FBX模型,把所有部件歸屬到一個(gè)根節(jié)點(diǎn),后續(xù)控制根節(jié)點(diǎn)其他部件也跟著移動(dòng)
  2. 在動(dòng)畫時(shí)間軸給每個(gè)部件加上動(dòng)畫關(guān)鍵幀,調(diào)試好動(dòng)畫

  3. 補(bǔ)間動(dòng)畫默認(rèn)是緩入緩出的,可以同個(gè)左上角切換面板到曲線編輯器修改補(bǔ)間動(dòng)畫線

  4. 最關(guān)鍵的一步。導(dǎo)出gltf時(shí)動(dòng)畫一項(xiàng)必須勾選,且動(dòng)畫模式設(shè)置為“合并的活動(dòng)動(dòng)作”,這樣的話,導(dǎo)出的gltf就能把所有部件動(dòng)作合并為一個(gè)動(dòng)作了。

  5. 最終預(yù)覽效果,螺旋槳的旋轉(zhuǎn)動(dòng)畫不需要做太快,因?yàn)樵趙eb端實(shí)際播放時(shí),速度倍率是可以通過(guò)action.setEffectiveTimeScal()調(diào)節(jié)的,要多快有多塊。

5.3  圖層的深度關(guān)系

如何處理高德自有圖層和自定義圖層的深度關(guān)系,這里必須了解高德提供的CustomLayer和GLCustomLayer的區(qū)別。

前者是在地圖實(shí)例畫布Canvas1之外另外覆蓋了一個(gè)Canvas標(biāo)簽,因此所有內(nèi)容都會(huì)置于Canvas1內(nèi)容之上,無(wú)論空間上是否合理;而后者則是與地圖實(shí)例共享畫布的,在GLCustomLayer上創(chuàng)建的內(nèi)容能夠與地圖上的元素、高德可視化類創(chuàng)建的元素共享深度關(guān)系,因此使用GLCustomLayer會(huì)讓多圖層的場(chǎng)景視覺(jué)上更加和諧,但代價(jià)就是Map需要逐幀重繪,性能損耗更高。所以如何取舍還是要看具體的業(yè)務(wù)場(chǎng)景進(jìn)行選擇。

總結(jié)

至此,使用高德地圖制作數(shù)字農(nóng)業(yè)可視化大屏的分享就告一段落了。事實(shí)上這并不是一個(gè)最終成本,因?yàn)槲疫€有很多想法沒(méi)有落實(shí), 比如精細(xì)化農(nóng)業(yè)大棚的搭建,無(wú)人機(jī)實(shí)時(shí)視頻投影、火災(zāi)預(yù)測(cè)等等功能展示;還有一些技術(shù)問(wèn)題沒(méi)有解決,比如cesiumlab使用FBX生成的3dtiles沒(méi)有支持LOD,即不同地圖縮放層級(jí)下的精細(xì)度,這在性能和視覺(jué)效果上肯定是存在優(yōu)化空間的,據(jù)我所見(jiàn)在cityEngine階段LOD信息還是存在的,至于具體在哪個(gè)過(guò)程中丟失了,還需要排查一下。

但戰(zhàn)線拉太長(zhǎng)的話項(xiàng)目可能就會(huì)永遠(yuǎn)沒(méi)有階段成果,時(shí)間關(guān)系就先發(fā)布這么多了了。說(shuō)不定分享出來(lái)之后,可以起到拋磚引玉的作用,最好能撈到更多志同道合的伙伴來(lái)一起共建虛擬農(nóng)場(chǎng)元宇宙。

本示例使用到的高德JSAPI

3D自定義圖層AMap.GLCustomLayer

自定義圖層AMap.CustomLayer

AMap.Map地圖對(duì)象類

海量點(diǎn)類AMap.MassMarkers

LOCA 數(shù)據(jù)可視化 API 2.0

空間數(shù)據(jù)計(jì)算的函數(shù)庫(kù) GeometryUtil

相關(guān)鏈接

數(shù)字孿生×低空經(jīng)濟(jì) | 天空地一體化 城市數(shù)字孿生電子沙盤指揮系統(tǒng)

在cityEngine編寫模型生成規(guī)則

THREEJS 陰影材質(zhì)的使用文檔

源代碼Github地址

演示頁(yè)面地址


作者:Gyrate
鏈接:https://juejin.cn/post/7432127587919298600
來(lái)源:稀土掘金
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。

該文章在 2025/4/12 18:27:20 編輯過(guò)
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 嫩草研究院在线 | 精品久久久久中文第一幕 | 国产成人深夜福利短视频99 | 日本一区不卡在线 | 人妻丰满av无码久久不卡 | 国产欧美一区二区三区观看 | 深夜特黄a级毛片免费播放 深夜国产成人福利在线观看女同 | 国产精品一级二级日韩久无码 | 欧美精品黑人粗大免费 | 亚洲AV国产成人精品区三上 | 精品福利一区二区在线观看 | 国产精品久久久久无码AV1 | 国产精品三级电影在线观看 | 欧美午夜小视频 | 久久久无码精品午夜 | 国产a一级无码毛片一区二区三区 | 日韩国产成人无码AV毛片蜜柚 | 成人国产AV精品久久久久 | 老司机亚洲精品影院 | 久久午夜免费鲁丝片 | 日本高清视频:色情www | 无码一区二区视频 | 麻豆视传媒官方短视频网站 | 伦理电影中文字幕韩国在线观 | 91制片厂制作果冻传媒八夷 | 亚洲中文字幕久在线 | 日韩人妻高清精品视频 | 性中国熟女毛耸耸性视频 | 囯产精品一区二区三区线 | 日本无码精品国产aⅴ亚洲 日本无码精品无码白石麻衣 | 中文字幕精品亚洲字幕资源网 | 一本久久a久久精品亚洲 | 五月丁香综合中文亚洲 | 国产成人综合一区二区三区 | 扬州市老司机乱伦麻豆 | 国产av无码专区亚汌a | 国产99久久久国产精品成人 | 亚洲三区视频 | 久久国产乱子精品免费女 | 国产精品伦子一区二区三区 | 国产午夜毛片一区二区三区 |