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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…