Uniapp中使用renderjs實現OpenLayers+天地圖的展示與操作

Uniapp中自帶的地圖組件對支持的地圖服務略有局限,同時,該組件在樣式布局上層級過高且無法控制,無法滿足部分高度自定義化的需求。故引入renderjs視圖層工具搭配OpenLayers框架對地圖功能進行實現,但由于renderjs的限制,只支持App端與H5端。

一、renderjs

renderjs是一個運行在視圖層的js,可對DOM元素進行操作。它比WXS更加強大但只支持app-vueweb(H5)
renderjs的主要作用有:

  1. 大幅降低邏輯層和視圖層的通訊損耗,提供高性能視圖交互能力;
  2. 在視圖層操作dom,運行 for web 的 js庫。

renderjs相關的更多信息見Uniapp官網介紹。

二、OpenLayers

OpenLayers 是一個開源的 JavaScript 庫,用于在 Web 瀏覽器中顯示和交互地圖數據。它支持多種地理數據源(如 WMS、WFS、GeoJSON 等),并提供豐富的功能,如地圖渲染、圖層控制、坐標轉換和用戶交互(縮放、平移、標記等)。
主要應用場景在:

  1. Web GIS 系統:構建地理信息展示與分析平臺。
  2. 數據可視化:疊加業務數據(如熱力圖、軌跡)。
  3. 自定義地圖:整合第三方地圖服務或離線瓦片。

OpenLayers 官網:https://openlayers.org/

三、Uniapp展示天地圖

1.依賴下載

npm install ol

2.引入OpenLayers相關方法

此時,需要借助renderjs對Openlayers進行處理,另起一個script 標簽<script module="ol" lang="renderjs"> ... </script>包裹相關內容。

<script module="ol" lang="renderjs">
import {Map,View
} from 'ol';
import TileLayer from 'ol/layer/Tile';
import Tile from 'ol/layer/Tile';
import VectorSource from 'ol/source/Vector';
import {Vector as VectorLayer,
} from 'ol/layer';
import {defaults as defaultControls,
} from 'ol/control';
</script>

3.初始化天地圖

頁面中建立一個id為map的塊級元素供后續操作,其中map_page類需要設置好寬度與高度。

<template><view><view id="map" class="map_page"></view></view>
</template>

使用renderjs操作OpenLayers對天地圖進行展示。

