Vue离线地图最终解决方案

·  阅读 3166
Vue离线地图最终解决方案

离线地图最终解决方案

前言

​ 能找到这个帖子的朋友应该是公司要求做离线地图,但是没了解过的吧,我前一段也是公司要求做离线地图但是我没了解过,我就去搜了很多文章,找了很多方案,最后和leader定下来了两个方案,一个是使用一张固定缩放的图片,然后将像素转化为px来做撒点的效果,另一个就是使用瓦片地图框架openlayers来实现瓦片地图,后文将一一介绍两个方法。

  • 方法1:用一张固定缩放的图片将像素转化成px来做撒点的效果
  • 方法2:用地图框架openlayers来实现瓦片地图

1.使用openlayers来实现瓦片地图

为什么使用 openlayers而不是使用leaflet,原因是我找到的教程很多是openlayers的,我看leaflet的很多功能都是社区做的,这样会增大我的学习量,所以我没有选

openlayers官网

首先解决地图问题

如果你要用原版地图的话,就可以直接使用地图下载工具瓦片地图下载工具,这只支持你下载原版地图,还有就是每个地图公司的坐标系可能存在不一样,本文是用腾讯地图来做的,为什要用腾讯一会儿说。

为什么要用腾讯地图?

因为腾讯地图有一个东西叫做自定义地图样式的工具,据网上帖子来说,你可以通过控制台去抓取到你自定义样式的地图的瓦片文件,然后用一些工具去批量抓取,但是实测百度地图更新了,抓不到了就很烦!

  • 用上面的那个地图下载工具下载的地图格式有点问题,你需要给文件夹改成下面这样的格式

wjgs.webp

地图存放到服务器

如果给本地搞个Tomcat什么的起个服务会很麻烦,这里也是用一个非常快捷的办法直接解决

  • http-server

  • //首先先去下载
    npm install httpserver
    复制代码
  • //上一步下载完毕后,在你瓦片的分级目录里面打开cmd直接启动服务
    http-server
    复制代码
  • 下面会输出一堆东西,不用管他,你只需要知道那个是你的ip+端口就行了,将你的ip+端口复制走,等下要用

使用openlayers加载离线地图,用Vue项目为例

//小声bb : 其实到这里下面的步骤基本就是复制另一个帖子了 这个帖子也给了我很多帮助
复制代码

openlayers 实战离线地图

安装openlayers
  • npm install ol
    复制代码
  • //或者直接用js引入
    <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.8.1/build/ol.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.8.1/css/ol.css">
    
    复制代码
复制下面代码
<template>
  <div style="width: 100%;height: 100%">
    <div class="map" id="map"></div>
    <el-card id="popup" class="popup">
      <div class="popupContainer"></div>
    </el-card>
  </div>
</template>

<script>
import 'ol/ol.css';
import Map from 'ol/Map';
import Feature from 'ol/Feature';
import VectorSource from 'ol/source/Vector';
import Overlay from 'ol/Overlay';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import View from 'ol/View';
import {transform} from 'ol/proj';
import XYZ from 'ol/source/XYZ'
import Point from 'ol/geom/Point';
import GeoJSON from 'ol/format/GeoJSON';
import {Fill, Stroke, Icon, Style} from 'ol/style'
import markerImg from '@/assets/img/markerIcon.png'
export default {
  name: "openlayersMap",
  data () {
    return {
      mapObj: null,
      mapDom: null,
      mapPointList: [],
      pointLayerSource:null,
      pointLayer: null,
      markerIcon: markerImg
    }
  },
  mounted() {
    this.initMap()
  },
  methods: {
    // 清除地图 某些情况 地图容器会存在两个 导致地图无法正常显示 这个问题折腾了我半天。
    // 找了半天官方貌似也没有提供 对应的 api,自己动手了。
    mapClear (){
      if (this.mapDom) {
        this.mapDom.innerHTML = ''
        this.mapDom = null
      }
    },
    
    // 初始化地图
    initMap () {
      // 先尝试清除
      this.mapClear()
      // 获取地图容器
      this.mapDom = document.getElementById('map')

      // 初始化地图配置
      this.mapObj = new Map({
        target: this.mapDom, // 地图容器
        view: new View({
          //设置你的地图初始中心点
          center: [117.990969, 36.635013], // 地图中心点
          zoom: 10, // 缩放
          projection: 'EPSG:4326' // 坐标系
        })
      })

      // 添加一个使用离线瓦片地图的层
      const offlineMapLayer = new TileLayer({
        source: new XYZ({
          //这里的url就是你要改的地方
          url: 'http://192.168.3.6:8081' + '/{z}/{x}/{y}.png'  // 设置本地离线瓦片所在路径
        })
      })
      // 将图层添加到地图
      this.mapObj.addLayer(offlineMapLayer)

      // 加载地理坐标
      this.addPoint()
    },

    // 添加地理坐标
    addPoint () {
      this.delPointAll()
      // 地理坐标数组
      const pointData = [
        {longitude: 117.990969, latitude: 36.635013}
      ]

      pointData.map(item => {
        // 创建点
        const point = new Feature({
          geometry: new Point([item.longitude, item.latitude]),
          data: item
        })

        // 点的样式
        const iconStyle = new Style({
          image: new Icon({
            color: '#ffffff',
            crossOrigin: 'anonymous',
            src: this.markerIcon,
          }),
        })
        // 设置样式
        point.setStyle(iconStyle)
        // 保存到数据  方便删除
        this.mapPointList.push(point)
      })

      // 创建geojson据源
      this.pointLayerSource = new VectorSource({features: this.mapPointList})
      // 创建图层 并加载数据
      this.pointLayer = new VectorLayer({source: this.pointLayerSource})
      // 将图层添加地图上
      this.mapObj.addLayer(this.pointLayer)
    },

    // 地理点位删除
    delPointAll(){
      // 判断 删除的数据源是否存在
      if (this.pointLayerSource) {
        // 遍历删除
        this.mapPointList.map(item => {
          this.pointLayerSource.removeFeature(item)
        })

        // 删除图层 重置数据
        this.mapObj.removeLayer(this.pointLayer)
        this.pointLayerSource = null
        this.pointLayer = null
        this.mapPointList = []
      }
    }
  },
  beforeDestroy() {
    this.mapClear()
  }
}
</script>

