OpenLayers 与 ECharts 结合
本章将介绍如何在 OpenLayers 地图中集成 ECharts 图表,实现地图与图表的联动展示。
示例
代码实现
本示例展示了如何在 OpenLayers 地图上添加标记点,并在点击标记时弹出 ECharts 饼图展示该区域的人口年龄分布数据。主要实现步骤如下:
- 引入所需依赖
vue
<template>
<div class="h-screen">
<div id="map" class="w-full h-full"></div>
<div id="popup" class="ol-popup">
<a href="#" id="popup-closer" class="ol-popup-closer" @click="closePopup"
>✖</a
>
<div id="popup-content" class="chart-popup"></div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
import "ol/ol.css";
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
import Feature from "ol/Feature";
import Point from "ol/geom/Point";
import { Style, Circle, Fill, Stroke, Text } from "ol/style";
import Overlay from "ol/Overlay";
import { fromLonLat } from "ol/proj";
import * as echarts from "echarts";
let map;
let overlay;
let chart;
// 模拟数据
const data = [
{
name: "和平区",
location: fromLonLat([117.215, 39.117]),
population: [
{ value: 235, name: "0-18岁" },
{ value: 274, name: "19-35岁" },
{ value: 310, name: "36-60岁" },
{ value: 335, name: "60岁以上" },
],
},
{
name: "河西区",
location: fromLonLat([117.223, 39.109]),
population: [
{ value: 154, name: "0-18岁" },
{ value: 285, name: "19-35岁" },
{ value: 246, name: "36-60岁" },
{ value: 178, name: "60岁以上" },
],
},
{
name: "南开区",
location: fromLonLat([117.15, 39.138]),
population: [
{ value: 187, name: "0-18岁" },
{ value: 356, name: "19-35岁" },
{ value: 298, name: "36-60岁" },
{ value: 265, name: "60岁以上" },
],
},
];
onMounted(() => {
// 初始化地图
map = new Map({
target: "map",
view: new View({
center: fromLonLat([117.18704223632814, 39.11967296689395]),
zoom: 12,
}),
});
// 添加天地图图层
const tdtLayer = new TileLayer({
source: new XYZ({
url: "http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=d32c6748c80f81a44acd8633cea41dfd",
}),
});
map.addLayer(tdtLayer);
// 创建标记图层
const vectorSource = new VectorSource();
const vectorLayer = new VectorLayer({
source: vectorSource,
});
map.addLayer(vectorLayer);
// 添加标记点
data.forEach((item) => {
const feature = new Feature({
geometry: new Point(item.location),
name: item.name,
population: item.population,
});
feature.setStyle(
new Style({
image: new Circle({
radius: 16,
fill: new Fill({
color: "#2196f3",
}),
stroke: new Stroke({
color: "#fff",
width: 2,
}),
}),
text: new Text({
text: "📊",
fill: new Fill({
color: "#fff",
}),
font: "16px sans-serif",
}),
})
);
vectorSource.addFeature(feature);
});
// 创建弹窗
const container = document.getElementById("popup");
const content = document.getElementById("popup-content");
overlay = new Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 250,
},
});
map.addOverlay(overlay);
// 点击事件
map.on("click", (evt) => {
const feature = map.forEachFeatureAtPixel(evt.pixel, (feature) => feature);
if (feature) {
const coordinates = feature.getGeometry().getCoordinates();
const name = feature.get("name");
const population = feature.get("population");
overlay.setPosition(coordinates);
chart = echarts.init(content);
const option = {
title: {
text: `${name}人口年龄分布`,
left: "center",
top: 10,
textStyle: {
fontSize: 14,
},
},
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b}: {c} ({d}%)",
},
legend: {
orient: "vertical",
left: 10,
top: "center",
},
series: [
{
name: "年龄分布",
type: "pie",
radius: ["40%", "70%"],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: "#fff",
borderWidth: 2,
},
label: {
show: false,
position: "center",
},
emphasis: {
label: {
show: true,
fontSize: "12",
fontWeight: "bold",
},
},
labelLine: {
show: false,
},
data: population,
},
],
};
chart.setOption(option);
}
});
// 鼠标样式
map.on("pointermove", (e) => {
const pixel = map.getEventPixel(e.originalEvent);
const hit = map.hasFeatureAtPixel(pixel);
map.getTargetElement().style.cursor = hit ? "pointer" : "";
});
});
// 关闭弹窗
const closePopup = () => {
overlay.setPosition(undefined);
if (chart) {
chart.dispose();
}
return false;
};
</script>
<style scoped>
.h-screen {
height: 24rem;
}
.w-full {
width: 100%;
}
.h-full {
height: 100%;
}
.chart-popup {
width: 300px;
height: 300px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
padding: 10px;
}
.ol-popup {
position: absolute;
background-color: white;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -50px;
min-width: 320px;
}
.ol-popup:after,
.ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 48px;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 48px;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
color: #666;
}
</style>