Skip to content

OpenLayers 与 ECharts 结合

上次更新 2025年8月20日星期三 3:16:20 字数 0 字 时长 0 分钟

本章将介绍如何在 OpenLayers 地图中集成 ECharts 图表,实现地图与图表的联动展示。

示例

新标签页预览

代码实现

本示例展示了如何在 OpenLayers 地图上添加标记点,并在点击标记时弹出 ECharts 饼图展示该区域的人口年龄分布数据。主要实现步骤如下:

  1. 引入所需依赖
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>
关注公众号