<script module="ol" lang="renderjs">
import {Map,View
} from 'ol';
import TileLayer from 'ol/layer/Tile';
import Tile from 'ol/layer/Tile';
import VectorSource from 'ol/source/Vector';
import {Vector as VectorLayer,
} from 'ol/layer';
import XYZ from 'ol/source/XYZ'
import {defaults as defaultControls,
} from 'ol/control';export default {name: 'gis-map',data() {return {//地圖對象map: null, // 地圖渲染控件vectorSource: null,vectorLayer: null,}},mounted() {this.initMap()},methods: {/*** 初始化地圖*/initMap() {this.vectorSource = new VectorSource();this.vectorLayer = new VectorLayer({source: this.vectorSource,});// 引入天地圖瓦片資源let source = new XYZ({url: 'http://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=**這里用你申請的密鑰**',})let tileLayer = new Tile({title: '天地圖',source: source,})// 標注圖層(行政區名稱,道路)瓦片資源let sourceMark = new XYZ({url: "http://t0.tianditu.com/DataServer?T=cta_w&tk=**這里用你申請的密鑰**&x={x}&y={y}&l={z}",})let tileMark = new Tile({title: '標注圖層',source: sourceMark,})// 地鐵路線圖層// let sourceSubway = new XYZ({// 	url: '******'// })// let tileSubway = new Tile({// 	title: '地鐵路線圖層',// 	source: sourceSubway,// })// 地圖實例this.map = new Map({controls: defaultControls({attribution: false,zoom: false,rotate: false,}),target: 'map', // 對應頁面里 id 為 map 的元素layers: [tileLayer, tileMark], //若有其他圖層如tileSubway,則可放進數組中進行初始化處理view: new View({// 地圖視圖projection: 'EPSG:4326', // 坐標系,有EPSG:4326和EPSG:3857center: [116.39, 39.92], // 中心坐標zoom: 12, // 地圖縮放級別(打開頁面時默認級別)minZoom: 1, // 地圖縮放最小級別maxZoom: 18,enableRotation: false, //禁用旋轉}),})},}	
}
</script>

實現效果如下:
在這里插入圖片描述

4.在地圖中添加標記點與多邊形

預覽效果:
在這里插入圖片描述

4.1 renderjs方法調用
4.1.1 點擊調用

頁面中點擊按鈕調用renderjs中的方法,使用ol.方法名的方式進行調用,其中ol取標簽<script module="ol" lang="renderjs">中module定義的名稱,可調用的方法則需在methods: {...}內。
以示例中的按鈕為例,點擊按鈕調用定位中心標記點: @click="ol.handleCenterIcon"

<view class="btn-area_item" @click="ol.handleCenterIcon">中心標記
</view>

此處需要特別注意在APP端可能出現點擊后調用無效的問題,主要原因可能有:

  1. 寫法不符合“僅支持內聯事件”的限制;
  2. 節點被其它組件(slot、v-if、自定義組件、三方組件)重新包裹,導致編譯期找不到模塊前綴。

其根本原因是:事件指令被編譯器識別失敗時,框架不會把點擊消息派發到 renderjs 層。
App 端 renderjs “無法點擊” 99% 都是寫法或節點被轉包導致編譯器沒有生成視圖層事件綁定,只要嚴格遵循:行內綁定、模塊前綴、裸節點,三條規則,點擊事件即可在 Android、iOS 真機正常調用renderjs 里的方法。

4.1.2 監聽值變化時調用

在相關塊級元素的節點上綁定監聽數據與監聽變化的目標方法。以示例中的gps值獲取為例,監聽設備gps值變化并調用renderjs中的getGps()方法:

<view id="map" class="map_x" :style="{height:mapHeightStr}" :gps="deviceGps" :change:gps="ol.getGps">
</view>

其中:gps=綁定需要監聽的目標值,:change:gps=綁定監聽后調用的方法。getGps()方法中則可接收新舊值,如getGps(newValue, oldValue)

4.2 繪制標記點與多邊形范圍
4.2.1 繪制標記點圖標

目前在renderjs中需要使用圖標等圖片資源時,無法直接使用圖標的路徑進行處理,需要將圖標資源轉化為base64后再引入使用,以當前處理的方式為例:在地圖組件文件夾內創建一個resource.js文件,在文件中定義好圖標資源的名稱,如const localtion = ""data:image/png;base64,iVBORw0KG...";,最后再統一export供renderjs進行使用。

繪制圖標的主要方法如下:

// 定位中心點
handleCenterIcon() {let vectorSource = new VectorSource();// 全局定義,方便后續進行其他操作,如清除等this.iconLayer = new VectorLayer({source: vectorSource,})// 添加圖層this.map.addLayer(this.iconLayer)// 設置圖片位置this.iconFeature = new Feature({geometry: new Point([this.gps.lng, this.gps.lat]),});// 設置圖標的資源與放大倍率this.iconFeature.setStyle(new Style({image: new Icon({src: imgs.navUp,scale: 0.5,}),}));// 將圖片Feature添加到Sourcethis.iconLayer.getSource().addFeature(this.iconFeature)
}

此時,圖標即可添加到地圖上,但此時的圖標不會隨著地圖的縮放而變化比例,針對實際的使用場景,需監聽地圖縮放,從而改變圖標的顯示比例。

監聽地圖縮放并改變圖標顯示比例的方法如下:

// 監聽地圖縮放,處理中心圖標的相對大小
zoomChange() {let that = this// 監聽縮放this.map.getView().on("change:resolution", function(res) {// 縮放大小let autoZoom = res.target.values_.zoom;that.realZoom = autoZoom;// 存在圖標時,獲取圖標的顯示倍率并按比例調整if (that.iconFeature) {let style = that.iconFeature.getStyle()style.getImage().setScale(autoZoom / 20);that.iconFeature.setStyle(style)}})
}

以上監聽方法可以在地圖初始化完成后進行調用。

4.2.2 繪制多邊形范圍

在繪制多邊形時需要使用坐標數組,需根據業務實際提供的坐標,使用fromLonLat進行統一處理,并設置坐標系為EPSG:4326EPSG:3857等其他坐標系。

// 循環轉換坐標點
for (let i = 0; i < len; i++) {let point = fromLonLat([path[i].lng, path[i].lat], 'EPSG:4326')finalPath.push(point)
}

4326 是“經緯度(度)”,3857 是“Web 墨卡托(米)”;

坐標系EPSG 編碼單位用途特點天地圖服務中的標識
WGS84 經緯度EPSG:4326全球通用,適合存儲與計算,適合 GPS 原始坐標、OGC WFS/WMS 查詢TILEMATRIXSET=c
Web 墨卡托投影EPSG:3857網絡地圖通用,適合瓦片拼接、前端展示TILEMATRIXSET=w

兩坐標系在 OpenLayers 中的互轉:

import {fromLonLat, toLonLat} from 'ol/proj';
// EPSG:4326 → EPSG:3857
const webMercator = fromLonLat([116.391, 39.907]);
// EPSG:3857 → EPSG:4326
const lonLat = toLonStr(webMercator);

繪制多邊形范圍的方法如下:

// 繪制任務路徑范圍
handlePolygon() {// 測試用路徑let path = [{lng: 118.050,lat: 24.621},{lng: 118.083,lat: 24.624},{lng: 118.084,lat: 24.685},{lng: 118.055,lat: 24.686},];let finalPath = [];let len = path.length;// 無路徑時直接返回,不渲染if (len == 0) {return false}// 循環轉換坐標系for (let i = 0; i < len; i++) {let point = fromLonLat([path[i].lng, path[i].lat], 'EPSG:4326');finalPath.push(point);}// 配置多邊形let polygon = new Polygon([finalPath]);let polygonFeature = new Feature({geometry: polygon,});let source = new VectorSource({features: [polygonFeature]});// 配置多邊形樣式let geoLayer = new VectorLayer({source: source,style: new Style({stroke: new Stroke({color: '#28dd98',width: 2}),// 描邊fill: new Fill({color: '#25C67A50',})//填充范圍顏色})});this.geoLayerValue = geoLayer;this.map.addLayer(geoLayer);
}
4.3完整示例
<template><view><view id="map" class="map_x" :style="{height:mapHeightStr}" :gps="deviceGps" :change:gps="ol.getGps"></view><view class="btn-area"><view class="btn-area_item" @click="ol.handleCenterIcon">中心標記</view><view class="btn-area_item" @click="ol.handlePolygon">顯示范圍按鈕</view></view></view>
</template><script>export default {props: {mapHeight: {type: [Number, String],default: 220}},data() {return {deviceGps: null}},computed: {mapHeightStr() {function isNumber(val) {return typeof val === 'number' && Number.isFinite(val);}if (isNumber(this.mapHeight)) {return this.mapHeight + 'px'} else {return this.mapHeight}}},mounted() {this.getDeviceGps()},methods: {// 獲取手機當前定位getDeviceGps(isShowTip = false) {let that = thisif (isShowTip) {wx.showLoading({title: '獲取當前定位中...'})}try {uni.getLocation({type: 'wgs84',geocode: true, //設置該參數為true可直接獲取經緯度及城市信息success: function(res) {wx.hideLoading()// 此時先賦值時間戳,強制使坐標發生改變,從而觸發監聽方法that.deviceGps = {lng: new Date().getTime(),lat: new Date().getTime()}// 模擬中點坐標let gps = {lat: 24.621, // res.latitude,lng: 118.050 //res.longitude}that.deviceGps = gpsthat.$emit("initGps", gps)},fail: function() {wx.hideLoading()uni.showToast({title: '獲取地址失敗,將導致部分功能不可用',icon: 'none'});}});} catch (err) {wx.hideLoading()}},}}
</script>
<script module="ol" lang="renderjs">import {imgs} from "./resource.js"import {Map,View} from 'ol';import TileLayer from 'ol/layer/Tile';import Tile from 'ol/layer/Tile';import VectorSource from 'ol/source/Vector';import {Vector as VectorLayer,} from 'ol/layer';import XYZ from 'ol/source/XYZ'import {defaults as defaultControls,} from 'ol/control';// 轉化經緯度import {transform,fromLonLat} from 'ol/proj';// 繪制形狀import {LineString,Point,Polygon,Circle as CircleGeo} from 'ol/geom';import Feature from 'ol/Feature';import {Fill,Stroke,Style,Icon,Circle,Text} from 'ol/style'export default {name: 'gis-map',data() {return {map: null, //地圖對象gps: null, //當前定位// 地圖渲染控件vectorSource: null,vectorLayer: null,iconLayer: null,geoLayerValue: null}},mounted() {this.initMap()},methods: {/*** 初始化地圖*/initMap() {this.vectorSource = new VectorSource();this.vectorLayer = new VectorLayer({source: this.vectorSource,});// 引入天地圖let source = new XYZ({url: '***你的天地圖資源地址***',})let tileLayer = new Tile({title: '天地圖',source: source,})// 標注圖層(行政區名稱,道路)let sourceMark = new XYZ({url: '***你的天地圖資源地址***',})let tileMark = new Tile({title: '標注圖層',source: sourceMark,})// 地圖實例this.map = new Map({controls: defaultControls({attribution: false,zoom: true,rotate: false,}),target: 'map', // 對應頁面里 id 為 map 的元素layers: [tileLayer, tileMark], view: new View({// 地圖視圖projection: 'EPSG:4326', // 坐標系,有EPSG:4326和EPSG:3857center: [118.050, 24.621], // 中心坐標zoom: 12, // 地圖縮放級別(打開頁面時默認級別)minZoom: 1, // 地圖縮放最小級別maxZoom: 18,enableRotation: false, //禁用旋轉}),})// 啟動地圖縮放監聽this.zoomChange()},// 調整中心點changeMapCenter(gps, zoom = 12) {if (this.map) {let view = this.map.getView();view.setZoom(zoom);view.setCenter([gps.lng, gps.lat],"EPSG:4326");this.map.render();}},// 接收GPS數據getGps(newValue, oldValue) {console.log("接收GPS數據", newValue);if (newValue != undefined) {this.gps = newValueif (this.map) {this.changeMapCenter(this.gps, 15)} else {this.initMap()}}},// 繪制中心點圖標handleCenterIcon() {if (this.iconLayer) {// 移除圖層this.map.removeLayer(this.iconLayer)this.iconLayer = null}let vectorSource = new VectorSource();this.iconLayer = new VectorLayer({source: vectorSource,})// 添加圖層this.map.addLayer(this.iconLayer)// 設置圖片位置this.iconFeature = new Feature({geometry: new Point([this.gps.lng, this.gps.lat]),});this.iconFeature.setStyle(new Style({image: new Icon({src: imgs.navUp,scale: 0.5,}),}));// 將圖片Feature添加到Sourcethis.iconLayer.getSource().addFeature(this.iconFeature)},// 監聽地圖縮放,處理中心圖標的相對大小zoomChange() {let that = thisthis.map.getView().on("change:resolution", function(res) {let autoZoom = res.target.values_.zoom;that.realZoom = autoZoom;// console.log("縮放事件", autoZoom, that.pointIconList);if (that.iconFeature) {let style = that.iconFeature.getStyle()style.getImage().setScale(autoZoom / 20);that.iconFeature.setStyle(style)}})},// 繪制任務路徑范圍handlePolygon() {if (this.geoLayerValue) {this.map.removeLayer(this.geoLayerValue);this.geoLayerValue = null;}let path = [{lng: 118.050,lat: 24.621},{lng: 118.083,lat: 24.624},{lng: 118.084,lat: 24.685},{lng: 118.055,lat: 24.686},];let finalPath = [];let len = path.length;if (len == 0) {return false}// 循環轉換坐標系for (let i = 0; i < len; i++) {let point = fromLonLat([path[i].lng, path[i].lat], 'EPSG:4326');finalPath.push(point);}// 配置多邊形let polygon = new Polygon([finalPath]);let polygonFeature = new Feature({geometry: polygon,});let source = new VectorSource({features: [polygonFeature]})let geoLayer = new VectorLayer({source: source,style: new Style({stroke: new Stroke({color: '#28dd98',width: 2}),fill: new Fill({color: '#25C67A50',})})})this.geoLayerValue = geoLayer;this.map.addLayer(geoLayer);},}}
</script>

5.其他地圖操作方法

在Uniapp環境中,OpenLayers的操作與在Web端中的使用方法基本一致,在此不再贅述,可參考文章OpenLayers學習記錄或OpenLayers 官網。

四、設備定位軌跡處理

在實際應用場景中,根據設備的實時位置繪制軌跡路線也較為常見,但由于renderjs的限制,以下僅僅介紹App端與H5端可實現的方案。
預覽效果:
在這里插入圖片描述

1.設備定位監聽方法

在App端,可使用plus.geolocation.watchPosition(successCB, errorCB, option)方法對設備定位進行定時查詢。

const watchPosition = plus.geolocation.watchPosition(function(pos) {console.log("==當前定位數據==", JSON.stringify(pos.coords));// 獲取當前定位的同時,更新中心點位置與方向console.log("==當前方向==", pos.coords.heading);console.log("==當前經緯度==", pos.coords.longitude, pos.coords.latitude);
}, function(error) {console.error(`地理信息報錯ERROR: ${error.message}`);
}, {enableHighAccuracy: true,timeout: 20000,maximumAge: 10000
});

其中返回的pos對象數據有:

屬性描述
coords.latitude十進制數的緯度
coords.longitude十進制數的經度
coords.accuracy位置精度
coords.altitude海拔,海平面以上以米計
coords.altitudeAccuracy位置的海拔精度
coords.heading方向,從正北開始以度計
coords.speed速度,以米/每秒計
timestamp響應的日期/時間

其中option可配置的參數有:

屬性描述
enableHighAccuracy是否使用其最高精度:1. false,默認值,設備會通過更快響應、更少的電量等方法來盡可能的節約資源t;2.true,這會導致較慢的響應時間或者增加電量消耗(比如對于支持 gps 的移動設備來說)
timeout限制返回時間
maximumAge可以返回多長時間(單位毫秒)內的緩存位置

在H5端上,也可以使用navigator.geolocation.watchPosition(success, error, options)方法,其返回參數與配置參數同上。

2.繪制軌跡方法

使用LineString()傳入軌跡數組(格式為:[[經度,緯度],[經度,緯度]…])進行處理。

handleAddPath() {let line = [[118.05, 24.63],[118.050, 24.64],[118.05, 24.65]];let lineString = new LineString(line);let pathFeature = new Feature(lineString);const source = new VectorSource({features: [pathFeature]})// 創建路徑實例this.pathLayer = new VectorLayer({source: source,style: new Style({fill: new Fill({ //填充路徑顏色color: 'rgba(255,255,255,0.5)'}),stroke: new Stroke({color: '#d70f19',width: 3})})})this.map.addLayer(this.pathLayer)console.log("路徑渲染完成", this.distance);
},

3.軌跡路徑長度計算

使用2.繪制軌跡方法中的lineString 進行計算。

handlePathLength(lineString) {let length = lineString.getLength({projection: 'EPSG:4326' // 確定坐標系類型})let output = ''output = Math.round(length * 1000) / 10 //km 保留一位小數console.log("路徑距離", output);return output
}

4.監聽陀螺儀判斷設備指北方向

下載依賴

npm i kompas

創建Kompas實例后監聽方向,并設置箭頭圖標的方向:

// 陀螺儀變化監聽,改變中心箭頭方向
const compass = new Kompas();
compass.watch();
compass.on('heading', function(heading) {console.log("陀螺儀變化", heading);let style = that.iconFeature.getStyle()style.getImage().setRotation(Math.PI / 180 * heading);that.iconFeature.setStyle(style)
});

上述方法非主流設備監聽方法,可能存在問題,需謹慎使用。歡迎大佬們指正!也歡迎討論相關可行的解決方案!

5.完整示例

<template><view><view id="map" class="map_x" :style="{height:mapHeightStr}" :gps="deviceGps" :change:gps="ol.getGps"></view><view class="btn-area"><view class="btn-area_item" @click="ol.handleCenterIcon">中心標記</view><view class="btn-area_item" @click="ol.handlePolygon">顯示范圍按鈕</view><view class="btn-area_item" @click="ol.handleStart">隨機定位繪制路徑</view><view class="btn-area_item" @click="ol.handleStop">停止隨機定位繪制路徑</view></view></view>
</template><script>export default {props: {mapHeight: {type: [Number, String],default: 220},// 地圖多邊形路徑mapPathLatLan: {type: Array,default: () => {return []}},// 點坐標pointLatLng: {type: Array,default: () => {return []}},},data() {return {deviceGps: null}},computed: {mapHeightStr() {function isNumber(val) {return typeof val === 'number' && Number.isFinite(val);}if (isNumber(this.mapHeight)) {return this.mapHeight + 'px'} else {return this.mapHeight}}},mounted() {this.getDeviceGps()},methods: {// 獲取手機當前定位getDeviceGps(isShowTip = false) {let that = thisif (isShowTip) {wx.showLoading({title: '獲取當前定位中...'})}try {uni.getLocation({type: 'wgs84',geocode: true, //設置該參數為true可直接獲取經緯度及城市信息success: function(res) {wx.hideLoading()that.deviceGps = {lng: new Date().getTime(),lat: new Date().getTime()}let gps = {lat:  res.latitude,lng: res.longitude}that.deviceGps = gpsthat.$emit("initGps", gps)},fail: function() {wx.hideLoading()uni.showToast({title: '獲取地址失敗,將導致部分功能不可用',icon: 'none'});}});} catch (err) {wx.hideLoading()}},emitDistance(distance) {this.$emit("changeDistance", distance)},showToast(title) {uni.showToast({icon: 'none',title: title})},}}
</script>
<script module="ol" lang="renderjs">import {imgs} from "./resource.js"import {Map,View} from 'ol';import TileLayer from 'ol/layer/Tile';import Tile from 'ol/layer/Tile';import VectorSource from 'ol/source/Vector';import {Vector as VectorLayer,} from 'ol/layer';import XYZ from 'ol/source/XYZ'import {defaults as defaultControls,} from 'ol/control';// 轉化經緯度import {transform,fromLonLat} from 'ol/proj';// 繪制形狀import {LineString,Point,Polygon,Circle as CircleGeo} from 'ol/geom';import Feature from 'ol/Feature';import {Fill,Stroke,Style,Icon,Circle,Text} from 'ol/style';import Kompas from 'kompas';export default {name: 'gis-map',data() {return {map: null, //地圖對象gps: null, //當前定位,// 巡檢線條路徑patrolPath: [],// 巡檢距離distance: 0,// 地圖渲染控件vectorSource: null,vectorLayer: null,iconLayer: null,iconFeature: null,geoLayerValue: null,// 監聽方法watchPosition: null,}},mounted() {this.initMap()},methods: {/*** 初始化地圖*/initMap() {this.vectorSource = new VectorSource();this.vectorLayer = new VectorLayer({source: this.vectorSource,});// 引入天地圖let source = new XYZ({url: '***你的天地圖資源地址***',})let tileLayer = new Tile({title: '天地圖',source: source,})// 標注圖層(行政區名稱,道路)let sourceMark = new XYZ({url:  '***你的天地圖資源地址***',})let tileMark = new Tile({title: '標注圖層',source: sourceMark,})// 地圖實例this.map = new Map({controls: defaultControls({attribution: false,zoom: true,rotate: false,}),target: 'map', // 對應頁面里 id 為 map 的元素layers: [tileLayer, tileMark], //tileSubwayview: new View({// 地圖視圖projection: 'EPSG:4326', // 坐標系,有EPSG:4326和EPSG:3857center: [118.050, 24.621], // 中心坐標zoom: 12, // 地圖縮放級別(打開頁面時默認級別)minZoom: 1, // 地圖縮放最小級別maxZoom: 18,enableRotation: false, //禁用旋轉}),})// 啟動地圖縮放監聽this.zoomChange();},// 調整中心點changeMapCenter(gps, zoom = 12) {if (this.map) {let view = this.map.getView();view.setZoom(zoom);view.setCenter([gps.lng, gps.lat],"EPSG:4326");this.map.render();}},// 接收GPS數據getGps(newValue, oldValue) {console.log("接收GPS數據", newValue);if (newValue != undefined) {this.gps = newValueif (this.map) {this.changeMapCenter(this.gps, 15)} else {this.initMap()}}},// 定位中心點handleCenterIcon() {this.removeIcon()let vectorSource = new VectorSource();this.iconLayer = new VectorLayer({source: vectorSource,})// 添加圖層this.map.addLayer(this.iconLayer)// 設置圖片位置// #ifdef APPthis.iconFeature = new Feature({geometry: new Point([this.gps.lng, this.gps.lat]),});// #endif// H5端測試使用,實際生產環境與APP相同// #ifdef H5this.iconFeature = new Feature({geometry: new Point([118.050, 24.621]),});// #endifthis.iconFeature.setStyle(new Style({image: new Icon({src: imgs.navUp,scale: 0.5,}),}));// 將圖片Feature添加到Sourcethis.iconLayer.getSource().addFeature(this.iconFeature)},// 移除圖標removeIcon() {if (this.iconLayer) {// 移除圖層this.map.removeLayer(this.iconLayer)this.iconLayer = null}},// 監聽地圖縮放,處理中心圖標的相對大小zoomChange() {let that = thisthis.map.getView().on("change:resolution", function(res) {let autoZoom = res.target.values_.zoom;that.realZoom = autoZoom;// console.log("縮放事件", autoZoom, that.pointIconList);if (that.iconFeature) {let style = that.iconFeature.getStyle()style.getImage().setScale(autoZoom / 20);that.iconFeature.setStyle(style)}})},// 繪制任務路徑范圍handlePolygon() {if (this.geoLayerValue) {this.map.removeLayer(this.geoLayerValue);}let path = [{lng: 118.050,lat: 24.621},{lng: 118.083,lat: 24.624},{lng: 118.084,lat: 24.685},{lng: 118.055,lat: 24.686},];let finalPath = [];let len = path.length;if (len == 0) {return false}// 循環轉換坐標點for (let i = 0; i < len; i++) {let point = fromLonLat([path[i].lng, path[i].lat], 'EPSG:4326');finalPath.push(point);}// 配置多邊形let polygon = new Polygon([finalPath]);let polygonFeature = new Feature({geometry: polygon,});let source = new VectorSource({features: [polygonFeature]})let geoLayer = new VectorLayer({source: source,style: new Style({stroke: new Stroke({color: '#28dd98',width: 2}),fill: new Fill({color: '#25C67A50',})})})this.geoLayerValue = geoLayer;this.map.addLayer(geoLayer);},// 時間開始/暫停/結束handleStart() {console.log("開始繪制路徑")if (this.iconFeature) {// H5端測試使用// #ifdef H5// 存儲定位數據this.patrolPath.unshift([118.050, 24.621]);// #endifthis.handleCenterPoint()} else {this.$ownerInstance.callMethod('showToast', '請先點擊中心標記');}},handleStop() {console.log("停止繪制路徑")// #ifdef APPplus.geolocation.clearWatch(this.watchPosition)// #endif// 測試用,清除隨機模擬定時器clearInterval(this.watchPosition)this.watchPosition = null},// 間隔10秒監聽定位路徑變化handleCenterPoint() {let that = this;let step = 0.01;// 模擬定位路徑變化this.watchPosition = setInterval(() => {const coords = [118.050 + step * Math.random(), 24.621 + step * Math.random()];step = step + 0.01;console.log("==當前定位==", coords[0], coords[1]);this.changeMapCenter({lng: coords[0],lat: coords[1]}, 15)// 動態改變當前定位圖標let newGeometry = new Point(coords);that.iconFeature.setGeometry(newGeometry)that.$ownerInstance.callMethod('emitPath', coords);// 存儲定位數據that.patrolPath.unshift(coords);// 畫路徑that.handleAddPath()}, 1000 * 10)return false// --------------------------------------------// 實際生產環境使用,以下需要真機執行const source = new VectorSource();this.watchPosition = plus.geolocation.watchPosition(function(pos) {console.log("==當前定位數據==", JSON.stringify(pos.coords));// 獲取當前定位的同時,更新中心點位置與方向if (pos.coords.heading) {let style = that.iconFeature.getStyle()style.getImage().setRotation(Math.PI / 180 * (pos.coords.heading || 1));that.iconFeature.setStyle(style)}const coords = [pos.coords.longitude, pos.coords.latitude];// 動態改變當前定位圖標let newGeometry = new Point(coords);that.iconFeature.setGeometry(newGeometry)// 存儲定位數據that.patrolPath.unshift(coords);// 繪制路徑that.handleAddPath()}, function(error) {console.error(`地理信息報錯ERROR: ${error.message}`);uni.showToast({icon: 'none',title: "定位數據獲取異常,將自動重試中"})plus.geolocation.clearWatch(that.watchPosition)setTimeout(() => {that.handleCenterPoint()}, 1500);}, {enableHighAccuracy: true,timeout: 20000,maximumAge: 10000});// 陀螺儀變化監聽,改變中心箭頭方向const compass = new Kompas();compass.watch();compass.on('heading', function(heading) {console.log("陀螺儀變化", heading);let style = that.iconFeature.getStyle()style.getImage().setRotation(Math.PI / 180 * heading);that.iconFeature.setStyle(style)});},// 繪制路徑handleAddPath() {let line = this.patrolPath;let lineString = new LineString(line);let pathFeature = new Feature(lineString);const source = new VectorSource({features: [pathFeature]})this.pathLayer = new VectorLayer({source: source,style: new Style({fill: new Fill({ //填充color: 'rgba(255,255,255,0.5)'}),stroke: new Stroke({color: '#d70f19',width: 3})})})this.map.addLayer(this.pathLayer)console.log("路徑渲染完成", this.distance);// 計算距離并拋出到父組件this.distance = this.handlePathLength(lineString);this.$ownerInstance.callMethod('emitDistance', this.distance);},// 計算長度handlePathLength(line) {let length = line.getLength({projection: 'EPSG:4326'})let output = ''output = Math.round(length * 1000) / 10 + ' 'return output}}}
</script>

參考文檔

  1. Uniapp如何使用renderjs通信(組件通信);
  2. 在Uniapp中使用OpenLayers;
  3. OpenLayers學習記錄,持續更新…;
  4. H5+官網.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/99187.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/99187.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/99187.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從C++開始的編程生活(8)——內部類、匿名對象、對象拷貝時的編譯器優化和內存管理

前言 本系列文章承接C語言的學習&#xff0c;需要有C語言的基礎才能學會哦~ 第8篇主要講的是有關于C的內部類、匿名對象、對象拷貝時的編譯器優化和內存管理。 C才起步&#xff0c;都很簡單&#xff01;&#xff01; 目錄 前言 內部類 性質 匿名對象 性質 ※對象拷貝時的…

MT5追大速率回測BUG

將MT5策略測試器中的回測速率調到最大(最快速度),**確實非常容易導致出現不符合策略邏輯的秒級成交(閃電交易)**。這并非MT5的“bug”,而是由**回測引擎的工作方式**與**策略代碼的編寫方法**在高速運行下不匹配所導致的。 --- ### 為什么最大速率會導致問題? MT5回測…

[數據結構——lesson10.堆及堆的調整算法]

引言 上節我們學習完二叉樹后[數據結構——lesson9.二叉樹]&#xff0c;這節我們將學習數據結構——堆 學習目標 1.堆的概念及結構 堆是一種特殊的完全二叉樹結構&#xff0c;在計算機科學和數據結構中廣泛應用&#xff0c;特別是在堆排序算法和優先隊列的實現中&#xff0c;…

九識智能與北控北斗合作研發的L4級燃氣超微量高精準泄漏檢測無人車閃耀服貿會,守護城市安全

2025年9月10日至14日&#xff0c;2025年中國國際服務貿易交易會將于北京首鋼園舉辦。在這場國際盛會上&#xff0c;九識智能與北京北控北斗科技投資有限公司&#xff08;以下簡稱“北控北斗”&#xff09;合作研發的L4級燃氣超微量高精準泄漏檢測無人車及相關系統解決方案&…

【C語言入門】手把手教你實現順序棧

棧是計算機科學中最基礎且重要的數據結構之一&#xff0c;它遵循"后進先出"&#xff08;LIFO&#xff09;的原則。想象一下一疊盤子&#xff0c;你只能從最上面取放&#xff0c;這就是棧的直觀體現。本文將用C語言帶你一步步實現一個順序棧&#xff0c;即使你是編程小…

北斗導航 | ARAIM(高級接收機自主完好性監測)算法在民航LPV-200進近中的具體實現流程

要詳細說明ARAIM(高級接收機自主完好性監測)算法在民航LPV-200進近中的具體實現流程,需結合ARAIM的核心邏輯(多星座融合、多假設解分離、風險優化分配)與LPV-200的嚴格要求(垂直保護級VPL≤35米、垂直告警限VAL=35米、有效監測門限EMT≤15米等),以下是 step-by-step 的…

AIPex:AI + 自然語言驅動的瀏覽器自動化擴展

AIPex:AI + 自然語言驅動的瀏覽器自動化擴展 引言 一、快速上手 1.1 安裝AIPex擴展 1.2 首次配置 1.3 界面介紹 第二章:30+工具詳解 2.1 標簽頁管理工具集 ??? **get_all_tabs - 全局標簽頁概覽** ?? **switch_to_tab - 智能標簽頁切換** ?? **標簽頁批量操作** ?? …

機器學習模型可信度與交叉驗證:通俗講解

先從一個故事說起&#xff1a;農場里的火雞科學家&#xff0c;觀察了一年發現“每天上午11點必有食物”&#xff0c;結果感恩節當天&#xff0c;它沒等到食物&#xff0c;反而成了人類的食物。這個故事告訴我們&#xff1a;只靠過去的經驗下結論&#xff0c;很可能出錯——機器…

HTML5和CSS3新增的一些屬性

1、HTML5新增特性這些新特性都有兼容性問題&#xff0c;基本是IE9以上版本瀏覽器才支持1&#xff09;新增語義化標簽2&#xff09;新增多媒體標簽音頻&#xff1a;<audio>視頻&#xff1a;<video>&#xff08;1&#xff09;視頻<video>---盡量使用mp4格式<…

Redis的RedLock

RedLock算法深度解析RedLock是Redis作者針對分布式環境設計的多節點鎖算法&#xff0c;核心目標是解決單點Redis在分布式鎖場景中的可靠性缺陷。傳統方案的局限性單節點Redis鎖的問題單點故障&#xff1a;單個Redis實例宕機導致所有鎖服務不可用可靠性不足&#xff1a;無法保證…

SpringMVC @RequestMapping的使用演示和細節 詳解

目錄 一、RequestMapping是什么&#xff1f; 二、RequestMapping 的使用演示 1.RequestMapping在方法上的使用&#xff1a; 2.RequestMapping同時在類和方法上使用&#xff1a; 3.RequestMapping指定請求參數&#xff1a; 4.RequestMapping使用Ant風格URL&#xff1a; 5.Requ…

flutter項目 -- 換logo、名稱 、簽名、打包

1、換logo, 透明底&#xff0c;下面5個尺寸&#xff0c;需要UI設計2、換名沒配置型的改名方式如下 打開app/src/main/AndroidManifest.xml3、簽名 運行 flutter doctor -vD:\project\Apk\keystore 自己建立的keystore文件夾&#xff0c; 注意命令后是 megoai-release-key(自…

【貪心算法】day9

&#x1f4dd;前言說明&#xff1a; 本專欄主要記錄本人的貪心算法學習以及LeetCode刷題記錄&#xff0c;按專題劃分每題主要記錄&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代碼&#xff1b;&#xff08;2&#xff09;優質解法 優質代碼&#xff1b;&#xff…

linux C 語言開發 (八) 進程基礎

文章的目的為了記錄使用C語言進行linux 開發學習的經歷。開發流程和要點有些記憶模糊&#xff0c;趕緊記錄&#xff0c;防止忘記。 相關鏈接&#xff1a; linux C 語言開發 (一) Window下用gcc編譯和gdb調試 linux C 語言開發 (二) VsCode遠程開發 linux linux C 語言開發 (…

從零學算法1094

1094.拼車 車上最初有 capacity 個空座位。車 只能 向一個方向行駛&#xff08;也就是說&#xff0c;不允許掉頭或改變方向&#xff09; 給定整數 capacity 和一個數組 trips , trips[i] [numPassengersi, fromi, toi] 表示第 i 次旅行有 numPassengersi 乘客&#xff0c;接他…

B2B企業營銷型AI Agent服務商推薦:誰更專業?如何選型?

一、引言&#xff1a;為什么B2B企業需要營銷型AI Agent&#xff1f;在當前競爭激烈的B2B市場中&#xff0c;企業普遍面臨幾大挑戰&#xff1a;線索獲取難&#xff1a;獲客成本持續上升&#xff0c;高質量線索難以篩選。銷售周期長&#xff1a;從初步接觸到簽單&#xff0c;往往…

算法-雙指針5.6

目錄 &#x1f33f;力扣611-有效三角形得個數 &#x1f9ca;題目鏈接&#xff1a;https://leetcode.cn/problems/valid-triangle-number/description/ &#x1f9ca;題目描述&#xff1a;?編輯 &#x1f9ca;解題思路&#xff1a; &#x1f9ca;解題代碼&#xff1a; &a…

超參數自動化調優指南:Optuna vs. Ray Tune 對比評測

點擊 “AladdinEdu&#xff0c;同學們用得起的【H卡】算力平臺”&#xff0c;注冊即送-H卡級別算力&#xff0c;80G大顯存&#xff0c;按量計費&#xff0c;靈活彈性&#xff0c;頂級配置&#xff0c;學生更享專屬優惠。 引言&#xff1a;從"手動煉丹"到"自動化…

軟考-局域網基礎考點總結

這篇文章用于整理軟考網絡相關的知識點&#xff0c;囊括了詳細的局域網基礎的考點&#xff0c;能夠讓你認真備考&#xff0c;基礎知識一網打盡&#xff0c;讓后續的學習更加通暢~ 第一部分&#xff1a;OSI七層參考模型 OSI(Open System Interconnection)模型是一個理論框架&am…

Node.js核心模塊介紹

1. fs 模塊fs&#xff08;File System&#xff09;模塊允許對文件系統進行操作&#xff0c;提供了文件讀寫、文件夾操作等功能。fs 支持同步和異步兩種 API。1.1. 常用方法讀取文件&#xff1a;異步: fs.readFile()同步: fs.readFileSync()寫入文件&#xff1a;異步: fs.writeF…