0


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

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

——————————————————————————————————————————

文章改动日志:

  1. 2024/8/2useLeafletMap hooks代码里新增tips注释提醒,旨在针对部分方法封装进行说明,不影响原hooks功能使用

——————————————————————————————————————————

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

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

  1. /**
  2. * leaflet hooks
  3. * params:
  4. * id: 地图绑定的html标签id,具有唯一性
  5. * mapOptions: 地图初始化配置
  6. * tips:
  7. * 1. 以下关于coordinate默认格式调整:{ lng: lng, lat: lat }
  8. * 2. 入参默认为Null的为非必须传参参数
  9. * 3. 封装旨在外部能更简洁调用使用,以尽量在外部不需要再次引用leaflet一堆直接使用L的前提下进行封
  10. * 装,故有些封装习惯看似冗余实则个人斟酌考虑,如不喜欢请自行调整, 以下均以tips提醒
  11. */
  12. import { createVNode, render } from 'vue'
  13. import L from 'leaflet'
  14. import 'leaflet.chinatmsproviders'
  15. import 'leaflet/dist/leaflet.css'
  16. export const useLeafletMap = (id) => {
  17. let map = null // 地图对象
  18. // 地图初始化,mapOption:自定义地图配置
  19. const mapInit = (mapOption = {}) => {
  20. //天地图key
  21. const TDT_KEY = '您申请的天地图key';
  22. const normalm = L.tileLayer.chinaProvider('TianDiTu.Normal.Map', {
  23. key: TDT_KEY,
  24. maxZoom: 18,
  25. minZoom: 5
  26. })
  27. const normala = L.tileLayer.chinaProvider('TianDiTu.Normal.Annotion', {
  28. key: TDT_KEY,
  29. maxZoom: 18,
  30. minZoom: 5
  31. })
  32. const normal = L.layerGroup([normalm, normala])
  33. // 地图默认配置
  34. const defaultMapOption = {
  35. renderer: L.canvas(), // 默认矢量图层绘制方法
  36. center: [37.03, 111.92],
  37. zoom: 16,
  38. layers: [normal],
  39. zoomControl: false, // 缩放组件
  40. logoControl: false, // 去掉logo
  41. attributionControl: false, // 去掉右下角logo
  42. minZoom: 12,
  43. maxZoom: 18, //最大显示层级
  44. preferCanvas: true,
  45. doubleClickZoom: false, // 取消双击放大
  46. closePopupOnClick: false // 默认点击地图关闭popup行为
  47. }
  48. map = new L.Map(id, Object.assign(defaultMapOption, mapOption))
  49. return Promise.resolve() // 回调地图初始化完成
  50. }
  51. // 地图平移
  52. const setMapCenter = (coordinate) => {
  53. const { lng, lat } = coordinate
  54. map.panTo(L.latLng(lat, lng), { animate: true })
  55. }
  56. // 地图缩放
  57. const setMapScale = (coordinate, zoom) => {
  58. const { lng, lat } = coordinate
  59. map.setView(L.latLng(lat, lng), zoom, { animate: true })
  60. }
  61. // 地图显示范围 coordinate1:范围点位1 coordinate2:范围点位2
  62. const setMapBounds = (coordinate1, coordinate2) => {
  63. const { lng: lng1, lat: lat1 } = coordinate1
  64. const { lng: lng2, lat: lat2 } = coordinate2
  65. map.setMaxBounds(L.latLngBounds(L.latLng(lat1, lng1), L.latLng(lat2, lng2)))
  66. }
  67. // 地图设置显示级别
  68. const setZoomLevels = (minLevels = 1, maxLevels = 18) => {
  69. map.setMinZoom(minLevels)
  70. map.setMaxZoom(maxLevels)
  71. }
  72. /**
  73. * 添加图片覆盖物类
  74. * params:
  75. * coordinate1: 经纬度 格式:默认
  76. * coordinate2: 经纬度 格式:默认
  77. * imgUrl: 点位显示图标
  78. * option: 图片覆盖物属性,{ opacity: 图片透明度, alt: 如果无法显示图像,浏览器将显示替代文本 }
  79. */
  80. const addMapImgOverLay = (coordinate1, coordinate2, imgUrl, option = {}) => {
  81. const { lng: lng1, lat: lat1 } = coordinate1
  82. const { lng: lng2, lat: lat2 } = coordinate2
  83. let bd = L.latLngBounds(L.latLng(lat1, lng1), L.latLng(lat2, lng2))
  84. let imgMarker = L.imageOverlay(imgUrl, bd, option)
  85. imgMarker.addTo(map)
  86. return imgMarker
  87. }
  88. /**
  89. * 地图添加自定义点位并返回该点位对象
  90. * params:
  91. * coordinate: 经纬度 格式:默认
  92. * markerOption: 地图图标对象配置
  93. * iconOption: 地图ICON图标配置
  94. * 格式:{
  95. * iconUrl: String, 图标地址
  96. * iconSize: [30, 30], 图标大小,非必需配置
  97. * iconAnchor: [15, 30] 图标偏移,非必需配置
  98. * }
  99. * clickEvent: 监听点击事件回调
  100. * tips:iconOption本可直接写入markerOption配置里,但因其配置直接使用L,故另封装一个入参,
  101. * 可考虑单独对ICON封装函数,这里就不行此举了
  102. */
  103. const addMapMarker = (
  104. coordinate,
  105. markerOption = {},
  106. iconOption = null,
  107. clickEvent = null
  108. ) => {
  109. const { lng, lat } = coordinate
  110. let option = {}
  111. if (iconOption) {
  112. let icon = L.icon(
  113. Object.assign(
  114. {
  115. iconSize: [30, 30],
  116. iconAnchor: [15, 30]
  117. },
  118. iconOption
  119. )
  120. )
  121. option.icon = icon
  122. }
  123. let marker = L.marker(L.latLng(lat, lng), Object.assign(option, markerOption))
  124. if (clickEvent) {
  125. marker.on('click', clickEvent)
  126. }
  127. marker.addTo(map)
  128. return marker
  129. }
  130. /**
  131. * 地图添加自定义线并返回该线对象
  132. * params:
  133. * coordinateList: 线的各点位经纬度数组 格式:[{ lng: 'lng', lat: 'lat' }]
  134. * polylineOption: 线(polyline)配置项
  135. * clickEvent: 监听点击事件回调
  136. */
  137. const addMapLine = (coordinateList, polylineOption = null, clickEvent = null) => {
  138. let points = coordinateList.map((item) => {
  139. return L.latLng(item.lat, item.lng)
  140. })
  141. let marker = L.polyline(points, polylineOption)
  142. if (clickEvent) {
  143. marker.on('click', clickEvent)
  144. }
  145. marker.addTo(map)
  146. return marker
  147. }
  148. /**
  149. * 地图添加自定义面并返回该面对象
  150. * params:
  151. * coordinateList: 面的各点位经纬度数组 格式:[[{ lng: 'lng', lat: 'lat' }], [{ lng: 'lng', lat: 'lat' }]]
  152. * 格式说明:处理面内环情况 外环位置是逆时针定义 内环位置值是顺时针定义
  153. * polylineOption: 面(polygon)配置项
  154. * clickEvent: 监听点击事件回调
  155. */
  156. const addMapPolygon = (coordinateList, polygonOption = null, clickEvent = null) => {
  157. let polygon = coordinateList.map((item) => {
  158. return item.map((pointItem) => {
  159. return L.latLng(pointItem.lat, pointItem.lng)
  160. })
  161. })
  162. let marker = L.polygon(polygon, polygonOption)
  163. if (clickEvent) {
  164. marker.on('click', clickEvent)
  165. }
  166. marker.addTo(map)
  167. return marker
  168. }
  169. /**
  170. * 地图添加自定义窗口并返回窗口对象
  171. * params:
  172. * coordinate: 弹出窗口经纬度 格式:默认
  173. * popupOption: 弹出窗口(popup)配置项
  174. * template: 自定义模板组件
  175. * params: 传入组件自定义参数
  176. * clickEvent: 监听点击事件回调
  177. */
  178. const addMapPopup = (coordinate, popupOption = {}, template, params = {}, clickEvent = null) => {
  179. // 弹窗默认配置,请根据业务需求参考leaflet文档自定义设置
  180. const defaultOption = {
  181. autoPan: false, // 地图做平移动画,如果同时展示多个弹出窗口则默认不做平移
  182. interactive: true // popup将监听鼠标事件
  183. }
  184. // 创建虚拟节点并传参
  185. const popupContent = getTemplateNode(template, params)
  186. // 监听组件自定义handleClick事件
  187. clickEvent && popupContent.el.addEventListener('click', clickEvent)
  188. // 创建天地图弹出窗口 popupContent.el:test模板dom
  189. let popup = getPopupObj(popupContent.el, coordinate, Object.assign(defaultOption, popupOption))
  190. // 地图添加弹出窗口
  191. addMapOverLay(popup)
  192. return popup
  193. }
  194. /**
  195. * 生成一个模板节点并传参
  196. * params:
  197. * template: 自定义模板组件
  198. * params: 传入组件自定义参数
  199. */
  200. const getTemplateNode = (template, params) => {
  201. // 创建虚拟节点并传参
  202. const popupContent = createVNode(template, params)
  203. let node = document.createElement('div') // 创建一个div节点
  204. render(popupContent, node) // 实例组件挂载到node节点上
  205. return popupContent
  206. }
  207. // 生成弹出窗口对象
  208. const getPopupObj = (el, coordinate, popupOption) => {
  209. const { lng, lat } = coordinate
  210. let popup = L.popup(popupOption).setLatLng(L.latLng(lat, lng)).setContent(el)
  211. return popup
  212. }
  213. // 地图添加覆盖物 obj: 覆盖物对象
  214. const addMapOverLay = (obj) => {
  215. map.addLayer(obj)
  216. }
  217. // 地图移除覆盖物 obj: 覆盖物对象
  218. const removeMapOverLay = (obj) => {
  219. map.removeLayer(obj)
  220. }
  221. // 地图移除所有覆盖物
  222. const removeMapAllOverLay = () => {
  223. // 地图图层遍历移除
  224. map.eachLayer((layer) => {
  225. removeMapOverLay(layer)
  226. })
  227. }
  228. // 获取map
  229. const getMap = () => {
  230. return map
  231. }
  232. return {
  233. mapInit,
  234. setMapCenter,
  235. setMapScale,
  236. setMapBounds,
  237. setZoomLevels,
  238. addMapImgOverLay,
  239. addMapMarker,
  240. addMapLine,
  241. addMapPolygon,
  242. addMapPopup,
  243. getTemplateNode,
  244. getPopupObj,
  245. addMapOverLay,
  246. removeMapOverLay,
  247. removeMapAllOverLay,
  248. getMap
  249. }
  250. }

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

  1. <template>
  2. <div id="map"></div>
  3. </template>
  4. <script setup name="index">
  5. import { useLeafletMap } from '@/hooks/useLeafletMap'
  6. // 引入
  7. const {
  8. mapInit,
  9. setMapCenter,
  10. setMapScale,
  11. setMapBounds,
  12. setZoomLevels,
  13. addMapImgOverLay,
  14. addMapMarker,
  15. addMapLine,
  16. addMapPolygon,
  17. addMapPopup,
  18. getTemplateNode,
  19. getPopupObj,
  20. addMapOverLay,
  21. removeMapOverLay,
  22. removeMapAllOverLay,
  23. getMap
  24. } = useLeafletMap('map')
  25. // 初始化地图
  26. onMounted(() => {
  27. mapInit().then(() => {
  28. // 地图初始化完毕后回调操作
  29. })
  30. })
  31. </script>
  32. <style scoped lang="less">
  33. #map {
  34. width: 100vw;
  35. height: 100vh;
  36. }
  37. </style>

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

  1. const coordinate = {
  2. lat: '39.89945',
  3. lng: '116.40769'
  4. }
  5. setMapCenter(coordinate)

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

  1. const coordinate = {
  2. lat: '39.89945',
  3. lng: '116.40769'
  4. }
  5. setMapScale(coordinate, 13)

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

  1. const coordinate1 = {
  2. lat: '30.49365',
  3. lng: '103.90114'
  4. }
  5. const coordinate2 = {
  6. lat: '30.64736',
  7. lng: '104.23073'
  8. }
  9. setMapBounds(coordinate1, coordinate2 )

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

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

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

  1. import testImg from '@/assets/image/test.png'
  2. const coordinate1 = {
  3. lat: '30.49365',
  4. lng: '103.90114'
  5. }
  6. const coordinate2 = {
  7. lat: '30.64736',
  8. lng: '104.23073'
  9. }
  10. addMapImgOverLay(
  11. coordinate1,
  12. coordinate2,
  13. testImg,
  14. {
  15. opacity: 100,
  16. alt: ''
  17. }
  18. )

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

  1. import testIcon from '@/assets/image/test.png'
  2. const coordinate = {
  3. lat: '39.89945',
  4. lng: '116.40769'
  5. }
  6. const marker = addMapMarker(
  7. coordinate,
  8. // leaflet文档里maiker options选项
  9. {
  10. title: '这是title'
  11. },
  12. // // leaflet文档里maiker的icon options选项
  13. {
  14. iconUrl: pointImg
  15. },
  16. (e) => {
  17. console.log(e)
  18. },
  19. )

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

  1. const coordinateList = [
  2. {
  3. lng: '116.40769',
  4. lat: '39.89945'
  5. },
  6. {
  7. lng: '116.50769',
  8. lat: '39.99945'
  9. },
  10. {
  11. lng: '116.60769',
  12. lat: '39.79945'
  13. },
  14. {
  15. lng: '116.70769',
  16. lat: '39.69945'
  17. },
  18. {
  19. lng: '116.80769',
  20. lat: '39.59945'
  21. }
  22. ]
  23. // 默认线配置
  24. const marker = addMapLine(coordinateList)

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

  1. // 蒙层四角 中国四角坐标 外部图层
  2. const smegmaPointList = [
  3. {
  4. lng: '73.0',
  5. lat: '59.0'
  6. },
  7. {
  8. lng: '73.0',
  9. lat: '3.0'
  10. },
  11. {
  12. lng: '136.0',
  13. lat: '3.0'
  14. },
  15. {
  16. lng: '136.0',
  17. lat: '59.0'
  18. },
  19. {
  20. lng: '73.0',
  21. lat: '59.0'
  22. }
  23. ]
  24. // 区域范围
  25. const mapCoordinateData = [
  26. {
  27. lng: '104.0325',
  28. lat: '30.56888'
  29. },
  30. {
  31. lng: '104.04353',
  32. lat: '30.56795'
  33. },
  34. {
  35. lng: '104.04375',
  36. lat: '30.53942'
  37. },
  38. {
  39. lng: '104.02315',
  40. lat: '30.53931'
  41. },
  42. {
  43. lng: '104.02164',
  44. lat: '30.54504'
  45. },
  46. {
  47. lng: '104.02542',
  48. lat: '30.55657'
  49. },
  50. {
  51. lng: '104.03053',
  52. lat: '30.56278'
  53. },
  54. {
  55. lng: '104.0325',
  56. lat: '30.56888'
  57. }
  58. ]
  59. // 常规画面示例
  60. const marker = addMapPolygon([smegmaPointList])
  61. // 画内环圆示例并配置相关信息
  62. const marker = addMapPolygon(
  63. [smegmaPointList, mapCoordinateData],
  64. {
  65. color: 'black',
  66. weight: 3,
  67. opacity: 0.2,
  68. fillColor: '#0c2940', // 蒙层颜色
  69. fillOpacity: 0.7 // 透明度
  70. },
  71. () => {}
  72. )

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

  1. 自定义test组件示例:
  1. // 自定义模块test @/components/test/index.vue
  2. <template>
  3. <div>{{ title }}</div>
  4. <template>
  5. <scropt setup>
  6. defineProps({
  7. title: {
  8. type: String,
  9. default: ''
  10. }
  11. })
  12. </scropt>
  1. addMapPopup调用示例:
  1. import TestTemplate from '@/components/test/index.vue'
  2. let coordinate = {
  3. lng: 111.92,
  4. lat: 37.03
  5. }
  6. // 示例场景一:popup直接挂载到point点上
  7. const marker = addMapMarker(coordinate) // 创建点
  8. let content = getTemplateNode(testTemplate, { parentName: '父组件传子组件' }) // 创建窗口节点
  9. content.el.addEventListener('click', (e) => { console.log(e) }) // 窗口节点点击事件
  10. marker1.bindPopup(content.el, { offset: L.point(0, -15), autoClose: false }) // 挂载
  11. // 示例场景二:popup自定义挂载到某个坐标位置
  12. const marker = addMapPopup(
  13. coordinate,
  14. { autoClose: false },
  15. testTemplate,
  16. { parentName: '这是自定义popup parentName' },
  17. (e) => {
  18. console.log(e)
  19. console.log(2222)
  20. }
  21. )

addMapOverLay:添加覆盖物示例:

  1. addMapOverLay(marker) // marker同上示例

addMapOverLay:移除覆盖物示例:

  1. removeMapOverLay(marker) // marker同上示例

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

  1. 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.其它想到了以后再补充吧…………


本文转载自: https://blog.csdn.net/YueMiaoL/article/details/140720140
版权归原作者 半吊子指针 所有, 如有侵权,请联系我们删除。

“【天地图】VUE3基于Leaflet.js部分功能hooks封装useLeafletMap.js”的评论:

还没有评论