openLayer,一文解锁自己做地图成就了,容易上手🤪

1,031 阅读7分钟

1. 前言

最近在给JJ做一个交通事故预警方面的管理后台

说到交通事故领域的后台肯定少不了地图啊,之前本来以为要用高德、百度之类的地图用人家现成的API,我想,那不是so Easy吗~~~

谁料到沟通后才知道考虑到数据安全,选用的地图是他们自研的,市面上的地图是不能用的

我第一反应听到这个,心里OS: 这得多有含金量啊,自研开发一个地图?GOD~~~

后来,经过沟通调研后,发现其实openLayer可以覆盖这个需求,也就是本文的主角登场啦~~~

openLayer 可以使用自己的地图模板,把自己的坐标数据渲染在地图上,渲染使用的一些API也是类的内部函数,没走接口

image.png

2. 效果展示

888.gif

下面我们开始一步、两步、三步、四步望着天,看星星一颗、两颗、三颗、四颗连成线~~~

开始一步一步来实现这个效果

3. 基本概念

4. 搭一个框架

<template>
  <div class="about">
    <div class="map" id="map" ref="myMap"></div>
  </div>
</template>
<script setup>
  import { Map, View } from "ol";
  import TileLayer from "ol/layer/Tile";
  import VectorLayer from "ol/layer/Vector";
  import { transform } from "ol/proj";
  import { Vector, XYZ } from "ol/source";
  import { onMounted, ref } from "vue";

  const map = ref();
  const myMap = ref();

  onMounted(() => {
    initMap();
  });
  const initMap = () => {
    const amap = new TileLayer({
      source: new XYZ({
        url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}",
        wrapX: false,
      }),
    });
    map.value = new Map({
      target: myMap.value,
      layers: [amap],
      view: new View({
        center: transform([121.9281067, 30.90944], "EPSG:4326", "EPSG:3857"),
        zoom: 9,
        // minZoom: 8,
      }),
    });
  };
</script>
<style>
  @media (min-width: 1024px) {
    .about {
      min-height: 100vh;
      display: flex;
      align-items: center;
    }
  }
  .map {
    width: 100vw;
    height: 80vh;
  }
</style>

就这几行代码,地图就出来了

image.png

这里说下关键API:

  • transform:把GPS标准坐标转为地图可展示的投影坐标(想了解更彻底的可以搜下EPSG:4326和EPSG:3857)
// GPS标准坐标系 WGS 84
transform([121.9281067, 30.90944], "EPSG:4326", "EPSG:3857")
  • TileLayerLayer的一种,创建一个使用Open Street Map地图源的瓦片图层

  • XYZSource的一种,Web 地图瓦片编码方案,模板可自定义,每个瓦片都有一个唯一的标识符,由 X、Y 和 Z 三个坐标值组成

  • OSMSource的一种,开源的在线地图服务

  • View:设置显示地图的视图

    • center:地图中心点
    • zoom:层级展示(缩放级别)

LayerSource是一对一的关系

有一个Layer必然需要一个Source,然后把这个Layer添加到Map上,就可以显示出来了

上面Source使用过的是XYZ,如果使用OSM,效果是这样的

image.png

5. 确定渲染的Feature

基于以上的代码,新增一个addTrack函数

import markIcon from "./car.png";
let tracksLayer = [];
let styles = {};
onMounted(() => {
    initMap();
    addTrack()
});


const coordinates = [
   transform([121.0, 31.0], "EPSG:4326", "EPSG:3857"),
    transform([121.1, 31.1], "EPSG:4326", "EPSG:3857"),
    transform([121.2, 31.1], "EPSG:4326", "EPSG:3857"),
    transform([121.2, 31.4], "EPSG:4326", "EPSG:3857"),
    transform([121.4, 31.4], "EPSG:4326", "EPSG:3857"),
  ];
