Openlayers 气泡框功能,主要用来展示地图要素的属性信息,也被称为气泡、卡片、弹出框等等,笔者习惯称为气泡框。
气泡框功能很常用,场景一般是,在地图叠加点线面的时候同时展示当前要素(Feature)的基础信息,比如名称、数量、地址等;还有就是点击地图叠加元素的时候再弹出气泡框,来展示信息。
气泡框因容器大小限制,一般只展示简略信息,想展示详细信息的话,往往通过新开页面或者弹出大的 div 容器展示。
一般来说,气泡框属于地图覆盖物(Overlay),属于 dom 元素,没有在 canvas 内部,层级位于图层之上,作为顶层元素存在;气泡框一般都会包含关闭图标或按钮,可以点击关闭,也有特殊场景,不允许关闭,不过如果不关闭的话,直接使用 dom 元素实现就可以。
气泡框作为地图对象元素加载的好处之一,就是可以跟随地图移动,效果更加符合使用习惯。
本文主要介绍,气泡框的初始化、气泡框展示信息、气泡框样式、气泡框定位。
<html lang="en">
<head>
<meta charSet="utf-8">
<!--注意:openlayers 原版的比较慢,这里引起自己服务器版-->
<link rel="stylesheet" href="http://openlayers.vip/examples/css/ol.css" type="text/css">
<style>
/* 注意:这里必须给高度,否则地图初始化之后不显示;一般是计算得到高度,然后才初始化地图 */
.map {
height: 700px;
width: 100%;
float: left;
}
/*气泡框容器样式*/
.leaflet-container {
position: absolute;
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
-webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
bottom: -12px;
}
/*气泡框关闭按钮样式*/
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
width: 18px;
height: 14px;
background: 0;
color: #ffffff;
text-align: center;
text-decoration: none;
font: 1pc/14px Tahoma, Verdana, sans-serif;
font-weight: 700
}
/*气泡框内容样式*/
.common-popup .leaflet-popup-content-wrapper {
background: transparent;
box-shadow: none;
padding: 0;
border-radius: 0;
}
/*气泡框下角*/
.common-popup .leaflet-popup-tip {
margin-left: 120px
}
.leaflet-popup-tip-container {
position: relative;
overflow: hidden;
margin: 0;
height: 28px
}
.leaflet-popup-tip {
margin: -10px auto 0;
padding: 1px;
width: 17px;
height: 18px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
-ms-transform: rotate(45deg)
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
background: #fff;
box-shadow: 0 3px 14px rgba(0, 0, 0, .4)
}
/*自定义气泡框样式*/
.customer-popup-southejor-style {
border: 1px solid #fff;
border-radius: 5px;
background: #1a90c587;
color: #fff;
box-shadow: inset 0 0 4px 2px #fff;
}
/*自定义气泡框标题样式*/
.customer-popup-southejor-title {
height: 10%;
background-color: #03a9f48f;
padding: 2px;
font-size: 20px;
text-align: center;
}
</style>
<!--注意:openlayers 原版的比较慢,这里引起自己服务器版-->
<script src="http://openlayers.vip/examples/resources/ol.js"></script>
<script src="http://openlayers.vip/examples/resources/jquery-3.5.1.min.js"></script>
<script src="./tiandituLayers.js"></script>
<title>OpenLayers example</title>
</head>
<body>
<h2>OpenLayers Popup</h2>
<!--地图容器,需要指定 id -->
<div id="map" class="map"></div>
<script type="text/javascript">
var map = new ol.Map({
// 地图容器
target: 'map',
// 地图图层,比如底图、矢量图等
layers: [
getIMG_CLayer(),
getIBO_CLayer(),
getCIA_CLayer(),
],
// 地图视野
view: new ol.View({
projection: "EPSG:4326",
// 定位
center: [115.67724700667199, 37.73879478106912],
// 缩放
zoom: 6,
maxZoom: 18,
minZoom: 1,
})
});
// 开启图形要素点击事件
map.on('click', function (event) {
this.forEachFeatureAtPixel(event.pixel, function (feature) {
// 为点击的feature发送自定义的click消息
feature.dispatchEvent && feature.dispatchEvent({type: 'click', event: event});
});
});
// 初始化气泡框
createPopup();
// 默认样式
var defaultStyle = new ol.style.Style({
//边框样式
stroke: new ol.style.Stroke({
color: 'white',
width: 2,
}),
//填充样式
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.7)',
}),
// 点样式
image: new ol.style.Icon({
// 允许跨域,如果不设置,打印地图不会打印
crossOrigin: 'anonymous',
// 标注图片和文字之间的距离
anchor: [0.5, 0],
// 图片的偏移
offset: [0, 0],
// 图片的锚点,一般来说,都是右下角
anchorOrigin: 'bottom-right',
//图标的url
src: "http://api.tianditu.gov.cn/v4.0/image/marker-icon.png",
scale: 1,
})
})
// 初始化图层
var layer = initVectorLayer();
// 面和点
var feature = undefined, featurePoint = undefined;
addFeatures();
// 添加点线面
function addFeatures() {
featurePoint = getFeatureByWKT("POINT(116.17983834030585 39.98298600752048)");
feature = getFeatureByWKT("POLYGON((115.13540462521966 37.877850766866445,116.31094173459466 39.042401548116445,117.23379329709466 38.130536313741445,117.10195735959466 37.295575376241445,115.64077571896966 36.976971860616445,115.26724056271966 37.174725766866445,115.13540462521966 37.877850766866445))");
//设置显示属性
feature.set('content:内容', '这是一个面状图形要素');
feature.set('other:其他属性', '还有其他面状属性');
featurePoint.set('content:内容', '这是一个点图形要素');
featurePoint.set('other:其他属性', '还有其他点属性');
//定义绑定事件
let clickFunc = function (e) {
// 获取要展示的属性信息
let popupStr = getPopupHtml(e.target);
//获取地图对象
let temp = '<div class="customer-popup-southejor-style">' +
' <div class="customer-popup-southejor-title">\n' +
' <span>气泡框</span>\n' +
' </div>\n' +
' <div style="font-size: 16px; padding: 5px;">' +
' <div class="customer-popup-southejor-index" style="padding-left: 10px;">' +
popupStr.content +
' </div>' +
' </div>\n' +
' </div>'
//打开气泡框
openInfoWindowHtml(e.target, temp)
}
//绑定事件
feature.on('click', clickFunc);
featurePoint.on('click', clickFunc);
layer.getSource().addFeatures([feature, featurePoint]);
}
/**
* @todo 矢量图层
* @returns {VectorLayer}
* @constructor
*/
function initVectorLayer() {
//实例化一个矢量图层Vector作为绘制层
let source = new ol.source.Vector();
//创建一个图层
let customVectorLayer = new ol.layer.Vector({
source: source,
zIndex: 2,
//设置样式
style: defaultStyle,
});
//将绘制层添加到地图容器中
map.addLayer(customVectorLayer);
return customVectorLayer;
}
/**
* @todo wkt格式数据转化成图形对象
* @param {string} wkt "POINT(112.7197265625,39.18164062499999)" 格式数据
* @param {string|Projection} sourceCode 源投影坐标系
* @param {string|Projection} targetCode 目标投影坐标系
* @returns {Feature}
*/
function getFeatureByWKT(wkt, sourceCode, targetCode) {
try {
let view = map.getView();
if (!wkt) {
return null;
}
let format = new ol.format.WKT();
let feature;
feature = format.readFeature(wkt, {
featureProjection: targetCode || view.getProjection(),
dataProjection: sourceCode || view.getProjection(),
});
return feature;
} catch (e) {
console.log(e);
return null;
}
}
/**
* todo 创建 popup 气泡框元素
* @param
*/
function createPopup() {
// 气泡框容器字符串
var popupStr = "<div id='popup-leaflet-container-southejor' class=\"leaflet-container\" > \n" +
" <div class=\"leaflet-popup-pane\">\n" +
" <div class=\"leaflet-popup common-popup\">\n" +
" <a id='popup-closer' class=\"leaflet-popup-close-button\" href=\"JavaScript:void(0)\">×</a>\n" +
" <div class=\"leaflet-popup-content-wrapper\">\n" +
" </div>\n" +
" <div class=\"leaflet-popup-tip-container\">\n" +
" <div class=\"leaflet-popup-tip\"></div>\n" +
" </div>\n" +
" </div>\n" +
" </div>\n" +
" </div>";
// 创建气泡框容器
let popupDiv = document.createElement("div"); //创建一个div标签
// 设置 id
popupDiv.id = "mapPopup";
// 初始化不显示
popupDiv.style.display = 'none';
// 初始化容器
popupDiv.innerHTML = popupStr;
//限制页面只能出现一个气泡框
if (document.getElementsByTagName('body')[0].contains(popupDiv)) {
document.getElementsByTagName('body')[0].removeChild(popupDiv);
}
document.getElementsByTagName('body')[0].appendChild(popupDiv);
return initPopup(popupDiv);
}
// 气泡框,主体,关闭按钮
var popup, contentBody, closerPopup;
/**
* todo 初始化气泡框
* @returns {Overlay}
*/
function initPopup(popupContainer) {
//气泡框-内容ID
contentBody = $('#popup-leaflet-container-southejor .leaflet-popup-content-wrapper')[0];
//气泡框-关闭ID
closerPopup = document.getElementById('popup-closer');
// 设置为显示
popupContainer.style.display = 'block';
//气泡框覆盖层
popup = new ol.Overlay({
// 容器
element: popupContainer,
// 当Popup超出地图边界时,为了Popup全部可见,自动移动到可视范围
autoPan: true,
autoPanAnimation: {
//当Popup超出地图边界时,为了Popup全部可见,地图移动的速度.
duration: 250
}
});
return popup;
}
function openInfoWindowHtml(featureTemp, content) {
try {
// 如果没有图形要素参数,则打开默认气泡框
if (!featureTemp) {
contentBody.innerHTML.trim() ? map.addOverlay(popup) : alert('请先点击图形要素(面或点)!');
return;
}
// 更新显示属性
contentBody.innerHTML = content;
// 获取图形要素中心点
var center = ol.extent.getCenter(featureTemp.getGeometry().getExtent());
// 气泡框设置中心点
popup.setPosition(center);
// 移除上一个气泡框
map.removeOverlay(popup);
// 获取图形要素类型
let featureType = featureTemp.getGeometry().getType();
//这 里面和线图形与点图形不一样;如果是点图层(图标),则气泡框上移
// 气泡框的 setOffset 可以根据实际调整
if (featureType == 'Point') {
let styleTemp = featureTemp.getStyle() || layer.getStyle();
if (styleTemp && styleTemp.getImage()) {
let size = styleTemp.getImage().getSize();
let scale = styleTemp.getImage().getScale();
if (size && size.length > 1) {
popup.setOffset([-10, -size[1] * scale]);
} else {
popup.setOffset([0, -40]);
}
} else {
popup.setOffset([0, -5]);
}
//其他图形则正常
} else {
popup.setOffset([-10, 0]);
}
// 添加气泡框
map.addOverlay(popup);
// 绑定关闭按钮事件
closerPopup.onclick = function () {
map.removeOverlay(popup);
};
// 气泡框宽度
let popupWidth = 300;
// 调整位置
let popupPointLeft = (popupWidth / 2);
// 调整横向偏移
$(map.getTargetElement()).find(".leaflet-container").width(popupWidth + "px");
$(map.getTargetElement()).find(".leaflet-container").css('right', -popupPointLeft + "px");
$(map.getTargetElement()).find(".leaflet-popup-tip-container .leaflet-popup-tip").css("margin-left", popupPointLeft + "px");
return popup;
} catch (e) {
console.log(e)
}
}
// 获取气泡框属性
function getPopupHtml(feature) {
if (!feature) {
return;
}
let popup = {};
// 定义 table 容器
let content = "<table class='popup-table-html-show-southejor' style='width: 100%;color:white'>";
// 获取图形要素所有属性
let featureProperties = feature.getProperties();
// 设置字段和属性宽度
let tdColumn = '35%', tdField = '65%';
for (let featureElement in featureProperties) {
// 这里需要跟前边 feature 属性定义对应;
// 前边定义为:字段:值
if (featureElement && featureElement.indexOf(":") != -1) {
let featureElementArr = featureElement.split(":");
content += "<tr style='height: 25px;'>";
// 拼接属性信息
content += "<td style='border-width: 0px 0px 1px 0px;word-wrap:break-word;" +
"word-break:break-all;width: " + tdColumn + "'><div>" + featureElementArr[1] + " : </div></td>" +
"<td style='border-width: 0px 0px 1px 0px;word-wrap:break-word;word-break:break-all;" +
"width: " + tdField + "'><div class='popupIdentity-" + featureElementArr[0] + "'>" +
featureProperties[featureElement] + "</div></td>";
content += "</tr>";
}
}
popup.content = content += "</table>";
return popup;
}
/**
* todo 定位到气泡框
*/
function moveToPopup() {
// 用于定位的[x,y]
let xy;
// 图形要素的类型;面线和点获取 xy 方式不同
let type = feature.getGeometry().getType();
// 获取面线和点的 xy
if ('Polygon' == type || 'LineString' == type) {
xy = ol.extent.getCenter(feature.getGeometry().getExtent());
} else {
xy = feature.getGeometry().getCoordinates();
}
// 定位位置(y 轴)
let init = 500;
// 获取气泡框高度
let heightTemp = init + $('#popup-leaflet-container-southejor').height() - 40;
heightTemp = heightTemp || 680;
// 计算 y 轴间隔
let gap = map.getCoordinateFromPixel([300, heightTemp])[1] - map.getCoordinateFromPixel([300, init])[1];
xy && xy.length > 1 && (xy[1] = xy[1] - gap);
// 定位;位置,定位时长
let flyTo = function (location, time) {
let duration = time ? time : 2000;
let zoom = map.getView().getZoom();
let parts = 2;
let called = false;
// 定位回调
function callback(complete) {
--parts;
if (called) {
return;
}
if (parts === 0 || !complete) {
called = true;
}
}
// 定位动作
map.getView().animate({
center: location,
duration: duration
}, callback);
// 定位动作
map.getView().animate({
zoom: zoom - 1,
duration: duration / 2
}, {
zoom: zoom,
duration: duration / 2
}, callback);
}
flyTo(xy, 1000);
}
/**
* todo 关闭气泡框
* @param _map
*/
function closeInfoWindowHtml() {
map.removeOverlay(popup);
}
</script>
<button id="openInfoWindowHtml" onClick="openInfoWindowHtml()">打开气泡框</button>
<button id="moveToPopup" onClick="moveToPopup()">定位到气泡框</button>
<button id="closeInfoWindowHtml" onClick="closeInfoWindowHtml()">关闭气泡框</button>
</body>
</html>
Openlayers 自定义气泡框:Openlayers feature popup