leafletHelper.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. // leaflet 地图组件
  2. // author: wzx
  3. // version: 2.0
  4. import L from 'leaflet'
  5. // import patch from '@/utils/map/sub/patch'
  6. import config from '@/utils/map/sub/config'
  7. import global from '@/utils/map/sub/global'
  8. import player from '@/utils/map/sub/player'
  9. import playerRoute from '@/utils/map/sub/playerRoute'
  10. // import checkPoint from '@/utils/map/sub/checkPoint'
  11. import route from '@/utils/map/sub/route'
  12. import store from '@/store/index'
  13. export default {
  14. global: global,
  15. config: config,
  16. player: player,
  17. playerRoute: playerRoute,
  18. // checkPoint: checkPoint,
  19. route: route,
  20. exControlContainer: null,
  21. elRoute: null,
  22. elPlayer: null,
  23. elPlayerTooltip: null,
  24. elPlayerTrail: null,
  25. elList: null,
  26. elFullScreen: null,
  27. // 地图初始化
  28. // mapid <string> 地图容器的id
  29. // mapUrl <string> 地图瓦片的地址
  30. // options <object> 地图选项
  31. async init(caller, mapid, mapUrl, options) {
  32. console.log("mapUrl", mapUrl);
  33. // patch.run()
  34. global.init()
  35. global.setCaller(caller)
  36. global.setMapUrl(mapUrl)
  37. global.mapOptions = Object.assign(config.mapDefaultOptions, options)
  38. // if (global.mapOptions.maxBounds != null) {
  39. // global.setPreloadBounds(global.mapOptions.maxBounds)
  40. // // console.log("preloadBounds: ", global.mapOptions.maxBounds)
  41. // // global.mapOptions.maxBounds = L.latLngBounds(global.mapOptions.maxBounds).pad(0.3)
  42. // global.mapOptions.maxBounds = null
  43. // // console.log("maxBounds: ", global.mapOptions.maxBounds)
  44. // }
  45. const mapImageInfo = await uni.getImageInfo({src: mapUrl});
  46. // console.log(mapImageInfo);
  47. let tfwUrl = "";
  48. let index= mapUrl.lastIndexOf("."); // 获取最后一个.的位置
  49. if (index >= 0) {
  50. tfwUrl = mapUrl.substring(0, index) + ".tfw";
  51. console.log("tfwUrl", tfwUrl);
  52. } else {
  53. console.error("tfwUrl err", tfwUrl);
  54. return;
  55. }
  56. const bounds = await this.getImageBoundsByTfw(tfwUrl, mapImageInfo.width, mapImageInfo.height);
  57. const center = this.getImageCenterByBounds(bounds);
  58. global.mapOptions.maxBounds = bounds;
  59. global.mapOptions.center = center;
  60. console.log("global.mapOptions", global.mapOptions);
  61. global.map = L.map(mapid, global.mapOptions);
  62. // global.map = L.map(mapid, {
  63. // attributionControl: false
  64. // }).setView(centPoint, zoomNum)
  65. L.control.attribution({
  66. prefix: '&copy; 彩图奔跑 All Rights Reserved.', // 地图右下角属性文本的前缀内容
  67. // prefix: false // 地图右下角属性文本的前缀内容
  68. }).addTo(global.map);
  69. L.control.scale({
  70. maxWidth: 120, // 控件的最大宽度,单位是像素
  71. metric: true, // 是否显示公制比例线(米/公里)
  72. imperial: false, // 是否显示英制比例线(英里/英尺)
  73. position: 'topright' // 控件的位置(地图的一个角)。可能的值是 ‘topleft’、 ‘topright’、 ‘bottomleft’ 或 ‘bottomright’
  74. }).addTo(global.map)
  75. this.extendControl()
  76. // 添加地图点击弹窗
  77. global.map.on('click', (e) => {
  78. console.log("坐标", e.latlng.toString())
  79. // L.popup().setLatLng(e.latlng)
  80. // .setContent("坐标:" + e.latlng.toString())
  81. // .openOn(global.map);
  82. });
  83. // global.map.on('resize', (e) => {
  84. // console.log("[resize] 地图大小调整", e)
  85. // this.onWindowResize()
  86. // });
  87. // global.map.on('invalidateSize', (e) => {
  88. // console.log("[invalidateSize] 地图容器大小调整", e)
  89. // // this.onWindowResize()
  90. // });
  91. global.map.on('zoomstart', (e) => {
  92. // console.log("[zoomstart] 即将开始地图缩放", e)
  93. player.onZoomStart(e)
  94. // checkPoint.onZoomStart(e)
  95. route.onZoomStart(e)
  96. });
  97. global.map.on('zoom', (e) => {
  98. // console.log("[zoom] 地图缩放", e)
  99. player.onZoom(e)
  100. // checkPoint.onZoom(e)
  101. route.onZoom(e)
  102. });
  103. global.map.on('zoomend', (e) => {
  104. // console.log("[zoomend] 地图缩放结束", e)
  105. this.preLoadTile()
  106. player.onZoomEnd(e)
  107. // checkPoint.onZoomEnd(e)
  108. route.onZoomEnd(e)
  109. });
  110. // this.setToken(token)
  111. this.addMapLayer(mapUrl, bounds);
  112. player.init()
  113. // checkPoint.init()
  114. route.init()
  115. // this.preLoadTile()
  116. },
  117. async readTfwFile(tfwUrl) {
  118. // console.log(tfwUrl);
  119. const response = await fetch(tfwUrl);
  120. // const lines = await response.text().split('\n');
  121. const responseText = await response.text();
  122. const lines = responseText.split('\r\n');
  123. // console.log(lines);
  124. const a = parseFloat(lines[0]); // x pixel size
  125. const b = parseFloat(lines[1]); // x rotation
  126. const c = parseFloat(lines[2]); // y rotation
  127. const d = parseFloat(lines[3]); // y pixel size
  128. const x_origin = parseFloat(lines[4]); // x coordinate of upper left corner
  129. const y_origin = parseFloat(lines[5]); // y coordinate of upper left corner
  130. return {
  131. a,
  132. b,
  133. c,
  134. d,
  135. x_origin,
  136. y_origin
  137. };
  138. },
  139. async getImageBoundsByTfw(tfwUrl, imageWidth, imageHeight) {
  140. const tfwData = await this.readTfwFile(tfwUrl);
  141. // console.log("tfwData", tfwData);
  142. const xmin = tfwData.x_origin;
  143. const ymax = tfwData.y_origin;
  144. const xmax = tfwData.x_origin + tfwData.a * imageWidth;
  145. const ymin = tfwData.y_origin + tfwData.d * imageHeight;
  146. const wgs84LatLng1 = this.Convert_EPSG3857_To_WGS84(ymax, xmin);
  147. const wgs84LatLng2 = this.Convert_EPSG3857_To_WGS84(ymin, xmax);
  148. const bounds = [
  149. [wgs84LatLng1.lat, wgs84LatLng1.lng],
  150. [wgs84LatLng2.lat, wgs84LatLng2.lng]
  151. ];
  152. // console.log("bounds", bounds);
  153. return bounds;
  154. },
  155. getImageCenterByBounds(bounds) {
  156. const wgs84Center = {
  157. lat: bounds[0][0] + (bounds[1][0] -bounds[0][0]) / 2,
  158. lng: bounds[0][1] + (bounds[1][1] -bounds[0][1]) / 2,
  159. }
  160. return wgs84Center;
  161. },
  162. // EPSG3857坐标(平面坐标) 转 WGS84经纬度坐标(球状坐标)
  163. Convert_EPSG3857_To_WGS84(lat, lng) {
  164. let tempLng = lng / 20037508.34 * 180;
  165. let tempLat = lat / 20037508.34 * 180;
  166. tempLat = 180 / Math.PI * (2 * Math.atan(Math.exp(tempLat * Math.PI / 180)) - Math.PI / 2);
  167. return {
  168. lng: lng == 0 ? 0 : tempLng,
  169. lat: lat == 0 ? 0 : tempLat
  170. };
  171. },
  172. // WGS84经纬度坐标(球状坐标) 转 EPSG3857坐标(平面坐标)
  173. Convert_WGS84_To_EPSG3857(lat, lng) {
  174. let mercator = {};
  175. let x = lng * 20037508.34 / 180;
  176. let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
  177. y = y * 20037508.34 / 180;
  178. mercator.x = x;
  179. mercator.y = y;
  180. return {
  181. lng: lng == 0 ? 0 : mercator.x,
  182. lat: lat == 0 ? 0 : mercator.y
  183. };
  184. },
  185. /* setMapServerUrl(url) {
  186. console.log("[setMapServerUrl] url: " + url)
  187. L.setMapServerUrl(url)
  188. },
  189. setToken(token) {
  190. console.log("[setToken] token: " + token)
  191. L.setToken(token)
  192. }, */
  193. addMapLayer(mapUrl, bounds) {
  194. // console.log('[addMapLayer] mapurl', mapurl)
  195. global.map_layer = L.imageOverlay(mapUrl, bounds).addTo(global.map);
  196. },
  197. addMapLayer2(mapUrl) {
  198. // console.log('[addMapLayer] mapurl', mapurl)
  199. // var mapurl = 'static/map/' + mapid + '/{z}/{x}/{y}.png'
  200. // this.setMapServerUrl(mapUrl)
  201. // var tileUrl = mapUrl + '/{z}/{x}/{y}.png'
  202. var tileUrl = '{z}/{x}/{y}.png'
  203. global.map_layer = L.tileLayer(tileUrl, {
  204. // minZoom: minZoom,
  205. // maxZoom: maxZoom,
  206. errorTileUrl: '/static/image/tileMiss.png',
  207. tms: false, // 是否反转Y轴坐标,
  208. // keepBuffer: 20, // 平移地图时,在卸载切片之前,请保留这么多行和几列的切片
  209. // attribution: '版权所有'
  210. }).addTo(global.map);
  211. },
  212. // onWindowResize() {
  213. // var centPoint = global.mapOptions.center
  214. // if (centPoint != null) {
  215. // console.log("[Leaflet] onWindowResize centPoint:", centPoint)
  216. // global.map.setView(centPoint)
  217. // // global.map.panTo(centPoint)
  218. // }
  219. // },
  220. extendControl() {
  221. var that = this
  222. L.Control.Search = L.Control.extend({
  223. options: {
  224. position: 'topleft' //初始位置
  225. },
  226. initialize: function(options) {
  227. L.Util.extend(this.options, options)
  228. },
  229. onAdd: function(map) {
  230. // var caller = global.getCaller()
  231. //创建Dom元素 L.DomUtil.create('元素类型', 'class类名')
  232. that.exControlContainer = L.DomUtil.create('div', 'leaflet-bar')
  233. that.elRoute = L.DomUtil.create('a', 'leaflet-control-common')
  234. that.elRoute.innerHTML = '<span>路线</span>'
  235. if (store.state.mapControlRoute) {
  236. L.DomUtil.setClass(that.elRoute, 'leaflet-control-selected')
  237. }
  238. that.elPlayer = L.DomUtil.create('a', 'leaflet-control-common')
  239. that.elPlayer.innerHTML = '<span>玩家</span>'
  240. if (store.state.mapControlPlayer) {
  241. L.DomUtil.setClass(that.elPlayer, 'leaflet-control-selected')
  242. }
  243. that.elPlayerTooltip = L.DomUtil.create('a', 'leaflet-control-common')
  244. that.elPlayerTooltip.innerHTML = '<span>提示</span>'
  245. if (store.state.mapControlTooltip) {
  246. L.DomUtil.setClass(that.elPlayerTooltip, 'leaflet-control-selected')
  247. }
  248. that.elPlayerTrail = L.DomUtil.create('a', 'leaflet-control-common')
  249. that.elPlayerTrail.innerHTML = '<span>轨迹</span>'
  250. if (store.state.mapControlTrail) {
  251. L.DomUtil.setClass(that.elPlayerTrail, 'leaflet-control-selected')
  252. }
  253. that.elList = L.DomUtil.create('a', 'leaflet-control-common')
  254. that.elList.innerHTML = '<span>列表</span>'
  255. if (store.state.mapPopupShow) {
  256. L.DomUtil.setClass(that.elList, 'leaflet-control-selected')
  257. }
  258. that.elFullScreen = L.DomUtil.create('a', 'leaflet-control-common')
  259. that.elFullScreen.innerHTML = '<span>全屏</span>'
  260. if (store.state.fullScreen) {
  261. L.DomUtil.setClass(that.elFullScreen, 'leaflet-control-selected')
  262. }
  263. // that.exControlContainer.style.display = 'flex'
  264. that.exControlContainer.appendChild(that.elRoute)
  265. that.exControlContainer.appendChild(that.elPlayer)
  266. that.exControlContainer.appendChild(that.elPlayerTooltip)
  267. that.exControlContainer.appendChild(that.elPlayerTrail)
  268. that.exControlContainer.appendChild(that.elList)
  269. that.exControlContainer.appendChild(that.elFullScreen)
  270. //注册事件
  271. L.DomEvent.addListener(that.elRoute, 'click dblclick', function(ev) {
  272. that.elRouteClick(ev)
  273. }, that.route)
  274. L.DomEvent.addListener(that.elPlayer, 'click dblclick', function(ev) {
  275. that.elPlayerClick(ev)
  276. }, that.player)
  277. L.DomEvent.addListener(that.elPlayerTooltip, 'click dblclick', function(ev) {
  278. that.elPlayerTooltipClick(ev)
  279. }, that.player)
  280. L.DomEvent.addListener(that.elPlayerTrail, 'click dblclick', function(ev) {
  281. that.elPlayerTrailClick(ev)
  282. }, that.player)
  283. L.DomEvent.addListener(that.elList, 'click dblclick', function(ev) {
  284. that.elListClick(ev)
  285. }, this)
  286. L.DomEvent.addListener(that.elFullScreen, 'click dblclick', function(ev) {
  287. that.elFullScreenClick(ev)
  288. }, this)
  289. //返回这个主元素
  290. return that.exControlContainer
  291. },
  292. onRemove: function(map) {
  293. // Clean up the DOM
  294. },
  295. })
  296. //在L.control上添加一个search(封装好的函数)
  297. L.control.Search = function(options) {
  298. return new L.Control.Search(options)
  299. }
  300. //将自定义控件添加到地图上
  301. L.control.Search().addTo(global.map)
  302. },
  303. preventEvent(ev) {
  304. L.DomEvent.stopPropagation(ev)
  305. L.DomEvent.preventDefault(ev)
  306. },
  307. elRouteClick(ev) {
  308. this.preventEvent(ev)
  309. this.route.toggle()
  310. this.toggleControl(this.elRoute)
  311. },
  312. elPlayerClick(ev) {
  313. this.preventEvent(ev)
  314. this.toggleControl(this.elPlayer)
  315. this.player.togglePlayer()
  316. },
  317. elPlayerTooltipClick(ev) {
  318. this.preventEvent(ev)
  319. this.toggleControl(this.elPlayerTooltip)
  320. this.player.toggleTooltip()
  321. console.log("store.state.mapControlPlayer: " + store.state.mapControlPlayer)
  322. if (!store.state.mapControlPlayer) {
  323. this.player.togglePlayer(true)
  324. // this.player.toggleTooltipFlag = false
  325. this.toggleControl(this.elPlayer)
  326. }
  327. },
  328. elPlayerTrailClick(ev) {
  329. this.preventEvent(ev)
  330. this.toggleControl(this.elPlayerTrail)
  331. this.player.toggleTrail()
  332. },
  333. elListClick(ev) {
  334. this.preventEvent(ev)
  335. this.toggleControl(this.elList)
  336. global.getCaller().popupToggle()
  337. },
  338. elFullScreenClick(ev) {
  339. this.preventEvent(ev)
  340. this.toggleControl(this.elFullScreen)
  341. global.getCaller().fullScreenToggle()
  342. },
  343. toggleControl(el) {
  344. if (L.DomUtil.hasClass(el, 'leaflet-control-selected')) {
  345. L.DomUtil.removeClass(el, 'leaflet-control-selected')
  346. } else {
  347. L.DomUtil.setClass(el, 'leaflet-control-selected')
  348. }
  349. },
  350. long2tile(lon, zoom) {
  351. return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom)));
  352. },
  353. lat2tile(lat, zoom) {
  354. return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) /
  355. 2 * Math.pow(2, zoom)));
  356. },
  357. // 获取图片的Blob值
  358. getImageBlob(url, cb) {
  359. var xhr = new XMLHttpRequest();
  360. xhr.open("get", url, true);
  361. xhr.responseType = "blob";
  362. xhr.onload = function() {
  363. if (this.status == 200) {
  364. if(cb) cb(this.response);
  365. }
  366. };
  367. xhr.send();
  368. },
  369. // 预加载地图瓦片
  370. preLoadTile() {
  371. return
  372. var zoom = global.map.getZoom()
  373. if (zoom >= global.mapOptions.maxZoom) {
  374. console.log("[preLoadTile] 已达到最大缩放级别,无需预加载地图瓦片")
  375. return
  376. }
  377. var preloadZoom = zoom + 1;
  378. if (global.getPreloadTileMapByZoom(preloadZoom) != null) {
  379. console.log("[preLoadTile] 跳过,已预加载地图瓦片 Zoom: " + preloadZoom)
  380. return
  381. } else {
  382. console.log("[preLoadTile] 预加载地图瓦片 Zoom: " + preloadZoom)
  383. global.setPreloadTileMap({
  384. zoom: preloadZoom
  385. })
  386. }
  387. var bounds = null
  388. var preloadBounds = global.getPreloadBounds()
  389. if (preloadBounds != null) {
  390. // console.log("[preLoadTile] preloadBounds", preloadBounds)
  391. bounds = L.latLngBounds(preloadBounds)
  392. } else {
  393. console.warn("[preLoadTile] preloadBounds == null")
  394. bounds = global.map.getBounds().pad(0.2)
  395. }
  396. var west = bounds.getWest()
  397. var south = bounds.getSouth()
  398. var east = bounds.getEast()
  399. var north = bounds.getNorth()
  400. // console.log("[preLoadTile] 预加载地图瓦片", bounds, west, south, east, north, zoom)
  401. // L.circleMarker([south, west], {
  402. // radius: 18, // 圆的半径 px
  403. // weight: 1.6, // 描边宽度 px
  404. // opacity: 1.0, // 描边不透明度
  405. // fill: true, // 是否填充
  406. // fillColor: 'red', // 填充颜色
  407. // fillOpacity: 1.0, // 填充不透明度
  408. // })
  409. // .addTo(global.map)
  410. // console.log("[preLoadTile] circleMarker", [south, west])
  411. // L.circleMarker([north, east], {
  412. // radius: 18, // 圆的半径 px
  413. // weight: 1.6, // 描边宽度 px
  414. // opacity: 1.0, // 描边不透明度
  415. // fill: true, // 是否填充
  416. // fillColor: 'red', // 填充颜色
  417. // fillOpacity: 1.0, // 填充不透明度
  418. // })
  419. // .addTo(global.map)
  420. // console.log("[preLoadTile] circleMarker", [north, east])
  421. // Determine which tile we need
  422. var dataEast = this.long2tile(east, preloadZoom)
  423. var dataWest = this.long2tile(west, preloadZoom)
  424. var dataNorth = this.lat2tile(north, preloadZoom)
  425. var dataSouth = this.lat2tile(south, preloadZoom)
  426. var mapUrl = global.getMapUrl()
  427. // console.log("[preLoadTile] dataNorth = " + dataNorth + " dataSouth = " + dataSouth)
  428. for (let y = dataNorth; y < dataSouth + 1; y++) {
  429. // console.log("[preLoadTile] y = " + y)
  430. for (let x = dataWest; x < dataEast + 1; x++) {
  431. // console.log("[preLoadTile] x = " + x)
  432. var url = mapUrl + '/' + preloadZoom + '/' + x + '/' + y + '.png'
  433. var img = new Image()
  434. this.getImageBlob( url , function(blob){
  435. // console.log(blob)
  436. img.src = URL.createObjectURL(blob);
  437. });
  438. // img.src = url
  439. // console.log("[preLoadTile] 预加载地图瓦片", url)
  440. }
  441. }
  442. },
  443. test() {
  444. console.log("test ok")
  445. },
  446. free() {
  447. console.log("[Leaflet] free()")
  448. player.free()
  449. // checkPoint.free()
  450. route.free()
  451. }
  452. }