【天地图】VUE3基于Leaflet.js部分功能hooks封装useLeafletMap.js

引言:这里以天地图作为底图使用leaflet.js封装部分地图常用功能,前篇有写过纯天地图封装hooks,所以为什么又换技术栈了呢?起因是为解决天地图渲染十万条数据卡顿问题,自从用了leaflet渲染后就没那么折腾了,使用canvas矢量图层绘制这加载速度也就解决了,心情也舒畅了~除了查leaflet相关攻略大部分都是收费一说,难顶。经过自己开发体验顺便也说一下纯天地图与leaflet的优缺点和感受吧,放在了文章最后,这部分也不重要,仅供参考!

言归正传,仅根据个人习惯封装,仅供参考,内部默认配置项请根据项目实际情况自行配置调整。

leaflet中文文档参考地址1:Documentation - Leaflet - 一个交互式地图 JavaScript 库

 leaflet中文文档参考地址2:https://leafletjs.cn/reference.htmlLeaflet 1.9.3 中文文档 - 帮助手册&教程 - 《leaflet 中文文档 - 帮助手册 - 教程》 - 极客文档https://leafletjs.cn/reference.html

天地图API地址:天地图API

vue3引入leaflet参考地址vue3.0使用leaflet_leaflet vue3-CSDN博客

各方法详细说明均在代码里体现,各方法参数配置参考leaflet文档对应功能,首先创建一个hooks——useLeafletMap.js

/**
 * leaflet hooks
 * params:
 *  id: 地图绑定的html标签id,具有唯一性
 *  mapOptions: 地图初始化配置
 * tips:
 * 1. 以下关于coordinate默认格式调整:{ lng: lng, lat: lat }
 * 2. 入参默认为Null的为非必须传参参数
 */
import { createVNode, render } from 'vue'
import L from 'leaflet'
import 'leaflet.chinatmsproviders'
import 'leaflet/dist/leaflet.css'

