无码国模产在线观看免费-无码国内精品久久人妻-无码国内精品久久综合88-无码国内精品人妻少妇-无码国内精品人妻少妇蜜桃视频-无码国语中文在线播放

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

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

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

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),不僅包括高德開(kāi)發(fā)平臺(tái)的技術(shù),也集成了具體業(yè)務(wù)分析、GIS數(shù)據(jù)生成、3D模型制作等內(nèi)容。附演示頁(yè)面地址,源代碼地址見(jiàn)文末。

2. 需求分析

本次做可視化大屏的開(kāi)發(fā),我希望最終的開(kāi)發(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. 魚(yú)塘:標(biāo)注魚(yú)塘的位置和范圍,顯示魚(yú)塘的面積和水深等基本信息。展示魚(yú)塘的養(yǎng)殖情況,包括養(yǎng)殖的魚(yú)類品種、生長(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è)田間的病蟲(chóng)害發(fā)生情況、氣象條件、作物生長(zhǎng)狀況等因素,運(yùn)用病蟲(chóng)害預(yù)測(cè)模型,預(yù)測(cè)病蟲(chóng)害的發(fā)生趨勢(shì)和流行范圍。

2.3 安全監(jiān)管

  1. 無(wú)人機(jī)巡查:在地圖上展示無(wú)人機(jī)的巡查路線和實(shí)時(shí)位置,用戶可以直觀地看到無(wú)人機(jī)的飛行軌跡。
  2. 入侵告警:在地圖上劃定重點(diǎn)安全區(qū)域,如農(nóng)田保護(hù)區(qū)、魚(yú)塘養(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)控畫(huà)面和相關(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ù)需求分析,我們就可以開(kāi)始將它們轉(zhuǎn)為技術(shù)上的需求模塊進(jìn)行逐個(gè)實(shí)現(xiàn),其中部分圖層可視化效果,使用高德平臺(tái)提供的可視化類Loca可以滿足了,其他部分圖層則需要自行開(kāi)發(fā),這里我將自己平時(shí)積累的可視化圖層整理為的gl-layers圖層庫(kù),核心代碼是基于three JS和高德自定義圖層類CustomLayer、GLCustomLayer進(jìn)行開(kāi)發(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ì)開(kāi)發(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)魚(yú)塘地塊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 主體框架開(kāi)發(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)畫(huà)等內(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)聽(tīng)地圖縮放和點(diǎn)擊,用于開(kāi)發(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)的建筑面生成模型,綠化圖層則是綠樹(shù)等植物的覆蓋區(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)我們都可以種上一棵樹(shù)。

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. 開(kāi)啟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. 編寫代碼,這里使用之前開(kāi)發(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)于如何在地圖的平面上添加陰影,需要開(kāi)個(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; // 開(kāi)啟陰影投射    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)田和魚(yú)塘地塊具有共同的特性,實(shí)現(xiàn)方法類似可以合起來(lái)講,在QGIS上我們就可以通過(guò)屬性表對(duì)polygone按屬性做分類

  1. 獲取數(shù)據(jù),實(shí)例化Polylone,其實(shí)這種常規(guī)的Polygon,高德地圖Loca也有提供,之所以用自己開(kāi)發(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ì)魚(yú)塘產(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)。右鍵圖層打開(kāi)屬性表添加識(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)田和魚(yú)塘數(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. 我使用之前開(kāi)發(fā)的GlLayer.BorderLayer進(jìn)行實(shí)例化渲染,方便定制各種動(dòng)畫(huà)。

    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)離開(kāi),則解除告警狀態(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)畫(huà)圖層  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)畫(huà)的方法;關(guān)于gltf動(dòng)畫(huà)如何制作,在后面有單獨(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)畫(huà)      mixer = new THREE.AnimationMixer(gltf.scene);      const action = mixer.clipAction(gltf.animations[0])      // 動(dòng)畫(huà)播放速度      action.setEffectiveTimeScale(guiCtrl.mixerPlaySpeed);      action.play();      resolve(model)    })  }) } // 播放無(wú)人機(jī)動(dòng)畫(huà) function animateFn() { requestAnimationFrame(animateFn); if (mixer) {  // 更新無(wú)人機(jī)旋轉(zhuǎn)動(dòng)畫(huà)  mixer.update(0.01); //必須加上參數(shù)才有動(dòng)畫(huà)     } }
  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)畫(huà)以達(dá)到更好的視覺(jué)效果

    jsx

    // 給圖層的顯示增加動(dòng)畫(huà)效果 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ā)解開(kāi)了這個(gè)問(wèn)題,感謝wt大佬的支持。three.js提供了一種陰影材(ShadowMaterial)此材質(zhì)可以接收陰影,但在其他方面完全透明。

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

  1. 渲染器打開(kāi)投影
jsx
代碼解讀
復(fù)制代碼
// 禁用自動(dòng)清理,以保持地圖底圖可見(jiàn) renderer.autoClear = false; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 重要:會(huì)影響到畫(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; // 開(kāi)啟陰影投射 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)畫(huà)

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

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

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

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

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

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

前者是在地圖實(shí)例畫(huà)布Canvas1之外另外覆蓋了一個(gè)Canvas標(biāo)簽,因此所有內(nèi)容都會(huì)置于Canvas1內(nèi)容之上,無(wú)論空間上是否合理;而后者則是與地圖實(shí)例共享畫(huà)布的,在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ò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(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è)而開(kāi)發(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

主站蜘蛛池模板: 亚洲A片无码一区二区蜜桃久久 | 日本无码少妇 | 日本一道本不卡免费播放 | 精品人妻无码专区在中文字幕 | 涩涩资源中文字幕久久婷婷爱 | 国产满18av精品免费观看视频 | 动漫精品一区二区三区 | 黑人巨大两根一起挤进A片小说 | 青青久在线视频免费观看手机 | 潮喷日韩欧美亚洲 | 日本一道人妻无码一区在线 | 日韩精品无码一区二区三区av | 91女神爱丝袜vivian在线观看 | av无码国产一区二区三区 | japanese中国丰满少妇 | 91精品国产经典在线观看 | 狠狠操网站 | 国产999精品人妻一区二区三区 | 天天干福利导航 | 亚洲精品无码aⅴ片影音先锋 | 日韩不卡av免费观看 | 狠狠色噜噜狠狠色综合久 | 99久久国产精品亚洲 | 亚洲愉拍一区二区三区 | 黄色国产在线 | 一级网站草莓视频亚洲精品成人小视频 | 九色视频最新网址 | 欧美性生活a片 | 久久精品久久久久 | 国产人妻人伦精品熟女 | 狠狠躁日日躁夜夜躁A片 | 2024全国精品卡一卡二 | 欧美日韩亚洲成人 | 亚洲欧美自拍另类欧美 | 手机在线观看视频免费视频 | av无码播放一区 | a级自慰免费网站 | 国产伦精品一区二区三区妓女原神 | 一区二区福利视频 | 久久精品人妻无码一区二区三区V | 无码成人片一区二区三区 |