<style scoped>
.map {
  width: 100%;
  height: 100%;
}
</style>

复制代码

这里面你需要改的地方基本就那几个

  • this.mapObj 中的center字段,是用来设置你地图的默认中心点的
  • offlineMapLayer中的url这个字段,后面的不需要改,只需要给前面的ip+端口改成你本地启动的就行
  • 那么跑起来基本就没啥问题了。

撒点功能的实现

撒点应该很多做地图的都要实现这个功能吧

还是看上述代码块,他中间的addPoint中的pointData数组是用来放你要撒的点的坐标的,iconStyle是用来设置点的样式的,你可以直接给color删掉,然后给最后的src赋上图片链接就行了

撒点弹窗功能的实现

这个可以看一下原帖的另一段代码,因为这块我没有用到所以就没去看他

 // 地图点击事件
    this.mapObj.on('click', function (evt) {
        // 获取点击位置的数据
        const feature = self.mapObj.forEachFeatureAtPixel(evt.pixel, function (feature) {
            return feature;
        })

        // 根据 点击元素 className 判断是否点击在自定义popup上
        const isClickPopUp = evt.originalEvent.path.map(item => item.className).includes('el-card__body')
        if (!isClickPopUp) {
            popupDom.style.display = 'none'
        }

        // 官方示例 采用 jq + bootstrap弹窗,但是我觉得没有必要 如果大量使用bootstrap 组件可以考虑引入。
        const popupContainer = document.getElementsByClassName('popupContainer')[0]

        // 判断数据
        if (feature) {
            // feature.values_.data ,data字段是在 addPoint 函数创建point时添加 可以自定义
            if (feature.values_.data) {
                const pointData = feature.values_.data
                popup.setPosition(evt.coordinate)
                popupContainer.innerHTML = `<div>${pointData.name}</div>`
                popupDom.style.display = 'block'
            }
        }
    })
复制代码

区县范围的实现

这个具体看原帖吧,我忘了当时是咋写的了QAQ

openlayers 实战离线地图

2.截取地图将坐标转换为px

这个方案其实就简单很多了,你需要给地图固定到一个缩放等级,很多东西都是写死的,然后直接通过一套算法去转换就行了

首先data里面要有一些静态的数据
data() {
    return {
      //   地图缩放等级
      zoom: 15,
      //   一些默认值
      MCBAND: [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0],
      LLBAND: [75, 60, 45, 30, 15, 0],
      MC2LL: [
        [
          1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331,
          200.9824383106796, -187.2403703815547, 91.6087516669843,
          -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2,
        ],
        [
          -7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289,
          96.32687599759846, -1.85204757529826, -59.36935905485877,
          47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86,
        ],
        [
          -3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616,
          59.74293618442277, 7.357984074871, -25.38371002664745,
          13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37,
        ],
        [
          -1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591,
          40.31678527705744, 0.65659298677277, -4.44255534477492,
          0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06,
        ],
        [
          3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062,
          23.10934304144901, -0.00023663490511, -0.6321817810242,
          -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4,
        ],
        [
          2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8,
          7.47137025468032, -0.00000353937994, -0.02145144861037,
          -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5,
        ],
      ],
      LL2MC: [
        [
          -0.0015702102444, 111320.7020616939, 1704480524535203,
          -10338987376042340, 26112667856603880, -35149669176653700,
          26595700718403920, -10725012454188240, 1800819912950474, 82.5,
        ],
        [
          0.0008277824516172526, 111320.7020463578, 647795574.6671607,
          -4082003173.641316, 10774905663.51142, -15171875531.51559,
          12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5,
        ],
        [
          0.00337398766765, 111320.7020202162, 4481351.045890365,
          -23393751.19931662, 79682215.47186455, -115964993.2797253,
          97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5,
        ],
        [
          0.00220636496208, 111320.7020209128, 51751.86112841131,
          3796837.749470245, 992013.7397791013, -1221952.21711287,
          1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5,
        ],
        [
          -0.0003441963504368392, 111320.7020576856, 278.2353980772752,
          2485758.690035394, 6070.750963243378, 54821.18345352118,
          9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5,
        ],
        [
          -0.0003218135878613132, 111320.7020701615, 0.00369383431289,
          823725.6402795718, 0.46104986909093, 2351.343141331292,
          1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45,
        ],
      ],
      //   地图可视区域
      N: { width: 1226, height: 1926 },
      //   当前可视范围中心点坐标
      Q: { lng: 121.4391, lat: 31.1676 },
      //   坐标点数组
      items: [
        { x: 121.445613, y: 31.175118 },
        { x: 121.462861, y: 31.200346 },
        { x: 121.429338, y: 31.145276 },
        { x: 121.411697, y: 31.166757 },
        { x: 121.41999, y: 31.182991 },
      ],
    };
  },
