// leaflet 地图组件 // author: wzx // version: 2.0 import L from 'leaflet' // import patch from '@/utils/map/sub/patch' import config from '@/utils/map/sub/config' import global from '@/utils/map/sub/global' import player from '@/utils/map/sub/player' import playerRoute from '@/utils/map/sub/playerRoute' // import checkPoint from '@/utils/map/sub/checkPoint' import route from '@/utils/map/sub/route' import store from '@/store/index' export default { global: global, config: config, player: player, playerRoute: playerRoute, // checkPoint: checkPoint, route: route, exControlContainer: null, elRoute: null, elPlayer: null, elPlayerTooltip: null, elPlayerTrail: null, elList: null, elFullScreen: null, // 地图初始化 // mapid 地图容器的id // mapUrl 地图瓦片的地址 // options 地图选项 async init(caller, mapid, mapUrl, options) { console.log("mapUrl", mapUrl); // patch.run() global.init() global.setCaller(caller) global.setMapUrl(mapUrl) global.mapOptions = Object.assign(config.mapDefaultOptions, options) // if (global.mapOptions.maxBounds != null) { // global.setPreloadBounds(global.mapOptions.maxBounds) // // console.log("preloadBounds: ", global.mapOptions.maxBounds) // // global.mapOptions.maxBounds = L.latLngBounds(global.mapOptions.maxBounds).pad(0.3) // global.mapOptions.maxBounds = null // // console.log("maxBounds: ", global.mapOptions.maxBounds) // } const mapImageInfo = await uni.getImageInfo({src: mapUrl}); // console.log(mapImageInfo); let tfwUrl = ""; let index= mapUrl.lastIndexOf("."); // 获取最后一个.的位置 if (index >= 0) { tfwUrl = mapUrl.substring(0, index) + ".tfw"; console.log("tfwUrl", tfwUrl); } else { console.error("tfwUrl err", tfwUrl); return; } const bounds = await this.getImageBoundsByTfw(tfwUrl, mapImageInfo.width, mapImageInfo.height); const center = this.getImageCenterByBounds(bounds); global.mapOptions.maxBounds = bounds; global.mapOptions.center = center; console.log("global.mapOptions", global.mapOptions); global.map = L.map(mapid, global.mapOptions); // global.map = L.map(mapid, { // attributionControl: false // }).setView(centPoint, zoomNum) L.control.attribution({ prefix: '© 彩图奔跑 All Rights Reserved.', // 地图右下角属性文本的前缀内容 // prefix: false // 地图右下角属性文本的前缀内容 }).addTo(global.map); L.control.scale({ maxWidth: 120, // 控件的最大宽度,单位是像素 metric: true, // 是否显示公制比例线(米/公里) imperial: false, // 是否显示英制比例线(英里/英尺) position: 'topright' // 控件的位置(地图的一个角)。可能的值是 ‘topleft’、 ‘topright’、 ‘bottomleft’ 或 ‘bottomright’ }).addTo(global.map) this.extendControl() // 添加地图点击弹窗 global.map.on('click', (e) => { console.log("坐标", e.latlng.toString()) // L.popup().setLatLng(e.latlng) // .setContent("坐标:" + e.latlng.toString()) // .openOn(global.map); }); // global.map.on('resize', (e) => { // console.log("[resize] 地图大小调整", e) // this.onWindowResize() // }); // global.map.on('invalidateSize', (e) => { // console.log("[invalidateSize] 地图容器大小调整", e) // // this.onWindowResize() // }); global.map.on('zoomstart', (e) => { // console.log("[zoomstart] 即将开始地图缩放", e) player.onZoomStart(e) // checkPoint.onZoomStart(e) route.onZoomStart(e) }); global.map.on('zoom', (e) => { // console.log("[zoom] 地图缩放", e) player.onZoom(e) // checkPoint.onZoom(e) route.onZoom(e) }); global.map.on('zoomend', (e) => { // console.log("[zoomend] 地图缩放结束", e) this.preLoadTile() player.onZoomEnd(e) // checkPoint.onZoomEnd(e) route.onZoomEnd(e) }); // this.setToken(token) this.addMapLayer(mapUrl, bounds); player.init() // checkPoint.init() route.init() // this.preLoadTile() }, async readTfwFile(tfwUrl) { // console.log(tfwUrl); const response = await fetch(tfwUrl); // const lines = await response.text().split('\n'); const responseText = await response.text(); const lines = responseText.split('\r\n'); // console.log(lines); const a = parseFloat(lines[0]); // x pixel size const b = parseFloat(lines[1]); // x rotation const c = parseFloat(lines[2]); // y rotation const d = parseFloat(lines[3]); // y pixel size const x_origin = parseFloat(lines[4]); // x coordinate of upper left corner const y_origin = parseFloat(lines[5]); // y coordinate of upper left corner return { a, b, c, d, x_origin, y_origin }; }, async getImageBoundsByTfw(tfwUrl, imageWidth, imageHeight) { const tfwData = await this.readTfwFile(tfwUrl); // console.log("tfwData", tfwData); const xmin = tfwData.x_origin; const ymax = tfwData.y_origin; const xmax = tfwData.x_origin + tfwData.a * imageWidth; const ymin = tfwData.y_origin + tfwData.d * imageHeight; const wgs84LatLng1 = this.Convert_EPSG3857_To_WGS84(ymax, xmin); const wgs84LatLng2 = this.Convert_EPSG3857_To_WGS84(ymin, xmax); const bounds = [ [wgs84LatLng1.lat, wgs84LatLng1.lng], [wgs84LatLng2.lat, wgs84LatLng2.lng] ]; // console.log("bounds", bounds); return bounds; }, getImageCenterByBounds(bounds) { const wgs84Center = { lat: bounds[0][0] + (bounds[1][0] -bounds[0][0]) / 2, lng: bounds[0][1] + (bounds[1][1] -bounds[0][1]) / 2, } return wgs84Center; }, // EPSG3857坐标(平面坐标) 转 WGS84经纬度坐标(球状坐标) Convert_EPSG3857_To_WGS84(lat, lng) { let tempLng = lng / 20037508.34 * 180; let tempLat = lat / 20037508.34 * 180; tempLat = 180 / Math.PI * (2 * Math.atan(Math.exp(tempLat * Math.PI / 180)) - Math.PI / 2); return { lng: lng == 0 ? 0 : tempLng, lat: lat == 0 ? 0 : tempLat }; }, // WGS84经纬度坐标(球状坐标) 转 EPSG3857坐标(平面坐标) Convert_WGS84_To_EPSG3857(lat, lng) { let mercator = {}; let x = lng * 20037508.34 / 180; let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); y = y * 20037508.34 / 180; mercator.x = x; mercator.y = y; return { lng: lng == 0 ? 0 : mercator.x, lat: lat == 0 ? 0 : mercator.y }; }, /* setMapServerUrl(url) { console.log("[setMapServerUrl] url: " + url) L.setMapServerUrl(url) }, setToken(token) { console.log("[setToken] token: " + token) L.setToken(token) }, */ addMapLayer(mapUrl, bounds) { // console.log('[addMapLayer] mapurl', mapurl) global.map_layer = L.imageOverlay(mapUrl, bounds).addTo(global.map); }, addMapLayer2(mapUrl) { // console.log('[addMapLayer] mapurl', mapurl) // var mapurl = 'static/map/' + mapid + '/{z}/{x}/{y}.png' // this.setMapServerUrl(mapUrl) // var tileUrl = mapUrl + '/{z}/{x}/{y}.png' var tileUrl = '{z}/{x}/{y}.png' global.map_layer = L.tileLayer(tileUrl, { // minZoom: minZoom, // maxZoom: maxZoom, errorTileUrl: '/static/image/tileMiss.png', tms: false, // 是否反转Y轴坐标, // keepBuffer: 20, // 平移地图时,在卸载切片之前,请保留这么多行和几列的切片 // attribution: '版权所有' }).addTo(global.map); }, // onWindowResize() { // var centPoint = global.mapOptions.center // if (centPoint != null) { // console.log("[Leaflet] onWindowResize centPoint:", centPoint) // global.map.setView(centPoint) // // global.map.panTo(centPoint) // } // }, extendControl() { var that = this L.Control.Search = L.Control.extend({ options: { position: 'topleft' //初始位置 }, initialize: function(options) { L.Util.extend(this.options, options) }, onAdd: function(map) { // var caller = global.getCaller() //创建Dom元素 L.DomUtil.create('元素类型', 'class类名') that.exControlContainer = L.DomUtil.create('div', 'leaflet-bar') that.elRoute = L.DomUtil.create('a', 'leaflet-control-common') that.elRoute.innerHTML = '路线' if (store.state.mapControlRoute) { L.DomUtil.setClass(that.elRoute, 'leaflet-control-selected') } that.elPlayer = L.DomUtil.create('a', 'leaflet-control-common') that.elPlayer.innerHTML = '玩家' if (store.state.mapControlPlayer) { L.DomUtil.setClass(that.elPlayer, 'leaflet-control-selected') } that.elPlayerTooltip = L.DomUtil.create('a', 'leaflet-control-common') that.elPlayerTooltip.innerHTML = '提示' if (store.state.mapControlTooltip) { L.DomUtil.setClass(that.elPlayerTooltip, 'leaflet-control-selected') } that.elPlayerTrail = L.DomUtil.create('a', 'leaflet-control-common') that.elPlayerTrail.innerHTML = '轨迹' if (store.state.mapControlTrail) { L.DomUtil.setClass(that.elPlayerTrail, 'leaflet-control-selected') } that.elList = L.DomUtil.create('a', 'leaflet-control-common') that.elList.innerHTML = '列表' if (store.state.mapPopupShow) { L.DomUtil.setClass(that.elList, 'leaflet-control-selected') } that.elFullScreen = L.DomUtil.create('a', 'leaflet-control-common') that.elFullScreen.innerHTML = '全屏' if (store.state.fullScreen) { L.DomUtil.setClass(that.elFullScreen, 'leaflet-control-selected') } // that.exControlContainer.style.display = 'flex' that.exControlContainer.appendChild(that.elRoute) that.exControlContainer.appendChild(that.elPlayer) that.exControlContainer.appendChild(that.elPlayerTooltip) that.exControlContainer.appendChild(that.elPlayerTrail) that.exControlContainer.appendChild(that.elList) that.exControlContainer.appendChild(that.elFullScreen) //注册事件 L.DomEvent.addListener(that.elRoute, 'click dblclick', function(ev) { that.elRouteClick(ev) }, that.route) L.DomEvent.addListener(that.elPlayer, 'click dblclick', function(ev) { that.elPlayerClick(ev) }, that.player) L.DomEvent.addListener(that.elPlayerTooltip, 'click dblclick', function(ev) { that.elPlayerTooltipClick(ev) }, that.player) L.DomEvent.addListener(that.elPlayerTrail, 'click dblclick', function(ev) { that.elPlayerTrailClick(ev) }, that.player) L.DomEvent.addListener(that.elList, 'click dblclick', function(ev) { that.elListClick(ev) }, this) L.DomEvent.addListener(that.elFullScreen, 'click dblclick', function(ev) { that.elFullScreenClick(ev) }, this) //返回这个主元素 return that.exControlContainer }, onRemove: function(map) { // Clean up the DOM }, }) //在L.control上添加一个search(封装好的函数) L.control.Search = function(options) { return new L.Control.Search(options) } //将自定义控件添加到地图上 L.control.Search().addTo(global.map) }, preventEvent(ev) { L.DomEvent.stopPropagation(ev) L.DomEvent.preventDefault(ev) }, elRouteClick(ev) { this.preventEvent(ev) this.route.toggle() this.toggleControl(this.elRoute) }, elPlayerClick(ev) { this.preventEvent(ev) this.toggleControl(this.elPlayer) this.player.togglePlayer() }, elPlayerTooltipClick(ev) { this.preventEvent(ev) this.toggleControl(this.elPlayerTooltip) this.player.toggleTooltip() console.log("store.state.mapControlPlayer: " + store.state.mapControlPlayer) if (!store.state.mapControlPlayer) { this.player.togglePlayer(true) // this.player.toggleTooltipFlag = false this.toggleControl(this.elPlayer) } }, elPlayerTrailClick(ev) { this.preventEvent(ev) this.toggleControl(this.elPlayerTrail) this.player.toggleTrail() }, elListClick(ev) { this.preventEvent(ev) this.toggleControl(this.elList) global.getCaller().popupToggle() }, elFullScreenClick(ev) { this.preventEvent(ev) this.toggleControl(this.elFullScreen) global.getCaller().fullScreenToggle() }, toggleControl(el) { if (L.DomUtil.hasClass(el, 'leaflet-control-selected')) { L.DomUtil.removeClass(el, 'leaflet-control-selected') } else { L.DomUtil.setClass(el, 'leaflet-control-selected') } }, long2tile(lon, zoom) { return (Math.floor((lon + 180) / 360 * Math.pow(2, zoom))); }, lat2tile(lat, zoom) { return (Math.floor((1 - Math.log(Math.tan(lat * Math.PI / 180) + 1 / Math.cos(lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom))); }, // 获取图片的Blob值 getImageBlob(url, cb) { var xhr = new XMLHttpRequest(); xhr.open("get", url, true); xhr.responseType = "blob"; xhr.onload = function() { if (this.status == 200) { if(cb) cb(this.response); } }; xhr.send(); }, // 预加载地图瓦片 preLoadTile() { return var zoom = global.map.getZoom() if (zoom >= global.mapOptions.maxZoom) { console.log("[preLoadTile] 已达到最大缩放级别,无需预加载地图瓦片") return } var preloadZoom = zoom + 1; if (global.getPreloadTileMapByZoom(preloadZoom) != null) { console.log("[preLoadTile] 跳过,已预加载地图瓦片 Zoom: " + preloadZoom) return } else { console.log("[preLoadTile] 预加载地图瓦片 Zoom: " + preloadZoom) global.setPreloadTileMap({ zoom: preloadZoom }) } var bounds = null var preloadBounds = global.getPreloadBounds() if (preloadBounds != null) { // console.log("[preLoadTile] preloadBounds", preloadBounds) bounds = L.latLngBounds(preloadBounds) } else { console.warn("[preLoadTile] preloadBounds == null") bounds = global.map.getBounds().pad(0.2) } var west = bounds.getWest() var south = bounds.getSouth() var east = bounds.getEast() var north = bounds.getNorth() // console.log("[preLoadTile] 预加载地图瓦片", bounds, west, south, east, north, zoom) // L.circleMarker([south, west], { // radius: 18, // 圆的半径 px // weight: 1.6, // 描边宽度 px // opacity: 1.0, // 描边不透明度 // fill: true, // 是否填充 // fillColor: 'red', // 填充颜色 // fillOpacity: 1.0, // 填充不透明度 // }) // .addTo(global.map) // console.log("[preLoadTile] circleMarker", [south, west]) // L.circleMarker([north, east], { // radius: 18, // 圆的半径 px // weight: 1.6, // 描边宽度 px // opacity: 1.0, // 描边不透明度 // fill: true, // 是否填充 // fillColor: 'red', // 填充颜色 // fillOpacity: 1.0, // 填充不透明度 // }) // .addTo(global.map) // console.log("[preLoadTile] circleMarker", [north, east]) // Determine which tile we need var dataEast = this.long2tile(east, preloadZoom) var dataWest = this.long2tile(west, preloadZoom) var dataNorth = this.lat2tile(north, preloadZoom) var dataSouth = this.lat2tile(south, preloadZoom) var mapUrl = global.getMapUrl() // console.log("[preLoadTile] dataNorth = " + dataNorth + " dataSouth = " + dataSouth) for (let y = dataNorth; y < dataSouth + 1; y++) { // console.log("[preLoadTile] y = " + y) for (let x = dataWest; x < dataEast + 1; x++) { // console.log("[preLoadTile] x = " + x) var url = mapUrl + '/' + preloadZoom + '/' + x + '/' + y + '.png' var img = new Image() this.getImageBlob( url , function(blob){ // console.log(blob) img.src = URL.createObjectURL(blob); }); // img.src = url // console.log("[preLoadTile] 预加载地图瓦片", url) } } }, test() { console.log("test ok") }, free() { console.log("[Leaflet] free()") player.free() // checkPoint.free() route.free() } }