1. 前言
最近在给JJ做一个交通事故预警方面的管理后台
说到交通事故领域的后台肯定少不了地图啊,之前本来以为要用高德、百度之类的地图用人家现成的API,我想,那不是so Easy吗~~~
谁料到沟通后才知道考虑到数据安全,选用的地图是他们自研的,市面上的地图是不能用的
我第一反应听到这个,心里OS: 这得多有含金量啊,自研开发一个地图?GOD~~~
后来,经过沟通调研后,发现其实openLayer可以覆盖这个需求,也就是本文的主角登场啦~~~
openLayer 可以使用自己的地图模板,把自己的坐标数据渲染在地图上,渲染使用的一些API也是类的内部函数,没走接口
2. 效果展示
下面我们开始一步、两步、三步、四步望着天,看星星一颗、两颗、三颗、四颗连成线~~~
开始一步一步来实现这个效果
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>
就这几行代码,地图就出来了
这里说下关键API:
- transform:把
GPS标准坐标转为地图可展示的投影坐标(想了解更彻底的可以搜下EPSG:4326和EPSG:3857)
// GPS标准坐标系 WGS 84
transform([121.9281067, 30.90944], "EPSG:4326", "EPSG:3857")
-
TileLayer:
Layer的一种,创建一个使用Open Street Map地图源的瓦片图层 -
XYZ:
Source的一种,Web 地图瓦片编码方案,模板可自定义,每个瓦片都有一个唯一的标识符,由 X、Y 和 Z 三个坐标值组成 -
OSM:
Source的一种,开源的在线地图服务 -
View:设置显示地图的视图
- center:地图中心点
- zoom:层级展示(缩放级别)
Layer和Source是一对一的关系
有一个Layer必然需要一个Source,然后把这个Layer添加到Map上,就可以显示出来了
上面Source使用过的是XYZ,如果使用OSM,效果是这样的
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]);
};
代码加完之后,效果长这样
可以看到,我们总共加了4个Feature:
-
trackFeature:红色的线
-
startMarker:开始点
-
endMarker:结束点
-
geoMarker:小车
细心的小伙伴肯定发现了,其实目前代码是支持同时绘制多条轨迹的
addLayer([轨迹1, 轨迹2])
关键API:
1,VectorLayer:Layer的一种,是一个图层,承载Feature集合
2,Vector:Source的一种,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);
});
};
就可以实现下面这种效果啦
关键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图层的渲染
中场休息,写累了~~~
上了个厕所,来吧,让我们再战~~~
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. 总结
这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。
欢迎转载,但请注明来源。
最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。