vue+openlayers实现行政边界、标注交互、效果弹窗

  • 需求
  • 最终效果
  • 环境安装/依赖引入
  • html部分
  • 逻辑部分
    • 1.创建变量/初始化地图/常量
    • 2.拿到后台数据/通过json加载中国区划
    • 3.添加标注
    • 4.添加弹窗
    • 5.CSS样式
  • 完整代码
  • 附件

需求

  • 1.实现中国行政区划地图展示。
  • 2.地图标注出每个省份的数据结果,hover交互查看详情。
  • 3.参与人数越多的对应省份颜色越深。

最终效果

环境安装/依赖引入

OpenLayers通过NPM 安装

npm install ol -S

引入依赖

import chinaData from '@/assets/chinaBj.json'  //中国区划json文件
import './map.css' //自定义地图样式
import Point from 'ol/geom/Point'
import Polygon from 'ol/geom/Polygon'
import Feature from 'ol/Feature'
import Map from 'ol/Map'
import Overlay from 'ol/Overlay'
import View from 'ol/View'
import { Fill, Stroke, Style, Icon, Text } from 'ol/style'
import { Vector as VectorSource } from 'ol/source'
import { Vector as VectorLayer } from 'ol/layer'

html部分

map为地图实例
popup为自定义弹框

<template><div class="box"><div id="map" class="map"></div><div id="popup" class="ol-popup"><div id="popup-content" class="popup-content"></div></div></div>
</template>

逻辑部分

1.创建变量/初始化地图/常量

