uniapp-renderjs加载高德地图实现点聚合,绘制边界

2,174 阅读3分钟

uniapp 自带的map标签有时候不满足项目需求,就要在app中加载高德地图来实现更多的地图操作,而在uniapp中不能操作dom,所以只能用uniapp提供的render.js来实现加载高德地图了。


什么是renderjs?

renderjs是一个运行在视图层的js。他比wsx更加强大。它只支持app-vue和web.

renderjs的主要作用有2个:

  1. 大幅降低逻辑层和视图层的通讯损耗,提供高性能视图交互能力
  2. 在视图层操作dom,运行 for web 的 js库

image.png

功能详解

  • 大幅降低逻辑层和视图层的通讯损耗,提供高性能视图交互能力

uni-app的app端逻辑层和视图层是分离的,这种机制有很多好处,但也有一个副作用是在造成了两层之间通信阻塞。尤其是App的Android端阻塞问题影响了高性能应用的制作。

renderjs运行在视图层,可以直接操作视图层的元素,避免通信折损。

在hello uni-app的canvas示例中,App端使用了renderjs,由运行在视图层的renderjs直接操作视图层的canvas,实现了远超微信小程序的流畅canvas动画示例。具体在hello uni-app示例中体验,对比App端和小程序端的性能差异。

  • 在视图层操作dom,运行for web的js库

官方不建议在uni-app里操作dom,但如果你不开发小程序,想使用一些操作了dom、window的库,其实可以使用renderjs来解决。

在app-vue环境下,视图层由webview渲染,而renderjs运行在视图层,自然可以操作dom和window。

这是一个基于renderjs运行echart完整版的示例:renderjs版echart

同理,f2threejs等库都可以这么用。

renderjs说完了说说高德地图的功能实现

image.png

实现效果:区域内打点,点击点位弹窗显示详细信息。

<template>
	<view>
		<view id="amap" :style="mapStyle" class="amap-style" :markers="markers" :change:markers="amap.updateMarker"></view>
	</view>
</template>

<!-- 高德地图 renderjs视图层 -->
<script module="amap" lang="renderjs">
	import { geojson } from '@/utils/geojson.js';
let map = undefined,
	cluster = undefined;