export const useLeafletMap = (id) => {
	let map = null // 地图对象
	// 地图初始化,mapOption:自定义地图配置
	const mapInit = (mapOption = {}) => {
		//天地图key
		const TDT_KEY = '您申请的天地图key';
		const normalm = L.tileLayer.chinaProvider('TianDiTu.Normal.Map', {
			key: TDT_KEY,
			maxZoom: 18,
			minZoom: 5
		})
		const normala = L.tileLayer.chinaProvider('TianDiTu.Normal.Annotion', {
			key: TDT_KEY,
			maxZoom: 18,
			minZoom: 5
		})
		const normal = L.layerGroup([normalm, normala])
		// 地图默认配置
		const defaultMapOption = {
			renderer: L.canvas(), // 默认矢量图层绘制方法
			center: [37.03, 111.92],
			zoom: 16,
			layers: [normal],
			zoomControl: false, // 缩放组件
			logoControl: false, // 去掉logo
			attributionControl: false, // 去掉右下角logo
			minZoom: 12,
			maxZoom: 18, //最大显示层级
			preferCanvas: true,
			doubleClickZoom: false, // 取消双击放大
			closePopupOnClick: false // 默认点击地图关闭popup行为
		}
		map = new L.Map(id, Object.assign(defaultMapOption, mapOption))
		return Promise.resolve() // 回调地图初始化完成
	}
	// 地图平移
	const setMapCenter = (coordinate) => {
		const { lng, lat } = coordinate
		map.panTo(L.latLng(lat, lng), { animate: true })
	}
	// 地图缩放
	const setMapScale = (coordinate, zoom) => {
		const { lng, lat } = coordinate
		map.setView(L.latLng(lat, lng), zoom, { animate: true })
	}
	// 地图显示范围 coordinate1:范围点位1 coordinate2:范围点位2
	const setMapBounds = (coordinate1, coordinate2) => {
		const { lng: lng1, lat: lat1 } = coordinate1
		const { lng: lng2, lat: lat2 } = coordinate2
		map.setMaxBounds(L.latLngBounds(L.latLng(lat1, lng1), L.latLng(lat2, lng2)))
	}
	// 地图设置显示级别
	const setZoomLevels = (minLevels = 1, maxLevels = 18) => {
		map.setMinZoom(minLevels)
		map.setMaxZoom(maxLevels)
	}
	/**
	 * 添加图片覆盖物类
	 * params:
	 *  coordinate1: 经纬度 格式:默认
	 *  coordinate2: 经纬度 格式:默认
	 *  imgUrl: 点位显示图标
	 *  option: 图片覆盖物属性,{ opacity: 图片透明度, alt: 如果无法显示图像,浏览器将显示替代文本 }
	 */
	const addMapImgOverLay = (coordinate1, coordinate2, imgUrl, option = {}) => {
		const { lng: lng1, lat: lat1 } = coordinate1
		const { lng: lng2, lat: lat2 } = coordinate2
		let bd = L.latLngBounds(L.latLng(lat1, lng1), L.latLng(lat2, lng2))
		let imgMarker = L.imageOverlay(imgUrl, bd, option)
		imgMarker.addTo(map)
		return imgMarker
	}
	/**
	 * 地图添加自定义点位并返回该点位对象
	 * params:
	 *  coordinate: 经纬度 格式:默认
	 *  markerOption: 地图图标对象配置
	 *  iconOption: 地图ICON图标配置
	 *   格式:{
	 *   	 iconUrl: String, 图标地址
	 *       iconSize: [30, 30], 图标大小,非必需配置
	 *       iconAnchor: [15, 30] 图标偏移,非必需配置
	 *   }
	 *  clickEvent: 监听点击事件回调
	 */
	const addMapMarker = (
		coordinate,
		markerOption = {},
		iconOption = null,
		clickEvent = null
	) => {
		const { lng, lat } = coordinate
		let option = {}
		if (iconOption) {
			let icon = L.icon(
				Object.assign(
					{
						iconSize: [30, 30],
						iconAnchor: [15, 30]
					},
					iconOption
				)
			)
			option.icon = icon
		}
		let marker = L.marker(L.latLng(lat, lng), Object.assign(option, markerOption))
		if (clickEvent) {
			marker.on('click', clickEvent)
		}
		marker.addTo(map)
		return marker
	}
	/**
	 * 地图添加自定义线并返回该线对象
	 * params:
	 *  coordinateList: 线的各点位经纬度数组 格式:[{ lng: 'lng', lat: 'lat' }]
	 *  polylineOption: 线(polyline)配置项
	 *  clickEvent: 监听点击事件回调
	 */
	const addMapLine = (coordinateList, polylineOption = null, clickEvent = null) => {
		let points = coordinateList.map((item) => {
			return L.latLng(item.lat, item.lng)
		})
		let marker = L.polyline(points, polylineOption)
		if (clickEvent) {
			marker.on('click', clickEvent)
		}
		marker.addTo(map)
		return marker
	}
	/**
	 * 地图添加自定义面并返回该面对象
	 * params:
	 *  coordinateList: 面的各点位经纬度数组 格式:[[{ lng: 'lng', lat: 'lat' }], [{ lng: 'lng', lat: 'lat' }]]
	 *  格式说明:处理面内环情况 外环位置是逆时针定义 内环位置值是顺时针定义
	 *  polylineOption: 面(polygon)配置项
	 *  clickEvent: 监听点击事件回调
	 */
	const addMapPolygon = (coordinateList, polygonOption = null, clickEvent = null) => {
		let polygon = coordinateList.map((item) => {
			return item.map((pointItem) => {
				return L.latLng(pointItem.lat, pointItem.lng)
			})
		})
		let marker = L.polygon(polygon, polygonOption)
		if (clickEvent) {
			marker.on('click', clickEvent)
		}
		marker.addTo(map)
		return marker
	}
	/**
	 * 地图添加自定义窗口并返回窗口对象
	 * params:
	 *  coordinate: 弹出窗口经纬度 格式:默认
	 *  popupOption: 弹出窗口(popup)配置项
	 *  template: 自定义模板组件
	 *  params: 传入组件自定义参数
	 *  clickEvent: 监听点击事件回调
	 */
	const addMapPopup = (coordinate, popupOption, template, params, clickEvent = null) => {
		// 弹窗默认配置,请根据业务需求参考leaflet文档自定义设置
		const defaultOption = {
			autoPan: false, // 地图做平移动画,如果同时展示多个弹出窗口则默认不做平移
			interactive: true // popup将监听鼠标事件
		}
		// 创建虚拟节点并传参
		const popupContent = getTemplateNode(template, params)
		// 监听组件自定义handleClick事件
		clickEvent && popupContent.el.addEventListener('click', clickEvent)
		// 创建天地图弹出窗口 popupContent.el:test模板dom
		let popup = getPopupObj(popupContent.el, coordinate, Object.assign(defaultOption, popupOption))
		// 地图添加弹出窗口
		addMapOverLay(popup)
		return popup
	}
	/**
	 * 生成一个模板节点并传参
	 * params:
	 *  template: 自定义模板组件
	 *  params: 传入组件自定义参数
	 */
	const getTemplateNode = (template, params) => {
		// 创建虚拟节点并传参
		const popupContent = createVNode(template, params)
		let node = document.createElement('div') // 创建一个div节点
		render(popupContent, node) // 实例组件挂载到node节点上
		return popupContent
	}
	// 生成弹出窗口对象
	const getPopupObj = (el, coordinate, popupOption) => {
		const { lng, lat } = coordinate
		let popup = L.popup(popupOption).setLatLng(L.latLng(lat, lng)).setContent(el)
		return popup
	}
	// 地图添加覆盖物 obj: 覆盖物对象
	const addMapOverLay = (obj) => {
		map.addLayer(obj)
	}
	// 地图移除覆盖物 obj: 覆盖物对象
	const removeMapOverLay = (obj) => {
		map.removeLayer(obj)
	}
	// 地图移除所有覆盖物
	const removeMapAllOverLay = () => {
        // 地图图层遍历移除
		map.eachLayer((layer) => {
			removeMapOverLay(layer)
		})
	}
	// 获取map
	const getMap = () => {
		return map
	}
	return {
		mapInit,
		setMapCenter,
		setMapScale,
		setMapBounds,
		setZoomLevels,
		addMapImgOverLay,
		addMapMarker,
		addMapLine,
		addMapPolygon,
		addMapPopup,
		getTemplateNode,
		getPopupObj,
		addMapOverLay,
		removeMapOverLay,
		removeMapAllOverLay,
		getMap
	}
}