const singleTrack = (trackLine) => {
    // 矢量图层几何图形 - 线
    const trackFeature = new Feature({
      type: "track",
      geometry: trackLine,
    });
    // 矢量图层几何图形 - 开始点
    const startMarker = new Feature({
      type: "geoMarker",
      geometry: new Point(trackLine.getFirstCoordinate()),
    });
    // 矢量图层几何图形 - 结束点
    const endMarker = new Feature({
      type: "geoMarker",
      geometry: new Point(trackLine.getLastCoordinate()),
    });
    // 矢量图层几何图形 - 移动点
    const position = startMarker.getGeometry().clone();
    const geoMarker = new Feature({
      type: "icon",
      geometry: position,
    });

    return {
      layer: new VectorLayer({
        source: new Vector({
          features: [trackFeature, geoMarker, startMarker, endMarker],
        }),
        style: (feature) => {
          return styles[feature.get("type")];
        },
      }),
    };
  };
  const initMultiplyTrack = (tracks) => {
    return tracks.map((item) => singleTrack(item));
  };

  const addLayer = (tracks) => {
    styles = {
      track: new Style({
        stroke: new Stroke({
          width: 6,
          color: [220, 30, 60, 0.9],
        }),
      }),
      icon: new Style({
        image: new Icon({
          anchor: [0.5, 0.5],
          scale: 0.3,
          src: markIcon,
        }),
      }),
      geoMarker: new Style({
        image: new Circle({
          radius: 8,
          fill: new Fill({ color: "#333" }),
          stroke: new Stroke({
            color: "#f00",
            width: 2,
          }),
        }),
      }),
    };
    if (Array.isArray(tracks) && tracks.length > 0) {
      tracksLayer = initMultiplyTrack(tracks);
      tracksLayer.forEach((item) => {
        // 在地图中画不同轨迹layer
        map.value.addLayer(item.layer);
      });
    }
  };
const addTrack = () => {
    let trackLine = new LineString(coordinates);
    addLayer([trackLine]);
  };

代码加完之后,效果长这样

image.png

可以看到,我们总共加了4个Feature:

  • trackFeature:红色的线

  • startMarker:开始点

  • endMarker:结束点

  • geoMarker:小车

细心的小伙伴肯定发现了,其实目前代码是支持同时绘制多条轨迹

addLayer([轨迹1, 轨迹2])

关键API:

1,VectorLayerLayer的一种,是一个图层,承载Feature集合

2,VectorSource的一种,Feature集合,存放各种几何图形(线,点,矩形等)

3,map.value.addLayer(item.layer):把VectorLayer图层地图中渲染

6. 添加小车,让它跑起来

基于以上的代码,新增一个addTrack函数

  import { getVectorContext } from "ol/render";

  let animating = false;
  let lastTime;
  const speed = 200;
  let distance = 0;
  
const singleTrack = (trackLine) => {
    // 矢量图层几何图形 - 线
    const trackFeature = new Feature({
      type: "track",
      geometry: trackLine,
    });
    // 矢量图层几何图形 - 开始点
    const startMarker = new Feature({
      type: "geoMarker",
      geometry: new Point(trackLine.getFirstCoordinate()),
    });
    // 矢量图层几何图形 - 结束点
    const endMarker = new Feature({
      type: "geoMarker",
      geometry: new Point(trackLine.getLastCoordinate()),
    });
    // 矢量图层几何图形 - 移动点
    const position = startMarker.getGeometry().clone();
    const geoMarker = new Feature({
      type: "icon",
      geometry: position,
    });

    return {
      trackLine, // 默认的轨迹线
      geoMarker, // 移动点
      position, // 移动点的点几何图形
      layer: new VectorLayer({
        source: new Vector({
          features: [trackFeature, geoMarker, startMarker, endMarker],
        }),
        style: (feature) => {
          return styles[feature.get("type")];
        },
      }),
    };
  };
  
  const moveFeature = (event, item) => {
    const time = event.frameState.time;
    const elapsedTime = time - lastTime;
    distance = (distance + (speed * elapsedTime) / 1e6) % 2;
    lastTime = time;

    const currentCoordinate = item.trackLine.getCoordinateAt(
      distance > 1 ? 2 - distance : distance
    );
    if (distance >= 1) {
      stopMove("single", item);
    }
    item.position.setCoordinates(currentCoordinate);
    const vectorContext = getVectorContext(event);
    vectorContext.setStyle(styles.icon);
    vectorContext.drawGeometry(item.position);
    map.value.render();
  };

  const startMove = () => {
    if (animating) return;
    if (distance >= 1) return;
    animating = true;
    lastTime = Date.now();
    tracksLayer.forEach((item) => {
      item.moveFeature = (e) => moveFeature(e, item);
      item.layer.on("postrender", item.moveFeature);
      item.geoMarker.setGeometry(null);
    });
  };

  const stopMove = (type, item) => {
    if (!animating) return;
    if (type === "single") {
      item.geoMarker.setGeometry(item.position);
      item.layer.un("postrender", item.moveFeature);
      return;
    }
    animating = false;

    tracksLayer.forEach((item) => {
      // Keep marker at current animation position
      item.geoMarker.setGeometry(item.position);
      item.layer.un("postrender", item.moveFeature);
    });
  };