export default {
	data() {
		return {
			isMapLoadSuccess: false
		}
	},
	mounted() {
		console.log('renderjs mounted');
		// 清除地图数据
		this.reset();
		// 注意:异步回调函数的声明应该在 JSAPI 引入之前,函数名与callback=onLoad中对应
		if (!window.isOnRady) {
			window.isOnRady = () => {
				this.init();
			};
		} else {
			this.init();
		}
		// 异步加载高德地图 JSAPI
		if (!window.AMap) {
			this.loadAMap();
		}
	},
	methods: {
		// 异步加载高德地图 JSAPI
		loadAMap() {
			// 动态异步引入较大类库避免影响页面展示
			const myKey = '你的key';
			const myCode = "你的秘钥";
			const url = `https://webapi.amap.com/maps?v=2.0&key=${myKey}&callback=isOnRady`;
			const jsapi = document.createElement('script');
			jsapi.charset = 'utf-8';
			jsapi.src = url;
			document.head.appendChild(jsapi);
			// 安全密钥
			window._AMapSecurityConfig = {
				securityJsCode: myCode,
			};
			console.log('加载高德地图插件');
		},
		// 初始化
		init() {
			map = new AMap.Map('amap', this.mapDefaultOptions);

			map.on('complete', () => {
				console.log('地图加载完成');
				this.isMapLoadSuccess = true;
				this.initMarkers();
				this.drawBounds()
			})
		},
		// 地图相关数据初始化
		reset() {
			map && (map = undefined);
			// 如果有之前的数据,先清除
			if (cluster) {
				console.log('清除点聚合');
				cluster.setMap(null);
				cluster = undefined;
			}
		},
		// 生成点聚合点位
		initMarkers() {
			// 添加判断,防止地图没有加载完成就渲染
			if (!(this.isMapLoadSuccess && this.markers.length)) {
				return
			}
			// 根据官方示例数据形式
			const point = this.markers;
			// 先调整地图中心点
			const center = point[0].lnglat || this.mapDefaultOptions.center;
			map.setCenter(center);
			// 加载点聚合插件
			map.plugin(["AMap.MarkerCluster"], () => {
				cluster = new AMap.MarkerCluster(map, point, {
					gridSize: 80, // 聚合网格像素大小
					renderClusterMarker: (context) => { // 对聚合点位的渲染
						// console.log('context:', context);  // 入参中有四个数据 count data indexs marker
						// 修改聚合点位样式
						this.createClusterMarker(context);
						// 设置聚合点位点击事件
						const marker = context.marker;
						marker.on('click', (e) => {
							let mapZoom = map.getZoom();
							if (mapZoom < 20) {
								mapZoom += 2;
							}
							map.setZoomAndCenter(mapZoom, e.lnglat);
						})
					},
					renderMarker: ({
						data,
						marker
					}) => { // 对非聚合点位的渲染
						// console.log(context);  // 入参中有四个数据 count data indexs marker
						// 获取自己设置的name
						if(data[0].name) {
							// console.log(data[0].name);
						}
						marker.setIcon(this.creatAMapIcon());
						marker.setAnchor('bottom-center');
						// 设置非聚合点位点击事件
						marker.on('click', (e) => {
							map.setCenter(e.lnglat);
							this.tapMarker(data);


						})
					}
				});
			});
		},

		// 更新数据
		updateMarker(newVal) {
			console.log('renderjs 更新:', newVal.length);
			this.initMarkers();
		},
		// 生成高德地图Icon
		creatAMapIcon(type) {
			const iconOptions = {
				image: 'http://safety-1257229566.cos.ap-beijing.myqcloud.com/2024/08/29/3387843080304d6db8aa8545b50983bf.png',
				imageSize: new AMap.Size(45, 45)
			}
			return new AMap.Icon(iconOptions);
		},
		// 聚合点位样式(采用高德地图)
		createClusterMarker(context) {
			const count = this.markers.length + 1000;
			const factor = Math.pow(context.count / count, 1 / 18);
			const div = document.createElement('div');
			const Hue = 180 - factor * 180;
			const bgColor = 'red';
			const fontColor = 'hsla(' + Hue + ',100%,90%,1)';
			const borderColor = '#f2b1b1';
			const shadowColor = '#f2b1b1';
			div.style.backgroundColor = bgColor;
			const size = Math.round(20 + Math.pow(context.count / count, 1 / 5) * 20);
			div.style.width = div.style.height = size + 'px';
			div.style.border = 'solid 1px ' + borderColor;
			div.style.borderRadius = size / 2 + 'px';
			div.style.boxShadow = '0 0 5px ' + shadowColor;
			div.innerHTML = context.count;
			div.style.lineHeight = size + 'px';
			div.style.color = fontColor;
			div.style.fontSize = '14px';
			div.style.textAlign = 'center';
			context.marker.setOffset(new AMap.Pixel(-size / 2, -size / 2));
			context.marker.setContent(div);
		},
		// 非聚合marker点击事件
		tapMarker(data) {
				console.log('点位信息',data)
				this.openInfo(data)
		},
		// 自定义弹窗
		   openInfo(data) {
		        //构建信息窗体中显示的内容
		        var info = [];
		       info.push("<div style="background: white;width: 150px;padding: 10px;border-radius: 10px">\n" +
		                 `      <div style="font-size: 12px;width: 150px;">\n` +
		                 `       <div style="font-weight: 600;color:red;">${data[0].name}</div>\n` +
		                 `        <div style="margin: 5px 10px;">负责人: ${data[0].leadingPerson}</div>\n` +
						 `        <div style="margin: 5px 10px;">电话: ${data[0].leadingPhone}</div>\n` +
						 `        <div style="margin: 5px 10px;">归属地: ${data[0].belongTo}</div>\n` +
		                 "      </div>\n" +
		                 "    </div>");
		       const infoWindow = new AMap.InfoWindow({
				    isCustom: false,// 是否自定义窗体
				    anchor: false,// 信息锚点
					autoMove:false,// 是否自动调整窗体到视野内
		            content: info.join(""),// 显示内容,可以是HTML要素字符串或者HTMLElement对象
					offset: new AMap.Pixel(-10, -70),// 点位偏移
		        });
					let loglat = data[0].geo.split(",");
					let position = new AMap.LngLat(loglat[0], loglat[1]);// 经纬度坐标,用来描述地图上的一个点位置
		        infoWindow.open(map ,position);
		    },
		// 根据边界数据绘制区域区域边界数据
		drawBounds(){
	let coordinates = geojson.features[0].geometry.coordinates;
	let boundsMap = coordinates[0].map((item) => {
		return new AMap.LngLat(item[0], item[1]);
	});
	let bounds = [boundsMap];
	if (bounds) {
		
		for (var i = 0; i < bounds.length; i += 1) {
			//构造MultiPolygon的path
			bounds[i] = [bounds[i]];
		}

		let polygon = new AMap.Polygon({
			strokeWeight: 1,
			path: bounds,
			fillOpacity: 0.4,
			fillColor: 'rgba(200, 245, 239, 1)',
			strokeColor: '#3BC4B1'
		});
		map.add(polygon);
		map.setFitView(polygon); //视口自适应
	}
}


	},
}
</script>

<script>

export default {
	name: 'DetailsMap',
	data() {
		return {
			show: false,
			windowHeight: 0,
			windowWidth: 0,
			markers: [],
			form: {},
			mapDefaultOptions: {
				center: [120.7145, 41.5783],
				zoom: 14,
				resizeEnable: false //窗口大小调整
			}
		};
	},
	computed: {
		mapStyle() {
			return {
				height: this.windowHeight + 'px'
			};
		}
	},
	onLoad(option) {
		console.log('service onLoad');
		this.windowHeight = uni.getSystemInfoSync().windowHeight;
		this.windowWidth = uni.getSystemInfoSync().windowWidth;
		uni.onWindowResize((res) => {
			// 监听屏幕尺寸变化
			this.windowHeight = res.size.windowHeight;
			this.windowWidth = res.size.windowWidth;
		});
		this.getMarkList();
	},
	methods: {
		
		// 模拟接口请求数据
		 getDataApi() {
		 	setTimeout(() => {
		 		this.markers = [
		 			{
						weight: 8,
		 				lnglat: ['108.939621', '34.343147'],
		 				extData: {
		 					// 自定义携带的其他数据
		 					name: '123'
						}
		 			},
		 			{
		 				weight: 1,
		 				lnglat: ['113.370643', '22.938827']
		 			},
		 			{
		 				weight: 1,
		 				lnglat: ['112.985037', '23.15046']
		 			},
		 			{
		 				weight: 1,
		 				lnglat: ['110.361899', '20.026695']
		 			},
		 			{
		 				weight: 1,
		 				lnglat: ['121.434529', '31.215641']
		 			}
		 		];
		 	}, 1000);
		 }
	}
};
</script>

<style lang="scss" scoped>
/* 去除高德logo */
::v-deep .amap-logo {
	display: none !important;
}
/* 去掉高德的版本号 */
::v-deep .amap-copyright {
	opacity: 0 !important;
}
</style>