复制代码
下面是一些转换算法
// 定位点转px计算函数
    /** *********将地图坐标转换成像素***********/
    // point:当前像素 var point = new BMap.Point(x, y);
    // zoom:当前地图缩放级别 var zoom = map.getZoom();
    // center:当前地图可视范围中心点坐标 var center = map.getCenter();
    // bounds:地图可视区域    var bound = map.getSize();
    PointToPixel(point, zoom, center, bounds) {
      // 坐标到像素
      if (!point) {
        return;
      }
      point = this.FormatPoint(point);
      center = this.FormatPoint(center);
      var units = this.GetZoomUnits(zoom);
      var x = Math.round((point.lng - center.lng) / units + bounds.width / 2);
      var y = Math.round((center.lat - point.lat) / units + bounds.height / 2);
      // 这个应该是转换完成的像素
      return { x: x, y: y };
    },
    // 转换缩放级别
    GetZoomUnits(zoom) {
      return Math.pow(2, 18 - zoom);
    },
    FormatPoint(point) {
      let lng_lat;
      var mc;
      point.lng = this.getLoop(point.lng, -180, 180);
      point.lat = this.getRange(point.lat, -74, 74);
      lng_lat = {
        lng: point.lng,
        lat: point.lat,
      };
      for (let i = 0; i < this.LLBAND.length; i++) {
        if (lng_lat.lat >= this.LLBAND[i]) {
          mc = this.LL2MC[i];
          break;
        }
      }
      if (!mc) {
        for (let i = this.LLBAND.length - 1; i >= 0; i--) {
          if (lng_lat.lat <= -this.LLBAND[i]) {
            mc = this.LL2MC[i];
            break;
          }
        }
      }
      var cE = this.convertor(point, mc);
      lng_lat = {
        lng: cE.lng.toFixed(2),
        lat: cE.lat.toFixed(2),
      };
      return lng_lat;
    },
    getLoop(lng, a, b) {
      while (lng > b) {
        lng -= b - a;
      }
      while (lng < a) {
        lng += b - a;
      }
      return lng;
    },
    getRange(lat, a, b) {
      if (a != null) {
        lat = Math.max(lat, a);
      }
      if (b != null) {
        lat = Math.min(lat, b);
      }
      return lat;
    },
    convertor(point, mc) {
      if (!point || !mc) {
        return;
      }
      var lng = mc[0] + mc[1] * Math.abs(point.lng);
      var c = Math.abs(point.lat) / mc[9];
      var lat =
        mc[2] +
        mc[3] * c +
        mc[4] * c * c +
        mc[5] * c * c * c +
        mc[6] * c * c * c * c +
        mc[7] * c * c * c * c * c +
        mc[8] * c * c * c * c * c * c;
      lng *= point.lng < 0 ? -1 : 1;
      lat *= point.lat < 0 ? -1 : 1;
      return { lng: lng, lat: lat };
    },
    isblock(items) {
      this.aleat = items;
      this.isAleat = !this.isAleat;
    },
复制代码

这些算法你不需要管他都干了什么(我也看不大懂),只需要知道调用PointToPixel方法他会返回给你一个对象,这个对象就是你要的xy的坐标就行了

const px = this.PointToPixel(Q, this.zoom, this.Q, this.N);
复制代码

只有第一个值是动态传的你的坐标,后面的三个值全部都是死值!

然后你根据这个px去做定位就行了,v-for去循环一堆span出来然后position: absolute;动态绑定这个px就好了,至于点击弹窗的话,你可以给span外面套个div然后给span下面再写个div,再用定位定上去就行了,至于点击切换也不是很麻烦,然后用伪类写个小箭头就好了!

第二种方案肯定是简单很多的,但是效果肯定是差很多。

这就是我在网上搜了两天找出来的比较合理的地图解决方案,如果觉得有用希望能给点个赞,如果文中有什么错误的地方,希望各位能在评论区指出,我及时的改正!

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改