//用来存储 后端返回的省市名称
let provinces = []
//通过不同的省市名称 动态生成地图区划颜色
var getColorByDGP = function(adcode) {var r = 3var g = 140var b = 230//let index = provinces.findIndex(d => d.provinceCode === adcode)if (index === -1) {//默认颜色let a = 4 / 10return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'} else {//数量越多 颜色越深let a = provinces[index].resultCount / 5return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'}
}
data() {return {// 地图实例map: null,// 底图行政区划vectorLayer: null,// 弹窗对象overlay: null,// 标记集合sourceArr: null,// 公司数据源companyList: []}},
mounted() {// 创建地图实例this.vectorLayer = new VectorLayer({source: new VectorSource()})var map = new Map({layers: [this.vectorLayer],target: 'map',view: new View({zooms: [4, 5],//缩放层级center: [102.122082, 33.719192], //中心点的经纬度zoom: 4, //初始层级projection: 'EPSG:4326'})})// 地图实例this.map = map// 加载中国区划this.getReportList()// 添加弹窗this.addPopup()},

2.拿到后台数据/通过json加载中国区划

因为要实现根据后端数据来动态显示 地图颜色深浅,所以需要在异步加载完数据后 加载地图 并生成标注

// 后端获取点数据 (地图JSON数据和接口假数据会放在文章最后 以便调试)getReportList() {getAction('/qn_manage/controlCabin/company').then(res => {if (res.success) {this.companyList = res.result.companiesprovinces = res.result.provinces//通过Json加载区划图this.loadFeatures(chinaData)// 生成标注this.groundgreendot(this.companyList)} else {this.$message.error(res.message)}})},

加载地图、名称、配置区划颜色

  // 加载中国地图区划loadFeatures(data) {this.clearLayer(this.vectorLayer)let features = []// 遍历JSONdata.forEach(item => {let feature = new Feature({geometry: new Polygon(item.coordinates),name: item.name,center: item.center,code: item.code,level: item.level})// 配置区划颜色  feature.setStyle(this.polygonStyle(item.code))features.push(feature)// 加载地名和点this.loadPoint(item.center, item.name)})this.vectorLayer.getSource().addFeatures(features)//让地图自适应this.fitLayer(this.vectorLayer)},

动态配置区划要素的颜色

  // 配置样式和颜色polygonStyle(code) {// 根据后端数据生成颜色  数量越多 颜色越来越深const color = getColorByDGP(code)return new Style({stroke: new Stroke({color: `rgba(255,255,255,1)`,width: 1}),fill: new Fill({color: color})})},

地图自适应图层所在元素的范围

fitLayer(layer) {let extent = layer.getSource().getExtent()this.map.getView().fit(extent)},

3.添加标注

生成标注

 // 创建标注 入参为后端的数据groundgreendot(mapListdataS) {this.removeLayerByName('地面显示点')//创建画板if (mapListdataS && mapListdataS[0]) {this.sourceArr = new VectorSource({})const [x1, y1, x2, y2] = this.map.getView().calculateExtent()//过滤后端没有location字段的数据mapListdataS = mapListdataS.filter(d => d.location)for (var i = 0; i <= mapListdataS.length - 1; i++) {//点的坐标信息const lon = mapListdataS[i].location.split(',')[0]const lat = mapListdataS[i].location.split(',')[1]if (lon <= x2 && lon >= x1 && lat <= y2 && lat >= y1) {let coordinates = [lon, lat]//将点的信息存入self中let feature = new Feature({geometry: new Point(coordinates),self: mapListdataS[i]})// 设置点样式let markerStyle = new Style({//设置icon大小image: new Icon({scale: 0.8, //大小src: count //图片})})//设置样式feature.setStyle(markerStyle)//添加this.sourceArr.addFeature(feature)// 给要素添加 悬浮样式修改// this.setIconStyle()}}//LayerVec /VectorLayer  这两种都可以var layer = new VectorLayer({source: this.sourceArr,visible: true, //先测试下,这样就行了,设置true或者false=name: '地面显示点' //设置图层名称,根据名称查找图层})this.map.addLayer(layer)}},

通过名字删除图层

 // 通过名字删除图层removeLayerByName(name) {var layersToRemove = []this.map.getLayers().forEach(function(layer) {if (layer.get('name') != undefined && layer.get('name') === name) {layersToRemove.push(layer)}})var len = layersToRemove.lengthfor (var i = 0; i < len; i++) {this.map.removeLayer(layersToRemove[i])}},

4.添加弹窗

 // 添加弹窗addPopup(evt, evtFeature) {// 使用变量存储弹窗所需的 DOM 对象var container = document.getElementById('popup')var closer = document.getElementById('popup-closer')var content = document.getElementById('popup-content')// 创建一个弹窗 Overlay 对象this.overlay = new Overlay({element: container, //绑定 Overlay 对象和 DOM 对象的autoPan: true, // 定义弹出窗口在边缘点击时候可能不完整 设置自动平移效果autoPanAnimation: {duration: 250 //自动平移效果的动画时间 9毫秒}})// 将弹窗添加到 map 地图中this.map.addOverlay(this.overlay)let _that = this/*** 添加单击响应函数来处理弹窗动作 pointermove*/this.map.on('pointermove', async evt => {//判断鼠标是否悬停在要素上let feature = evtFeature? evtFeature: this.map.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {return feature})// 如果要素 存在且有数据if (feature != undefined && feature.values_.self) {// 改变鼠标样式_that.map.getTargetElement().style.cursor = 'pointer'//拿数据const data = feature.values_.self// 画弹窗样式const contentHtml = `<div class='myContent' ><div class='myContentTitle'>${data.companyName}</div><div class='myContentData'><div class='myContentItem' ><div  class='left'>参与调查人数: </div><div class='right'>${data.personCount}</div><div style='flex:1'>人</div></div><div class='myContentItem' ><div class='left'>累计报告数量: </div><div class='right'> ${data.reportCount}</div><div style='flex:1'>份</div></div></div></div>`content.innerHTML = contentHtmlcontent.style.display = 'block'content.style.left = evt.pixel[0] - 120 + 'px'content.style.top = evt.pixel[1] - 270 + 'px'_that.overlay.setPosition(evt.coordinate)} else {//鼠标没有悬停在要素上_that.map.getTargetElement().style.cursor = 'auto'content.style.display = 'none'_that.overlay.setPosition(undefined)}})}

5.CSS样式

/* 新弹框*/#popup-content {/* position: absolute; */top: 10px;left: 50px;/* background-color: #000000; */border-radius: 6px;z-index: 999999;display: none;border-radius: 4px;color: #000;padding: 10px 10px 10px 20px;width: 100%;
}.ol-popup {position: absolute;/* background-color: white; */-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));border-radius: 10px;bottom: 12px;left: -50px;background: url(../../assets/img/brand/contentBg.png) no-repeat top center;background-size: 100% 100%;border-radius: 5px;width: 4.25rem/* 340px -> 4.25rem */;height: 3.0625rem/* 245px -> 3.0625rem */;
}#popup-content .myContentTitle {background: url(../../assets/img/brand/contentTitle.png) no-repeat top center;background-size: 100% 100%;text-align: center;font-size: 20px;font-weight: bold;color: #e4f2fb;line-height: 87px;height: 87px;
}#popup-content .myContentData {display: flex;flex-direction: column;justify-content: space-around;align-items: center;height: calc(100% - 87px);
}#popup-content .myContentData .myContentItem {flex: 1;width: 100%;padding: 20px;font-size: 14px;font-family: Microsoft YaHei;font-weight: 400;color: #e4f2fb;display: flex;flex-direction: row;justify-content: space-around;line-height: 100%;
}#popup-content .myContentData .myContentItem .left {flex: 1;text-align: right;
}#popup-content .myContentData .myContentItem .right {font-size: 20px;font-family: 'AkzidenzGroteskBE-BoldCn';font-weight: bold;font-style: italic;opacity: 1;color: #e4f2fb;margin-left: 10px;margin-right: 10px;
}/* 下边的倒三角 */.ol-popup:after,
.ol-popup:before {top: 97%;border: solid transparent;content: " ";height: 0;width: 0;position: absolute;pointer-events: none;
}.ol-popup:after {border-top-color: #1d2e4c;border-width: 10px;left: 48px;margin-left: -10px;
}.ol-popup:before {border-top-color: #1d2e4c;border-width: 11px;left: 48px;margin-left: -11px;
}.popup-content {width: 400px;
}/* 关闭左上角放大缩小 */.ol-zoom {display: none;
}

