POI跳转设计与实现
POI列表点击跳转几种交互形式:
A:点击后直接飞过去跳转缩放,这样的效果缺点是在多个POI中跳转频繁时看着比较眼花缭乱;(eg.小红书地图)
B:点击后放大POI,假如点位超出范围,则地图平移到以该点位为中心的位置。(eg.高德地图)
C:点击POI后弹窗,锁定地图范围并去除其他POI(eg.其他应用)
方式一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MapZoomToPOI</title>
<script src="https://unpkg.com/maptalks@0.47.5/dist/maptalks.min.js"></script>
<style>
#map {
width: 100%;
height: 500px;
}
</style>
</head>
<body>
<div id="map"></div>
<button id="zoomButton">Zoom to POI</button>
<script>
var poi = [-74.08087539941407, 40.636167734187026]; // POI的位置
// 创建maptalks地图对象
var map = new maptalks.Map('map', {
center: [-74.08087539941407, 40.636167734187026], // 初始地图中心
zoom: 10, // 初始缩放级别
pitch: 0, // 初始俯仰角
bearing: 0, // 初始航向角
baseLayer: new maptalks.TileLayer('base', {
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
});
var layer = new maptalks.VectorLayer('v').addTo(map);
layer.addTo(map);
var marker = new maptalks.Marker(poi, {
symbol: {
'markerType': 'ellipse', // marker形状
'markerFill': '#FF5733', // marker填充颜色
'markerWidth': 20, // marker宽度
'markerHeight': 20, // marker高度
}
}).addTo(layer);
// 点击按钮时触发的缩放到POI的动画
document.getElementById('zoomButton').addEventListener('click', function() {
map.animateTo({
center: poi, // POI位置
zoom: 13, // 缩放级别
pitch: 0, // 俯仰角
bearing: 20 // 航向角
}, {
duration: 2000 // 动画持续时间,单位毫秒
});
});
</script>
</body>
</html>
方式二
这里用到turf工具:https://fenxianglu.cn/turfjs/category/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Map with POI</title>
<link rel="stylesheet" href="https://unpkg.com/maptalks/dist/maptalks.css">
<script src="https://unpkg.com/maptalks/dist/maptalks.min.js"></script>
<script src="https://unpkg.com/@turf/turf@6/turf.min.js"></script> <!-- 引入 turf -->
<style>
body, html {
margin: 0;
height: 100%;
width: 100%;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
}
#map {
width: 100%;
height: 80%;
}
.poi-list {
background: #f0f0f0;
padding: 10px;
height: 20%;
overflow-y: scroll;
}
.poi-list ul {
list-style-type: none;
padding: 0;
}
.poi-list li {
padding: 8px;
cursor: pointer;
}
.poi-list li:hover {
background-color: #ddd;
}
</style>
</head>
<body>
<div class="container">
<div id="map"></div>
<div class="poi-list">
<ul>
<li onclick="onPoiClick({ name: 'POI 1', coordinates: [-0.131049, 51.498568] })">POI 1</li>
<li onclick="onPoiClick({ name: 'POI 2', coordinates: [-0.107049, 51.498568] })">POI 2</li>
<li onclick="onPoiClick({ name: 'POI 3', coordinates: [-0.107049, 51.491568] })">POI 3</li>
</ul>
</div>
</div>
<script>
let map;
let poiLayer; // 新增一个Layer来管理POI标记
let poiMarkers = [];
let pickedPointSymbol = null;
let lastPickedMarker = null; // 存储上一个选中的 marker
let lastPickedMarkerAni= null; // 存储上一个选中的 marker的动画对象
function initializeMap() {
map = new maptalks.Map('map', {
center: [-0.113049, 51.498568], // 初始中心
zoom: 14,
baseLayer: new maptalks.TileLayer('base', {
urlTemplate: 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c', 'd'],
attribution: '© <a href="http://osm.org">OpenStreetMap</a> contributors, © <a href="https://carto.com/">CARTO</a>'
})
});
// 创建一个新的 VectorLayer 来管理 POI 标记
poiLayer = new maptalks.VectorLayer('poiLayer').addTo(map);
const pois = [
{ name: 'POI 1', coordinates: [-0.131049, 51.498568] },
{ name: 'POI 2', coordinates: [-0.107049, 51.498568] },
{ name: 'POI 3', coordinates: [-0.107049, 51.491568] },
];
pois.forEach(poi => {
const marker = new maptalks.Marker(poi.coordinates, {
symbol: {
'markerType': 'ellipse',
'markerWidth': 20,
'markerHeight': 20,
'markerFill': '#FF4500',
'markerOpacity': 0.7,
}
});
// 将标记添加到 POI 图层
poiLayer.addGeometry(marker);
poiMarkers.push({ poi, marker });
// 添加点击事件监听器
marker.on('click', function(e) {
onMarkerClick(e.target);
});
});
}
// POI 列表点击事件
function onPoiClick(poi) {
const markerData = poiMarkers.find(item => item.poi.name === poi.name);
if (markerData) {
onMarkerClick(markerData.marker); // 触发对应的 Marker 点击事件
}
}
// POI 点击事件处理
function onMarkerClick(marker) {
// 凸显该POI
highlightPoi(marker);
// 判断POI是否在视野范围内
if (!isPoiInView(marker)) {
// 如果不在视野范围内,平移地图并聚焦该POI
centerMapOnPoi(marker);
}
}
// 判断POI是否在地图的视野范围内
function isPoiInView(marker) {
const extent = map.getExtent(); // 获取地图的视野范围
console.log('Map extent:', extent);
const { xmax, ymax, xmin, ymin } = extent; // (xmin, ymin) 为左下角坐标,(xmax, ymax) 为右上角坐标
// 使用 turf 判断POI是否在视野范围内
const { x, y } = marker.getCoordinates();
const point = turf.point([x, y]);
const polygonCoordinates = [
[
[xmin, ymin],
[xmax, ymin],
[xmax, ymax],
[xmin, ymax],
[xmin, ymin],
],
];
const searchWithin = turf.polygon(polygonCoordinates);
// 使用 booleanPointInPolygon 判断点是否在多边形内
const isInPolygon = turf.booleanPointInPolygon(point, searchWithin);
return isInPolygon;
}
// 凸显POI的标记,并使用动画
function highlightPoi(marker) {
// 如果之前有凸显的POI标记,恢复它的原始尺寸
if (lastPickedMarker) {
lastPickedMarkerAni.finish();//注意 用finish而非cancel
lastPickedMarker.updateSymbol(pickedPointSymbol);
}
// 获取当前标记的原始尺寸
pickedPointSymbol = marker.getSymbol();
// 设置当前标记为凸显状态(仅修改 markerWidth 和 markerHeight),并使用动画
var Ani=marker.animate({
symbol: {
markerWidth: 30,
markerHeight: 30
}
}, {
duration: 100
});
// 保存当前选中的 marker和动画对象
lastPickedMarker = marker;
lastPickedMarkerAni = Ani;
}
// 平移地图并聚焦POI
function centerMapOnPoi(marker) {
const center = new maptalks.Coordinate(marker.getCoordinates());
console.log(`Panning to POI at ${marker.getCoordinates()}`);
map.panTo(center); // 只平移不缩放
}
// 初始化地图
initializeMap();
</script>
</body>
</html>
方式三
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MapZoomToPOI</title>
<script src="https://unpkg.com/maptalks@0.47.5/dist/maptalks.min.js"></script>
<style>
#map {
width: 100%;
height: 500px;
}
</style>
</head>
<body>
<div id="map"></div>
<button id="zoomButton">Zoom to POI</button>
<script>
var poi = [-74.08087539941407, 40.636167734187026]; // POI的位置
var nearbyPoi1 = [-74.079, 40.637]; // 第一个附近点
var nearbyPoi2 = [-74.082, 40.635]; // 第二个附近点
// 创建maptalks地图对象
var map = new maptalks.Map('map', {
center: poi, // 初始地图中心
zoom: 10, // 初始缩放级别
pitch: 0, // 初始俯仰角
bearing: 0, // 初始航向角
baseLayer: new maptalks.TileLayer('base', {
urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
})
});
var layer = new maptalks.VectorLayer('v').addTo(map);
// 创建POI标记
var markerPoi = new maptalks.Marker(poi, {
symbol: {
'markerType': 'ellipse', // marker形状
'markerFill': '#FF5733', // marker填充颜色
'markerWidth': 20, // marker宽度
'markerHeight': 20, // marker高度
},
properties: {
title: "POI地点", // 标题
dis: 1.2, // 距离
img: "<img src='https://via.placeholder.com/100' />", // 图片
star: 4.5 // 评分
}
}).addTo(layer);
// 创建附近点的标记
var markerNearby1 = new maptalks.Marker(nearbyPoi1, {
symbol: {
'markerType': 'ellipse',
'markerFill': '#33FF57', // 不同的颜色
'markerWidth': 20,
'markerHeight': 20,
}
}).addTo(layer);
var markerNearby2 = new maptalks.Marker(nearbyPoi2, {
symbol: {
'markerType': 'ellipse',
'markerFill': '#3357FF', // 不同的颜色
'markerWidth': 20,
'markerHeight': 20,
}
}).addTo(layer);
// 设置信息框
markerPoi.setInfoWindow({
title: markerPoi.properties.title,
content: '<br style="color:#f00">距离你 ' + markerPoi.properties.dis + ' km ' + markerPoi.properties.img + '</br>评分: ' + markerPoi.properties.star
});
// 鼠标交互事件监听
markerPoi
.on("mouseenter", function (e) {
e.target.updateSymbol({
markerFill: "#f00", // 鼠标进入时改变标记颜色
});
})
.on("mouseout", function (e) {
e.target.updateSymbol({
markerFill: "#FF5733", // 鼠标离开时恢复标记颜色
});
})
.on("click", function (e) {
// 打开信息窗口
e.target.openInfoWindow(e.coordinate);
});
// 点击按钮时触发的缩放到POI的动画
document.getElementById('zoomButton').addEventListener('click', function() {
// 隐藏其他两个标记
markerNearby1.remove();
markerNearby2.remove();
// 执行地图动画,缩放到POI位置
map.animateTo({
center: poi, // POI位置
zoom: 13, // 缩放级别
pitch: 0, // 俯仰角
bearing: 20 // 航向角
}, {
duration: 1000 // 动画持续时间,单位毫秒
});
});
// 在动画完成后禁用交互功能
map.on('animateend', function () {
// 禁用交互功能
map.disableZoom(); // 禁用缩放
map.disableDrag(); // 禁用拖动
map.disablePitch(); // 禁用俯仰角
map.disableRotation(); // 禁用旋转
});
</script>
</body>
</html>
其中值得一提的是,如果不使用disable方法,而是用修改map参数的方法,则需要考虑到移动端和web端有两套参数需要同步修改,需要注意。