就可以实现下面这种效果啦

999.gif

关键API:

触发startMove函数后会执行下面这两个关键API

item.layer.on("postrender", item.moveFeature);
item.geoMarker.setGeometry(null);
  • layer.on(postrender, moveFeature): 此处监听VectorLayer图层(就是上面存放小车、线的图层)的渲染,每当触发渲染就执行moveFeature函数

  • setGeometry:给小车坐标清空掉达到触发一次VectorLayer图层渲染的效果

触发moveFeature函数后会执行下面这几个关键API

  • trackLine.getCoordinateAt(distance):通过运行时间的运算得到当前轨迹下小车的坐标

  • setCoordinates(currentCoordinate):给小车设置得到的坐标

  • getVectorContext:得到当前图层(VectorLayer图层)的上下文

  • getVectorContext.drawGeometry:绘制小车

  • map.value.render():更新地图,同时再次触发postrender的渲染

触发stopMove函数会执行下面这两个关键API

  • setGeometry(item.position):给小车设置最后一次坐标

  • layer.un(postrender, moveFeature):销毁监听VectorLayer图层的渲染

中场休息,写累了~~~

image.png

上了个厕所,来吧,让我们再战~~~

7. 留住小车移动的轨迹

const singleTrack = (trackLine) => {
    // 矢量图层几何图形 - 线
    const trackFeature = new Feature({
      type: "track",
      geometry: trackLine,
    });
    // 矢量图层几何图形 - 开始点
    const startMarker = new Feature({
      type: "geoMarker",
      geometry: new Point(trackLine.getFirstCoordinate()),
    });
    // 矢量图层几何图形 - 结束点
    const endMarker = new Feature({
      type: "geoMarker",
      geometry: new Point(trackLine.getLastCoordinate()),
    });
    // 矢量图层几何图形 - 移动点
    const position = startMarker.getGeometry().clone();
    const geoMarker = new Feature({
      type: "icon",
      geometry: position,
    });
    // 矢量图层几何图形 - 移动痕迹的线
    const carTrackFeature = new Feature({
      type: "carTrack",
      geometry: new LineString([trackLine.getFirstCoordinate()]),
    });
    const carPosition = carTrackFeature.getGeometry().clone();

    return {
      trackLine, // 默认的轨迹线
      geoMarker, // 移动点
      position, // 移动点的点几何图形
      carTrackFeature, // 移动轨迹线
      carPosition, // 移动轨迹线的线几何线图形
      passCoordinates: [], // 存放当前动画的移动轨迹线的坐标数据
      layer: new VectorLayer({
        source: new Vector({
          features: [
            trackFeature,
            geoMarker,
            startMarker,
            endMarker,
            carTrackFeature,
          ],
        }),
        style: (feature) => {
          return styles[feature.get("type")];
        },
      }),
    };
  };
  
const addLayer = (tracks) => {
...
    styles = {
      ...,
      carTrack: new Style({
        stroke: new Stroke({
          width: 6,
          color: [117, 46, 195, 1],
        }),
      }),
    };
...
  };
  
const drawCarTrack = (event, item) => {
    const vectorContext1 = getVectorContext(event);
    vectorContext1.setStyle(styles.carTrack);
    vectorContext1.drawGeometry(item.carPosition);
  };

  const moveFeature = (event, item) => {
  ...
  // 给移动线几何设置坐标
    item.passCoordinates.push(currentCoordinate);
    item.carPosition.setCoordinates(item.passCoordinates);
    drawCarTrack(event, item);
    
    ...
  
  })
  
  const stopMove = (type, item) => {
  ...
  
  item.carTrackFeature.setGeometry(item.carPosition);
  ...
  
  })

这里没啥新的API介绍了,主要和小伙伴说下做了些什么

1,新增了一个轨迹痕迹的线carTrackFeature和它的样式carTrack

2,moveFeature函数中新增了这几件事

  • 通过passCoordinates收集运行的坐标点

  • 通过carPosition.setCoordinates把收集的坐标点数组赋值给轨迹痕迹的线

  • 通过drawGeometry把痕迹线画在画布上

3,stopMove函数中通过carTrackFeature.setGeometry(item.carPosition)最后把所有收集到的坐标点数组赋值给痕迹线,最后渲染一次

至此,就结束啦

8. 代码自提区

9. 总结

这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。

欢迎转载,但请注明来源。

最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。

image.png