完整代码

<!--* @Author: yangxiunan* @Date: 2020-10-20 17:06:42* @LastEditTime: 2020-10-27 10:20:26* @LastEditors: Please set LastEditors* @Description: In User Settings Edit* @FilePath: \cesium-city3dd:\myCode\ol6\src\components\loadJson.vue
-->
<template><div class="box"><div id="map" class="map"></div><div id="popup" class="ol-popup"><div id="popup-content" class="popup-content"></div></div></div>
</template>
<script>
import chinaData from "./chinaBj.json";
import "./map.css";
import Point from "ol/geom/Point";
import Polygon from "ol/geom/Polygon";
import Feature from "ol/Feature";
import Map from "ol/Map";
import Overlay from "ol/Overlay";
import View from "ol/View";
import { Fill, Stroke, Style, Icon, Text } from "ol/style";
import { Vector as VectorSource } from "ol/source";
import { Vector as VectorLayer } from "ol/layer";
import { getAction } from "@/api/manage";
import count from "./position.png";
// 标注触发Hover 时改变样式
// import Select from 'ol/interaction/Select'
// import { pointerMove } from 'ol/events/condition'
// import active from '@/assets/img/brand/selectPosition.png'
let provinces = [];
var getColorByDGP = function (adcode) {var r = 3;var g = 140;var b = 230;//let index = provinces.findIndex((d) => d.provinceCode === adcode);if (index === -1) {let a = 4 / 10;return "rgba(" + r + "," + g + "," + b + "," + a + ")";} else {let a = provinces[index].resultCount / 5;return "rgba(" + r + "," + g + "," + b + "," + a + ")";}
};
export default {name: "myMap",data() {return {// 地图实例map: null,// 底图行政区划vectorLayer: null,// 弹窗对象overlay: null,// 标记集合sourceArr: null,// 公司数据源companyList: [],};},mounted() {// 创建地图实例this.vectorLayer = new VectorLayer({source: new VectorSource(),});var map = new Map({layers: [this.vectorLayer],target: "map",view: new View({zooms: [4, 5],center: [102.122082, 33.719192],zoom: 4,projection: "EPSG:4326",}),});this.map = map;// 加载中国区划this.getReportList();// 添加弹窗this.addPopup();},methods: {// 后端获取点数据getReportList() {getAction("/qn_manage/controlCabin/company").then((res) => {if (res.success) {this.companyList = res.result.companies;provinces = res.result.provinces;//加载底图this.loadFeatures(chinaData);// 打点this.groundgreendot(this.companyList);} else {this.$message.error(res.message);}});},// 加载底图loadFeatures(data) {this.clearLayer(this.vectorLayer);let features = [];// 遍历JSONdata.forEach((item) => {let feature = new Feature({geometry: new Polygon(item.coordinates),name: item.name,center: item.center,code: item.code,level: item.level,});// 设置地图 颜色feature.setStyle(this.polygonStyle(item.code));features.push(feature);// 加载地名和点this.loadPoint(item.center, item.name);});this.vectorLayer.getSource().addFeatures(features);this.fitLayer(this.vectorLayer);},// 创建要素groundgreendot(mapListdataS) {this.removeLayerByName("地面显示点");//创建画板if (mapListdataS && mapListdataS[0]) {this.sourceArr = new VectorSource({});const [x1, y1, x2, y2] = this.map.getView().calculateExtent();mapListdataS = mapListdataS.filter((d) => d.location);for (var i = 0; i <= mapListdataS.length - 1; i++) {//点的坐标信息const lon = mapListdataS[i].location.split(",")[0];const lat = mapListdataS[i].location.split(",")[1];if (lon <= x2 && lon >= x1 && lat <= y2 && lat >= y1) {let coordinates = [lon, lat];//将点的信息存入self中let feature = new Feature({geometry: new Point(coordinates),self: mapListdataS[i],});// 设置点样式let markerStyle = new Style({//设置icon大小image: new Icon({scale: 0.8, //大小src: count, //图片}),});//设置样式feature.setStyle(markerStyle);//添加this.sourceArr.addFeature(feature);// 给要素添加 悬浮样式修改// this.setIconStyle()}}//LayerVec /VectorLayer  这两种都可以var layer = new VectorLayer({source: this.sourceArr,visible: true, //先测试下,这样就行了,设置true或者false=name: "地面显示点", //设置图层名称,根据名称查找图层});this.map.addLayer(layer);}},// 加载地名和点loadPoint(point, text, code) {let feature = new Feature({geometry: new Point(point),});feature.setStyle(() => {return new Style({text: new Text({text: text,stroke: new Stroke({color: "rgba(29,233,182,0)",}),fill: new Fill({color: "rgba(255,255,255,1)",}),textAlign: "center",// textBaseline: 'bottom',}),});});this.vectorLayer.getSource().addFeature(feature);},// 清除图层clearLayer(layer) {layer.getSource().clear();},// 地图自适应图层所在元素的范围fitLayer(layer) {// let extent = layer.getSource().getExtent()// this.map.getView().fit(extent)},// polygon样式polygonStyle(code) {// 根据后端数据生成颜色 颜色约深 数量越多const color = getColorByDGP(code);return new Style({stroke: new Stroke({color: `rgba(255,255,255,1)`,width: 1,}),fill: new Fill({color: color,}),});},// 通过名字删除图层removeLayerByName(name) {var layersToRemove = [];this.map.getLayers().forEach(function (layer) {if (layer.get("name") != undefined && layer.get("name") === name) {layersToRemove.push(layer);}});var len = layersToRemove.length;for (var i = 0; i < len; i++) {this.map.removeLayer(layersToRemove[i]);}},// 添加弹窗addPopup(evt, evtFeature) {// 使用变量存储弹窗所需的 DOM 对象var container = document.getElementById("popup");var closer = document.getElementById("popup-closer");var content = document.getElementById("popup-content");// 创建一个弹窗 Overlay 对象this.overlay = new Overlay({element: container, //绑定 Overlay 对象和 DOM 对象的autoPan: true, // 定义弹出窗口在边缘点击时候可能不完整 设置自动平移效果autoPanAnimation: {duration: 250, //自动平移效果的动画时间 9毫秒},});// 将弹窗添加到 map 地图中this.map.addOverlay(this.overlay);let _that = this;/*** 添加单击响应函数来处理弹窗动作 pointermove*/this.map.on("pointermove", async (evt) => {//判断鼠标是否悬停在要素上let feature = evtFeature? evtFeature: this.map.forEachFeatureAtPixel(evt.pixel,function (feature, layer) {return feature;});// 如果要素 存在且有数据if (feature != undefined && feature.values_.self) {// 改变鼠标样式_that.map.getTargetElement().style.cursor = "pointer";//拿数据const data = feature.values_.self;// 画弹窗样式const contentHtml = `<div class='myContent' ><div class='myContentTitle'>${data.companyName}</div><div class='myContentData'><div class='myContentItem' ><div  class='left'>参与调查人数: </div><div class='right'>${data.personCount}</div><div style='flex:1'>人</div></div><div class='myContentItem' ><div class='left'>累计报告数量: </div><div class='right'> ${data.reportCount}</div><div style='flex:1'>份</div></div></div></div>`;content.innerHTML = contentHtml;content.style.display = "block";content.style.left = evt.pixel[0] - 120 + "px";content.style.top = evt.pixel[1] - 270 + "px";_that.overlay.setPosition(evt.coordinate);} else {//鼠标没有悬停在要素上_that.map.getTargetElement().style.cursor = "auto";content.style.display = "none";_that.overlay.setPosition(undefined);}});},},
};
</script>
<style lang="less" scoped>
.box {width: 100%;height: 100%;position: relative;// background: rgba(9,15,39,1);#map {width: 100%;height: 100%;}
}
</style>

附件

包括中国区划JSON,测试数据,图片文件等

百度网盘链接
链接: https://pan.baidu.com/s/110Mu6Ioonp9ztHRD0Yq6JQ 密码: mrqs

vue+openlayers实现行政边界、标注交互、效果弹窗相关推荐

  1. 在vue中用openlayers调取天地图服务并动态选择各个省份的中心,及行政边界

    vue这块我就不说了,直接讲openlayers. 1.openlayers是什么? Openlayers是一个专为Web GIS客户端开发提供的JavaScript类库包,用于实现标准格式发布的地图 ...

  2. 130:vue+openlayers 加载中国边界JSON数据(EPSG:4326)

    第130个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中添加JSON数据,显示的是中国的边界线(EPSG:4326). 直接复制下面的 vue+openlayers源代 ...

  3. 182:vue+openlayers 使用d3实现地图区块呈现不同颜色的效果

    第182个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中加载解析geojson文件,同时利用d3的颜色功能,使得美国每个州呈现出不同的颜色区块,方便识别. 直接复制下面 ...

  4. 022:vue+openlayers加载中国边界JSON数据(代码示例)

    第022个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中添加JSON数据,显示的是中国的边界线(EPSG:3857). 直接复制下面的 vue+openlayers源代 ...

  5. 222:vue+openlayers 实现云雾缭绕,白鸽飞翔的效果

    第222个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayersvue+openlayers: 实现云雾缭绕,白鸽飞翔的效果,这里主要是动态的在canvas上绘制白鸽和云雾效果. ...

  6. 015:vue+openlayers 添加鹰眼效果( 代码示例 )

    第015个 点击查看专栏目录 本示例的目的是介绍演示如何在openlayers中使添加使用鹰眼控件,呈现鹰眼的效果. 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果: ...

  7. SpringBoot+Vue+Openlayers实现地图上新增和编辑坐标并保存提交

    场景 若依前后端分离版手把手教你本地搭建环境并运行项目: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108465662 Vue ...

  8. vue openlayers 加载高德地图等 gcj02 的图层偏移问题

    vue openlayers 加载高德地图等 gcj02 的图层偏移问题 这个问题是在使用 openlayers 地图引擎加载高德地图或者是谷歌地图都会遇到的问题,所以说呢这篇博文稍微说一下解决办法. ...

  9. 141:vue+openlayers 测量长度和面积,尾随数字和关闭按钮

    第141个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中测量长度和面积,控制开关及提示信息尾随在某个点上,长度尾随在最后一个点,面积是extent的中心交互点. 直接复 ...

最新文章

  1. 《Python从小白到大牛》第6章 数据类型
  2. mysql 5.7_MySQL 5.7新特性介绍
  3. java有没有求组合的函数_如何在Java 8中使用compose和andThen组合函数
  4. 【Linux】ubuntu或linux网卡配置/etc/network/interfaces
  5. html像素绘制文字,HTML5 - Canvas的使用样例10(绘制文本)
  6. spad 探测器_大面阵SPAD阵列集成微透镜阵列,填充因子改善明显
  7. TS对象中的实例属性和静态属性
  8. Hive分析窗口函数 NTILE,ROW_NUMBER,RANK,DENSE_RANK
  9. Android逆向笔记-大部分内购游戏破解思路
  10. react改变checkbox的文字类型_React Checkbox不发送onChange
  11. C++变量作用域、生存期、存储类别
  12. 数据库学习与应用之什么是数据库
  13. 人脸识别 (4) 人脸对齐
  14. 电商平台接入手机支付宝支付(服务商授权模式,可直接付款给卖家)
  15. CF1313 C2. Skyscrapers (hard version)
  16. 为什么要参加hadoop培训
  17. 应用商店安装ubantu_从 Play 商店下载 Android 应用安装文件 .apk
  18. 微软打补丁出现“此更新不适用于您的计算机”
  19. Python(一)为什么要学习Python
  20. mac下的mysql的my.ini文件在哪里

热门文章

  1. 中控考勤机FACE702 JAVA调试,可能会持续更新
  2. java 视频压缩转码_java实现视频压缩转码
  3. 【vijos】1164 曹冲养猪(中国剩余定理)
  4. 菜鸟教程离线版下载链接 chm
  5. 黄金原野的 “去中心化认证“技术
  6. JavaScript操作摄像头指南
  7. MS-DOS安装过程
  8. 模仿网易云音乐黑胶唱片的交互实现
  9. 4G:SIM7600CE-CNSE 4G模组调试
  10. 液压比例溢流阀R901170278、DBET-62/200G24-8K4V