^ 关注我,带你一起学GIS ^
注:当前使用的是 ol [9.2.4] 版本,天地图使用的
key
请到天地图官网申请,并替换为自己的key
前言
❝要素移动动画类似于轨迹动画,只不过要素移动动画是在地图上起点和终点之间来回移动。通过使用渲染后事件(postrender)和向量上下文沿直线设置标记特征,在路径上实现要素移动。
1. 数据准备
提前准备好路线GeoJSON数据,然后使用相应方法读取数据。通过fetch方法读取GeoJSON文件,再通过response.json()
方法读取出目标数据。
// 获取路线JSON数据
fetch("../../data/geojson/route.json").then(response => {
// 读取GeoJSON数据
response.json().then(res => {
// 路线数据
const route = new ol.format.GeoJSON().readGeometry(res.geometry)
})
)
不知道如何获取GeoJSON路线数据的同学,可以参考之前的文章:OpenLayers 绘制几何对象打开浏览器控制台,点击线按钮进行绘制,绘制完成后,可以看到控制台中输出的信息即为GeoJSON路线数据。
2. 添加要素图层
创建路径要素routeFeature
、起始标注要素startMarker
、结束标注要素endMarker
以及移动要素geoMarker
,然后创建矢量图层并将其添加到地图中。
// 路线数据
const route = new ol.format.GeoJSON().readGeometry(res.geometry)
// 创建Feature对象
const routeFeature = new ol.Feature({
geometry: route,
type: "route"
})
//起始标注
const startMarker = new ol.Feature({
type: "icon",
geometry: new ol.geom.Point(route.getFirstCoordinate())
})
//结束标注
const endMarker = new ol.Feature({
type: "icon",
geometry: new ol.geom.Point(route.getLastCoordinate())
})
//复制起始坐标
const position = startMarker.getGeometry().clone()
const geoMarker = new ol.Feature({
type: "geoMarker",
geometry: position
})
const styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
color: [237, 212, 0, 0.8],
width: 6
})
}),
"icon": new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: "../../image/icon.png"
})
}),
"geoMarker": new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: "black"
}),
stroke: new ol.style.Stroke({
color: "white",
width: 2
})
})
})
}
// 创建矢量图层
const vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
features: [routeFeature, geoMarker, startMarker, endMarker]
}),
style: (feature) => {
return styles[feature.get("type")]
}
})
map.addLayer(vectorLayer)
3. 要素动画函数
在该方法中要实现在地图上移动一个地理标记(如图标等)以实现动画效果。首先需要获取到当前移动的速度值,计算当前帧的时间,并计算持续时间和移动距离。更新位置对象坐标,在地图上绘制移动要素。最后使用map.render()
方法继续渲染动画,使动画持续进行
// 监听移动事件
function moveFeature(event) {
const speed = Number(speedInput.value)
// 获取当前帧的时间
const time = event.frameState.time
// 计算自上次渲染以来经过的时间
const elapsedTime = time - lastTime
// 根据速度和经过的时间更新距离,并取模2,使距离在0-2之间循环
distance = (distance + (speed * elapsedTime) / 1e6) % 2
// 更新上次渲染时间
lastTime = time
// 根据当前的距离计算出对应的坐标,当距离大于1时,使用2减去距离,以实现来回移动的效果
const currentCoordinate = route.getCoordinateAt(
distance > 1 ? 2 - distance : distance
)
// 更新位置对象的坐标
position.setCoordinates(currentCoordinate)
// 获取向量上下文对象,用于绘制图形
const vectorContext = ol.render.getVectorContext(event)
// 设置绘制样式
vectorContext.setStyle(styles.geoMarker)
// 绘制位置对象
vectorContext.drawGeometry(position)
// 继续渲染动画,使动画持续进行
map.render()
}
4. 控制动画方法
创建开始动画函数startAnimation
和结束动画函数stopAnimation
。在开始动画函数中更改按钮显示文本为”停止动画”,然后添加要素移动监听事件moveFeature
,并且移除原来的标注。在结束动画函数中更改按钮显示文本为”开始动画”,然后移除要素移动监听事件moveFeature
,并且更新标注对象在动画停止位置。最后添加开始按钮监听事件,用于控制动画开始和停止。
// 开始动画函数
function startAnimation() {
animating = true
lastTime = Date.now()
startButton.textContent = "停止动画"
// 添加要素移动监听事件
vectorLayer.on("postrender", moveFeature)
// 移除标注
geoMarker.setGeometry(null)
}
// 结束动画函数
function stopAnimation() {
animating = false
startButton.textContent = "开始动画"
// 保持标注在动画停止位置
geoMarker.setGeometry(position)
// 移除要素移动监听事件
vectorLayer.un("postrender", moveFeature)
}
// 开始按钮监听事件
startButton.addEventListener('click', evt => {
if (animating) {
stopAnimation()
} else {
startAnimation()
}
})
5. 完整代码
其中libs
文件夹下的包需要更换为自己下载的本地包或者引用在线资源。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>OpenLayers 要素动画</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="../../libs/css/ol9.2.4.css">
<script src="../../js/config.js"></script>
<script src="../../libs/js/ol9.2.4.js"></script>
<style>
* {
padding: 0;
margin: 0;
font-size: 14px;
font-family: '微软雅黑';
}
html,
body {
width: 100%;
height: 100%;
}
#map {
position: absolute;
top: 50px;
bottom: 0;
width: 100%;
}
#top-content {
position: absolute;
width: 100%;
height: 50px;
line-height: 50px;
background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
color: #fff;
text-align: center;
font-size: 32px;
}
#top-content span {
font-size: 32px;
}
.state {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
width: 20%;
height: 30px;
line-height: 30px;
display: flex;
justify-content: center;
justify-items: center;
border-radius: 5px;
background: linear-gradient(135deg, #ff00cc, #ffcc00, #00ffcc, #ff0066);
color: #fff;
}
.state-item {
margin-right: 10px;
text-align: center;
font-size: 16px;
font-weight: bold;
}
.start-move {
padding: 2px 5px;
transition: background-color 10s ease-in-out 10s;
text-align: center;
font-size: 14px;
color: #fff;
background: #377d466e;
border-radius: 5px;
border: 1px solid #50505040;
}
.move-speed {
vertical-align: text-bottom;
}
.start-move:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div id="top-content">
<span>OpenLayers 要素动画</span>
</div>
<div id="map" title="地图显示"></div>
<div class="state">
<div class="state-item">
<label for="">移动速度:</label>
<input class="move-speed" type="range" min="10" max="100" step="10" value="70"></input>
</div>
<div class="state-item">
<span class="start-move">开始移动</span>
</div>
</div>
</body>
</html>
<script>
//地图投影坐标系
const projection = ol.proj.get('EPSG:3857');
//==============================================================================//
//============================天地图服务参数简单介绍==============================//
//================================vec:矢量图层==================================//
//================================img:影像图层==================================//
//================================cva:注记图层==================================//
//======================其中:_c表示经纬度投影,_w表示球面墨卡托投影================//
//==============================================================================//
const TDTImgLayer = new ol.layer.Tile({
title: "天地图影像图层",
source: new ol.source.XYZ({
url: "http://t0.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=" + TDTTOKEN,
attibutions: "天地图影像图层",
crossOrigin: "anoymous",
wrapX: false
})
})
const TDTImgCvaLayer = new ol.layer.Tile({
title: "天地图矢量注记图层",
source: new ol.source.XYZ({
url: "http://t0.tianditu.com/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=" + TDTTOKEN,
attibutions: "天地图矢量注记",
crossOrigin: "anoymous",
wrapX: false
})
})
const map = new ol.Map({
target: "map",
loadTilesWhileInteracting: true,
view: new ol.View({
center: [102.65088511565695, 24.995626734327274],
zoom: 13.5,
worldsWrap: false,
minZoom: 1,
maxZoom: 20,
projection: 'EPSG:4326',
}),
layers: [TDTImgLayer, TDTImgCvaLayer],
// 地图默认控件
controls: ol.control.defaults.defaults({
zoom: true,
attribution: true,
rotate: true
})
})
// 获取路线JSON数据
fetch("../../data/geojson/route.json").then(response => {
response.json().then(res => {
// 路线数据
const route = new ol.format.GeoJSON().readGeometry(res.geometry)
// 创建Feature对象
const routeFeature = new ol.Feature({
geometry: route,
type: "route"
})
//起始标注
const startMarker = new ol.Feature({
type: "icon",
geometry: new ol.geom.Point(route.getFirstCoordinate())
})
//结束标注
const endMarker = new ol.Feature({
type: "icon",
geometry: new ol.geom.Point(route.getLastCoordinate())
})
//复制起始坐标
const position = startMarker.getGeometry().clone()
const geoMarker = new ol.Feature({
type: "geoMarker",
geometry: position
})
const styles = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
color: [237, 212, 0, 0.8],
width: 6
})
}),
"icon": new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: "../../image/icon.png"
})
}),
"geoMarker": new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: "black"
}),
stroke: new ol.style.Stroke({
color: "white",
width: 2
})
})
})
}
// 创建矢量图层
const vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
features: [routeFeature, geoMarker, startMarker, endMarker]
}),
style: (feature) => {
return styles[feature.get("type")]
}
})
map.addLayer(vectorLayer)
const speedInput = document.querySelector(".move-speed")
const startButton = document.querySelector(".start-move")
let animating = false
let distance = 0
let lastTime = 0
// 监听移动事件
function moveFeature(event) {
const speed = Number(speedInput.value)
// 获取当前帧的时间
const time = event.frameState.time
// 计算自上次渲染以来经过的时间
const elapsedTime = time - lastTime
// 根据速度和经过的时间更新距离,并取模2,使距离在0-2之间循环
distance = (distance + (speed * elapsedTime) / 1e6) % 2
// 更新上次渲染时间
lastTime = time
// 根据当前的距离计算出对应的坐标,当距离大于1时,使用2减去距离,以实现来回移动的效果
const currentCoordinate = route.getCoordinateAt(
distance > 1 ? 2 - distance : distance
)
// 更新位置对象的坐标
position.setCoordinates(currentCoordinate)
// 获取向量上下文对象,用于绘制图形
const vectorContext = ol.render.getVectorContext(event)
// 设置绘制样式
vectorContext.setStyle(styles.geoMarker)
// 绘制位置对象
vectorContext.drawGeometry(position)
// 继续渲染动画,使动画持续进行
map.render()
}
// 开始动画函数
function startAnimation() {
animating = true
lastTime = Date.now()
startButton.textContent = "停止动画"
// 添加要素移动监听事件
vectorLayer.on("postrender", moveFeature)
// 移除标注
geoMarker.setGeometry(null)
}
// 结束动画函数
function stopAnimation() {
animating = false
startButton.textContent = "开始动画"
// 保持标注在动画停止位置
geoMarker.setGeometry(position)
// 移除要素移动监听事件
vectorLayer.un("postrender", moveFeature)
}
// 开始按钮监听事件
startButton.addEventListener('click', evt => {
if (animating) {
stopAnimation()
} else {
startAnimation()
}
})
})
})
</script>
❝
OpenLayers示例数据下载,请在公众号后台回复:ol数据
全国信息化工程师-GIS 应用水平考试资料,请在公众号后台回复:GIS考试
❝
GIS之路公众号已经接入了智能助手,欢迎大家前来提问。
欢迎访问我的博客网站-长谈GIS:
http://shanhaitalk.com
都看到这了,不要忘记点赞、收藏+关注 哦!
本号不定时更新有关 GIS开发 相关内容,欢迎关注