引入天地图hooks并初始化地图,以下都为部分关键代码

<template>
	<div id="map"></div>
</template>

<script setup name="index">
	import { useLeafletMap } from '@/hooks/useLeafletMap'

    // 引入
	const {
		mapInit,
		setMapCenter,
		setMapScale,
		setMapBounds,
		setZoomLevels,
		addMapImgOverLay,
		addMapMarker,
		addMapLine,
		addMapPolygon,
		addMapPopup,
		getTemplateNode,
		getPopupObj,
		addMapOverLay,
		removeMapOverLay,
		removeMapAllOverLay,
		getMap
	} = useLeafletMap('map')
    // 初始化地图
	onMounted(() => {
		mapInit().then(() => {
			// 地图初始化完毕后回调操作
		})
	})
</script>

<style scoped lang="less">
	#map {
		width: 100vw;
		height: 100vh;
	}
</style>

setMapCenter:手动设置地图平移示例:

const coordinate = {
    lat: '39.89945',
    lng: '116.40769'
}
setMapCenter(coordinate)

setMapScale:手动设置地图中心点及比例尺级别示例:

const coordinate = {
    lat: '39.89945',
    lng: '116.40769'
}
setMapScale(coordinate, 13)

 setMapBounds:手动设置地图显示范围示例:

const coordinate1 = {
    lat: '30.49365',
    lng: '103.90114'
}
const coordinate2 = {
    lat: '30.64736',
    lng: '104.23073'
}
setMapBounds(coordinate1, coordinate2 )

setZoomLevels:手动设置地图显示级别示例:

setZoomLevels(12, 18) // 设置地图级别

addMapImgOverLay:添加地图图片覆盖物示例:


import testImg from '@/assets/image/test.png'

const coordinate1 = {
    lat: '30.49365',
    lng: '103.90114'
}
const coordinate2 = {
    lat: '30.64736',
    lng: '104.23073'
}
addMapImgOverLay(
    coordinate1,
    coordinate2,
    testImg,
    {
	    opacity: 100,
		alt: ''
    }
)

 addMapMarker:添加自定义点位并返回该点位对象示例:

import testIcon from '@/assets/image/test.png'

const coordinate = {
    lat: '39.89945',
    lng: '116.40769'
}
const marker = addMapMarker(
   coordinate,
    // leaflet文档里maiker options选项
    {
	    title: '这是title'
	},
    //  // leaflet文档里maiker的icon options选项
    {
        iconUrl: pointImg
    },
   (e) => {
        console.log(e)
    },
)

addMapLine:添加自定义线并返回该线对象示例:

const coordinateList = [
    {
        lng: '116.40769',
        lat: '39.89945'
    },
    {
        lng: '116.50769',
        lat: '39.99945'
    },
    {
        lng: '116.60769',
        lat: '39.79945'
    },
    {
        lng: '116.70769',
        lat: '39.69945'
    },
    {
        lng: '116.80769',
        lat: '39.59945'
    }
]
 
// 默认线配置
const marker = addMapLine(coordinateList)

addMapPolygon:添加自定义面并返回该面对象示例:

// 蒙层四角 中国四角坐标 外部图层
const smegmaPointList = [
	{
		lng: '73.0',
		lat: '59.0'
	},
	{
		lng: '73.0',
		lat: '3.0'
	},
	{
		lng: '136.0',
		lat: '3.0'
	},
	{
		lng: '136.0',
		lat: '59.0'
	},
	{
		lng: '73.0',
		lat: '59.0'
	}
]
// 区域范围
const mapCoordinateData = [
	{
		lng: '104.0325',
		lat: '30.56888'
	},
	{
		lng: '104.04353',
		lat: '30.56795'
	},
	{
		lng: '104.04375',
		lat: '30.53942'
	},
	{
		lng: '104.02315',
		lat: '30.53931'
	},
	{
		lng: '104.02164',
		lat: '30.54504'
	},
	{
		lng: '104.02542',
		lat: '30.55657'
	},
	{
		lng: '104.03053',
		lat: '30.56278'
	},
	{
		lng: '104.0325',
		lat: '30.56888'
	}
]
// 常规画面示例
const marker = addMapPolygon([smegmaPointList])
// 画内环圆示例并配置相关信息
const marker = addMapPolygon(
                    [smegmaPointList, mapCoordinateData],
                    {
	                    color: 'black',
                    	weight: 3,
                    	opacity: 0.2,
	                    fillColor: '#0c2940', // 蒙层颜色
	                    fillOpacity: 0.7 // 透明度
                    },
                    () => {}
                )

addMapPopup:添加自定义信息窗口并返回该窗口对象示例:

        自定义test组件示例:

// 自定义模块test @/components/test/index.vue
<template>
    <div>{{ title }}</div>
<template>
 
<scropt setup>
    defineProps({
        title: {
            type: String,
            default: ''
        }
    })
</scropt>

        addMapPopup调用示例:

import TestTemplate from '@/components/test/index.vue'

let coordinate = {
    lng: 111.92,
	lat: 37.03
}

// 示例场景一:popup直接挂载到point点上
const marker = addMapMarker(coordinate) // 创建点
let content = getTemplateNode(testTemplate, { parentName: '父组件传子组件' }) // 创建窗口节点
content.el.addEventListener('click', (e) => { console.log(e) }) // 窗口节点点击事件
marker1.bindPopup(content.el, { offset: L.point(0, -15), autoClose: false }) // 挂载

// 示例场景二:popup自定义挂载到某个坐标位置
const marker = addMapPopup(
	coordinate,
	{ autoClose: false },
	testTemplate,
	{ parentName: '这是自定义popup parentName' },
	(e) => {
		console.log(e)
		console.log(2222)
	}
)

addMapOverLay:添加覆盖物示例:

addMapOverLay(marker) // marker同上示例

addMapOverLay:移除覆盖物示例:

removeMapOverLay(marker) // marker同上示例

removeMapAllOverLay:移除天地图所有覆盖物示例:

removeMapAllOverLay()

这里放一个之前写得封装:【天地图】VUE3关于天地图部分功能hooks封装useTdtMap.js_vue3 天地图-CSDN博客

leaflet与纯天地图开发使用比较:

1.论开发地图自定义绘制工具:leaflet依赖第三方插件,插件暂时感觉没天地图灵活(待我再研究研究),天地图只需按管网API开发既是。

2.论十万数据加载速度:leaflet利用canvas绘制方式加载流畅,天地图需另想办法手动优化。

3.论API文档上手难度:体感两个差不多的,个别语法略有不同,比如移除地图所有图层方式上,leaflet相对在细节上感觉更细致些。

4.论加载Geojson数据:leaflet原有对应API方式加载,天地图需手动处理加载。

5.论官网文档查询:leaflet中文文档网页有时候会挂掉,所以我贴了两个地址。

6.论引入方式:leaflet我个人是没在官网找到关于VUE项目引入的,天地图就依照官网引导引入就行。

7.论开发出现问题寻求解决方案:就绿色上网来说,leaflet更多搜的是付费方案,天地图有些搜不太到,两者体感就大差不差了。

8.其它想到了以后再补充吧…………

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值