本文主要是介绍vue+pg库+openlayer5+geoserver+离线地图瓦片构建gis地图+地图撒点+点击点出现地图弹框(***完整流程***),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
实现效果:(ol5的apihttps://openlayers.org/en/latest/apidoc/module-ol_Feature-Feature.html#getProperties)
一、在vue项目中使用gis地图,实现地图的搭建
1、在pg库中存入你的gis地图数据(这里数据不提供,默认是实现了这一步)
2、在geoserver中获取pg库中的数据,并在geoserver中发布图层,将数据库中的数据发布成我们可以使用的地图
( 如果对这个步骤都不了解的朋友可以看下这个 ,详细步骤:https://blog.csdn.net/qq_41619796/article/details/93929048)
3、在tomcate中放入地图瓦片
4、在页面引用efgis.js:
efgis.js ,公共ol5方法封装
import {Map, View, Overlay } from 'ol';
import Feature from 'ol/Feature';
import {Text, Fill, Stroke, Style, Icon, Circle} from 'ol/style';
import {Tile as TileLayer, Vector as VectorLayer,} from 'ol/layer';
import {Vector as VectorSource, XYZ, TileWMS} from 'ol/source';
import {LineString, Point,Circle as CircleL} from 'ol/geom';
import {Control} from 'ol/control';
import axios from 'axios';
import store from '@/store/store.js';// 设置常量
var efgis = {};
const devStr = 'xinxing';/*** 地址*/
const DataSource = 'EPSG:4326'; //坐标系
var ServiceIP = ''; //地图服务地址
var TomcatIP = ''; //瓦片服务地址
var MapUrl = ''; //geoserver服务地址
var MapJDUrl = ''; //瓦片地址const defaultDatas = {center: [112.16629, 22.63025],zoom: 11,minZoom: 11,maxZoom: 24
};
var geolayerName = 'xinxing_xian';
var nowName = '';
var nowCenter = '';
var nowZoom = '';var map = null;
var points = [];
// 图层变量
let JDLayer = null;
let xinxingWhitelayer = null;
let nowIon = '';
let nowLat = '';
efgis = {/** 初始化地图对象* */init: function (mapDom,type) {/*** 填充地址-start*/ServiceIP = store.state.ServiceIP;//地图服务地址TomcatIP = store.state.TomcatIP; //瓦片服务地址MapUrl = ServiceIP + 'geoserver/xinxing/wms';//geoserver服务地址MapJDUrl = TomcatIP + 'xinxingmap/roadmap/{z}/{x}/{y}.png';//瓦片地址/*** 填充地址-end*/if(type !=undefined){if(type.name != undefined){nowName = type.name}if(type.center != undefined){nowCenter = type.center}if(type.zoom != undefined){nowZoom = type.zoom}}else{nowName = geolayerNamenowCenter = defaultDatas.centernowZoom = defaultDatas.zoom}map = new Map({target: mapDom,view: new View({center: nowCenter,zoom: nowZoom,projection: DataSource, //默认的是 'EPSG:3857'横轴墨卡托投影minZoom: defaultDatas.minZoom,maxZoom: defaultDatas.maxZoom}),layers: [],control: new Control({zoom: false})});this.initEvent();this.addGeoLayer(nowName);return map;},/*** 给地图添加事件*/initEvent() {var that = this;// 点击事件map.on('singleclick', e => {//点击事件获取当前经纬度nowIon = e.coordinate[0];//当前鼠标点击的经度nowLat = e.coordinate[1];//当前鼠标点击的纬度console.log('经度'+nowIon+","+'纬度'+nowLat);})//监听鼠标滚动事件map.getView().on('change:resolution', e => {let currentZoom = map.getView().getZoom();//当前地图层级if (currentZoom >= 13) {that.addXYZLayer();//当层级超过13层时调动地图瓦片that.addXinXingWhite();//当层级超过13层时调动geoserver图层} else {that.removeXYZLayer();that.removeXinXingWhite();}});let popupDom = document.getElementById('mapPopup');let popupContent = document.getElementById('popupContent');var overlay = new Overlay({element: popupDom,autoPan: true,autoPanAnimation: {duration: 250}});// 点击事件map.on("click", evt => {let addPopup = function (data) {if (data == undefined) {return;}let html = "";let html2 = '';for (let i in data) {if (data[i].name == 'name') {html += `<div class="tyDiv"><p class="ellipsis" style="text-align:center;width: 100%;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;padding-bottom: 5px" title="${data[i].val}">${data[i].val}</p>`continue;}html2 += `<p class="ellipsis" style="text-align:left;width: 100%;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;" title="${data[i].val}">${data[i].name}:${data[i].val}</p></div>`};return html + html2;}let flag = false;let pixel = map.getEventPixel(evt.originalEvent);map.forEachFeatureAtPixel(pixel, (feature) => {var baseURL = store.state.baseUrl;// console.log(111,feature.get('geometry').getType())if (feature.get('geometry').getType() != 'Point') {return;}// console.log(222,feature.getProperties())if (feature != undefined) {let properties = feature.getProperties();var coordinate = evt.coordinate;if (properties.html == '' || properties.html == undefined) {let data = properties.data;let html = addPopup(data);if (html != '') {$(popupContent).html(html)overlay.setPosition(coordinate);flag = true;}}else {//供电服务-停电管理-地图弹框if(properties.html.class =='gdfw_tdgl' && properties.html.id !='' && properties.html.id !=undefined && properties.html.id !=null){let url = `${baseURL}:9004/tdfw/findGzMessage`;let param = new URLSearchParams();param.append("id", properties.html.id);param.append("bs", properties.html.bs);param.append("dj", properties.html.dj);// axios({// url: url,// method: 'post',// params: param// }).then((res) => {// if (res.status === 200) {let res = {data:{gdxx:{lxdh:"18610086001",qxsj:"2019-07-10 09:50:00",qxzt:"处理完成",gzdz:"估伦村公用台变(200kVA)",gzfzr:"张三",gznr:"10kV长江线703开关保护测控装置零序过流保护功能调试和零序CT变比测试",gzzt:[{sj:"2019-07-10 08:30:00",zt:"派单"},{sj:"2019-07-10 08:50:00",zt:"接单"},{sj:"2019-07-10 09:50:00",zt:"到达现场"},{sj:"2019-07-10 10:30:00",zt:"故障确认"},{sj:"2019-07-10 12:22:00",zt:"处理完成"}]},xyfw:{tdsb:"1、35kV蕉山变电站10kV太平线#51杆51T1开关后段线路;2、110kV六祖变电站701开关;3、110kV六祖变电站702开关。",xlmc:"蕉山线",yxdkh:"否",yxgl:"否",yxzykh:"否"}}}let gdxx = res.data.gdxx;let xyfw = res.data.xyfw;let gzttStr = '<div></div>'gdxx.gzzt.forEach((item)=>{let nowLi = ` <li><p>`+item.zt+`</p><span title=`+item.sj+`>`+item.sj+`</span></li>`gzttStr += nowLi;});let str =`<div class='gdPopoverCon'><ul class="gdModelTab"><li id='gdxx' class='tabAct'>工单信息</li><li id='yxfw'>影响范围</li></ul><ul class="gdModelCon gdModelConO"><li><p>工作负责人:<span title="`+gdxx.gzfzr+`">`+gdxx.gzfzr+`</span></p><p>联系电话:<span title="`+gdxx.lxdh+`">`+gdxx.lxdh+`</span></p></li><li><p>抢修状态:<span title="`+gdxx.qxzt+`">`+gdxx.qxzt+`</span></p><p>抢修时间:<span title="`+gdxx.qxsj+`">`+gdxx.qxsj+`</span></p></li><li class="tsStyle"><p>故障地址:<span title="`+gdxx.gzdz+`">`+gdxx.gzdz+`</span></p></li><li class="tsStyle"><p>故障描述:<span title="`+gdxx.gznr+`">`+gdxx.gznr+`</span></p></li><li class="gzztDiv"><p class="gzztTit">工作状态:</p><ul class="gzzt">`+gzttStr+`</ul></li></ul><ul class="gdModelCon gdModelConT" style='display:none;'><li><p>线路名称:<span title="`+gdxx.xlmc+`">`+xyfw.xlmc+`</span></p><p>设备名称:<span title="`+gdxx.tdsb+`">`+xyfw.tdsb+`</span></p></li><li><p>抢修状态:<span title="`+gdxx.yxdkh+`">`+xyfw.yxdkh+`</span></p><p>抢修时间:<span title="`+gdxx.yxgl+`">`+xyfw.yxgl+`</span></p></li><li class="tsStyle"><p>故障地址:<span title="`+gdxx.yxzykh+`">`+xyfw.yxzykh+`</span></p></li></ul><div>`$(popupContent).html(str)overlay.setPosition(coordinate);flag = true;return;// }// }).catch((error) => {// console.log(error);// })}let data = properties.html;let html = data;if (html != '') {$(popupContent).html(html)overlay.setPosition(coordinate);flag = true;}}}})if (flag) {map.addOverlay(overlay)} else {map.removeOverlay(overlay);}});},//添加geoserver图层方法addGeoLayer: function (name,zIndex) {let layer = new TileLayer({source: new TileWMS({url: MapUrl,params: {'LAYERS': devStr + ':' + name,'TILED': true},serverType: 'geoserver',projection: DataSource}),zIndex: zIndex?zIndex:10});map.addLayer(layer);return layer},// 添加geoserver图层addXinXingWhite: function () {if(xinxingWhitelayer==null){// console.log(1111)xinxingWhitelayer = this.addGeoLayer("xinxing_yaogan", 14);// console.log(222,xinxingWhitelayer)}},// 移除geoserver图层removeXinXingWhite:function(){if(xinxingWhitelayer!=null){map.removeLayer(xinxingWhitelayer);xinxingWhitelayer = null;}},/** 添加瓦片图层* */addXYZLayer: function () {if (JDLayer) {return;} else {JDLayer = new TileLayer({source: new XYZ({url: MapJDUrl}),zIndex: 11});map.addLayer(JDLayer);}},// 删除瓦片图层removeXYZLayer: function () {if (JDLayer) {map.removeLayer(JDLayer)JDLayer = null;}},// 添加多个点,返回图层对象FeatureaddPointsLayer: function (points) {let arrLayer = [];for (let i = 0; i < points.length; i++) {let point1X = points[i].lon;let point1Y = points[i].lat;let name1 = points[i].num;let feature = this.vectorPointCircleReturn([point1X, point1Y], name1, points[i])arrLayer.push(feature)}return arrLayer;},addPointsLayer_zygk: function (points) {let arrLayer = [];for (let i = 0; i < points.length; i++) {let pointX = points[i].lon;let pointY = points[i].lat;let name = points[i].name;let img = points[i].img;let params = points[i].paramslet option ={coordinate:[pointX, pointY],img:img,name:name,params: params,scale:1.2,zIndex:99}if (points[i].html != undefined) {option.html = points[i].html} else {option.html = ""}let feature = this.vectorPointImgReturn_zygk(option)arrLayer.push(feature)}return arrLayer;},//删除多个点,输入图层对象数组removePointslayer(arr) {for (let i = 0; i < arr.length; i++) {map.removeLayer(arr[i]);}},// 撒点,点是图片vectorPointImg: function (point, img, name) {let scale = 0.4;let styleFunc = function () {let style = new Style({image: new Icon({src: require('../assets/images/mapIcon/' + img + '.png'),scale: scale}),text: new Text({ //文本样式text: name,textAlign: 'center',offsetY: -20,offsetX: 10,textBaseline: 'middle',font: '17px Calibri,sans-serif',fill: new Fill({color: '#cd5c5c'})})})return style;}let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: 99,style: styleFunc()});// let points = [112.16629, 22.65025]// let points = fromLonLat([112.16629, 22.65025])let fetureLine = new Feature({geometry: new Point(point)});source.addFeature(fetureLine);map.addLayer(vector);},vectorPointCircleReturn: function (point, name, data) {let scale = 0.4;let styleFunc = function () {let style = new Style({image: new Circle({radius: 7,stroke: new Stroke({color: '#cd5c5c'}),fill: new Fill({color: '#cd5c5c'})}),text: new Text({ //文本样式text: data.name ? data.name : '',textAlign: 'center',offsetY: -20,offsetX: 10,textBaseline: 'middle',font: '17px Calibri,sans-serif',fill: new Fill({color: '#cd5c5c'})})})return style;}let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: 99,style: styleFunc()});// let points = [112.16629, 22.65025]// let points = fromLonLat([112.16629, 22.65025])let fetureLine = new Feature({geometry: new Point(point),data: data.params});source.addFeature(fetureLine);map.addLayer(vector);return vector;},vectorPointImgReturn_zygk: function (option) {let styleFunc = function () {let scale = 0.4;let zIndex=99;let style = new Style({image: new Icon({src: require('../assets/images/mapIcon/' + option.img + '.png'),scale: option.scale?option.scale:scale}),text: new Text({ //文本样式text: option.name?option.name:'',textAlign: 'center',offsetY: -20,offsetX: 10,textBaseline: 'middle',font: '17px Calibri,sans-serif',fill: new Fill({color: '#cd5c5c'})})})return style;}let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: option.zIndex?option.zIndex:zIndex,style: styleFunc()});// let points = [112.16629, 22.65025]// let points = fromLonLat([112.16629, 22.65025])let fetureLine = new Feature({geometry: new Point(option.coordinate),data: option.params,html: option.html});source.addFeature(fetureLine);map.addLayer(vector);return vector;},// 撒点,可以改变样式vectorPointColor: function (coordinate, styles, data) {let defaultStyle = {color: '#cd5c5c',radius: 7,text: ''}let styleFunc = function (styles) {let style = new Style({image: new Circle({radius: styles.radius ? styles.radius : defaultStyle.radius,stroke: new Stroke({color: styles.color ? styles.color : defaultStyle.color}),fill: new Fill({color: styles.color ? styles.color : defaultStyle.color})}),text: new Text({ //文本样式text: styles.text ? styles.text : defaultStyle.text,textAlign: 'center',offsetY: -20,offsetX: 0,textBaseline: 'middle',font: '16px Microsoft YaHei',fill: new Fill({color: styles.color ? styles.color : defaultStyle.color})})})return style;}let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: 99,style: styleFunc(styles)});// let points = [112.16629, 22.65025]// let points = fromLonLat([112.16629, 22.65025])let feturePoint = new Feature({geometry: new Point(coordinate),data: data.params});source.addFeature(feturePoint);map.addLayer(vector);return vector;},// 潮流线路绘制vectorLineAutoDash_clt: function (lineArr) {let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: 97});// 遍历画线画点for (let j = 0; j < lineArr.length; j++) {let lines = lineArr[j];for (let i = 0; i < lines.length; i++) {if (i == lines.length - 1) {continue;}let to = {coordinate: [lines[i].lon, lines[i].lat],name: lines[i].name,type: lines[i].type,params: lines[i].params,};let from = {coordinate: [lines[i + 1].lon, lines[i + 1].lat],name: lines[i + 1].name,type: lines[i + 1].type,params: lines[i + 1].params,}let points = new Array(to.coordinate, from.coordinate);// let defaultStyle = {// color:'#cd5c5c',// radius :7,// text:''// }// 设置变电站点的颜色let getColor = function (type) {if (type == '110') {return '#cd5c5c'} else if (type == '35') {return '#33cccc'} else if (type == '220') {return '#fff';} else {return '#fff';}}let colorLine = getColor(to.type)// 绘制点this.vectorPointColor(from.coordinate, {color: getColor(from.type),radius: 7,text: from.name,}, from)this.vectorPointColor(to.coordinate, {color: getColor(to.type),radius: 7,text: to.name}, to)let fetureLine = new Feature({geometry: new LineString(points),dashOffset: 0});// 线背景let outlineStroke = new Style({stroke: new Stroke({// color:'red',// width:5})});let getAnimationStrokeStyle = function () {return new Style({stroke: new Stroke({color: colorLine,width: 4,lineDash: [1, 3, 5],lineDashOffset: fetureLine.get("dashOffset")})})};let getStyle = function () {return [outlineStroke, getAnimationStrokeStyle()];}fetureLine.setStyle(getStyle);source.addFeature(fetureLine);setInterval(function () {let offset = fetureLine.get('dashOffset');offset = offset == 8 ? 0 : offset + 4;fetureLine.set('dashOffset', offset);}, 100);}}map.addLayer(vector);},// 画直线,可以改变颜色vectorLineSoildColor: function (points, color) {let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: 99});let fetureLine = new Feature({geometry: new LineString(points),});let defaultStyle = {color: 'yellow'}function getStyle(color) {return new Style({stroke: new Stroke({color: color == '' ? defaultStyle.color : color,width: 5})});}fetureLine.setStyle(getStyle(color));source.addFeature(fetureLine);map.addLayer(vector);},// 画多条直线vectorLinesSoild: function (points) {let isError = false;for (let i = 0; i < points.length; i++) {if (i == (points.length - 1)) return;let point1X = points[i].lon;let point1Y = points[i].lat;let point2X = points[i + 1].lon;let point2Y = points[i + 1].lat;let color = '';if (points[i].yc == 1) {if (!isError) {color = 'red';}isError = !isError;}this.vectorLineSoildColor(new Array([point1X, point1Y], [point2X, point2Y]), color);}},// 恢复默认位置resetCenter: function () {map.getView().animate({center: defaultDatas.center}, {zoom: defaultDatas.zoom})},// 根据zoom加载或者删除点LineAndPoindZoom: function (points) {let xlPoints = [];let isOk = true;map.on('moveend', e => {let currentZoom = map.getView().getZoom();if (currentZoom >= 13) {if (isOk) {xlPoints = this.addPointsLayer(points)isOk = !isOk;}} else {if (!isOk) {this.removePointslayer(xlPoints)xlPoints = [];isOk = !isOk;}}});},load3minReset: function () {let that = this;let zoomArr = [];let i = 0;let timer = setInterval(() => {let zoom = map.getView().getZoom();if (i == 29) {that.resetCenter();zoomArr = [];i = 0;window.clearInterval(timer);return;}i++;if (zoomArr.length > 0) {if (zoomArr[zoomArr.length - 1] != zoom) {zoomArr = [];i = 0;return;}}zoomArr.push(zoom);}, 6000)return timer;},addCirclelayers:function (data,r) {// let data = [112.314079,22.6958978];r = r/10000;let styleFunc = function (name) {let style = new Style({fill:new Fill({color:[255,0,0,.3]}),stroke:new Stroke({color:'red',width:2})})return style;}let source = new VectorSource({wrapX: false});let vector = new VectorLayer({source: source,zIndex: 99,style: styleFunc(name)});// let points = [112.16629, 22.65025]// let points = fromLonLat([112.16629, 22.65025])let fetureLine = new Feature({// geometry: new CircleL(data,0.005),geometry: new CircleL(data,r),});source.addFeature(fetureLine);map.addLayer(vector);}
}export default efgis
5、创建地图组件 power_tqT.vue ,并引用
<template><div class="mapBox"><div id="map_tqT" ref="rootmap" class="box"></div><map-popup></map-popup><div class="getMore" @click="getClickQP"></div></div></template><script>import 'ol/ol.css';import webgisT from '../../config/efgistqT';import mapPopup from './mapPopup';export default {name:'城东站-地图',props: ['zydPoints'],//撒点数据data() {return {map: null,zydLayer: null,fullscreen:false,//是否全屏pointCenter:'',}},methods: {// 地图初始化init: function () {// geoserver图层this.map = webgisT.init('map_tqT');//注册地图并渲染到指定容器},// 作业点初始化initzydPoint() {let list = [];let pointsData = [];for (let i = 0; i < this.zydPoints.length; i++) {let data = this.zydPoints[i];let obj = {name:'',lon: data.jd,lat: data.wd,sb:data.sb,params: [],html:{'class':'sbyw_zntq',//设备运维-智能台区'data':data.sb,//数据名称'VandA':[]}};pointsData[i]=[];pointsData[i].push(data.jd);pointsData[i].push(data.wd);obj.html.VandA.push(data.ssdl);obj.html.VandA.push(data.ssdy);if(obj.sb.length>1){obj.sb.forEach((e,index) => {let lx = e.lx;if(lx == '杆'){obj['img'] = 'map_tq_gt';}else if(lx == '表'){obj['img'] = 'map_tq_db';} else if (lx == '变压器') {obj['img'] = 'map_tq_byq';}else{obj['img'] = '';}});}else if(obj.sb.length==1){let lx = obj.sb[0].lx;if (lx == '杆') {obj['img'] = 'map_tq_gt';} else if (lx == '表') {obj['img'] = 'map_tq_db';} else if (lx == '变压器') {obj['img'] = 'map_tq_byq';}else {obj['img'] = '';}}else{obj['img'] = '';}list.push(obj);}this.lists = list;webgisT.getCenterMap(pointsData);this.zydLayer = webgisT.addPointsLayer_zygk(list);},removezydPoint() {webgisT.removePointslayer(this.zydLayer);},goback(){this.$emit('goback');},getClickQP(){//点击全屏方法this.commonjs.getNowTit(this.fullscreen,'map_tqT');this.fullscreen = false;}},components:{mapPopup},mounted() {this.init();//加载作业点},watch: {zydPoints(val, old) {if (val != old) {if (this.zydLayer != null) {this.removezydPoint();}this.initzydPoint();}}}}
</script>
<style scoped lang="less"></style>
6、地图弹框的组件mapPopup.vue
<template><div id="mapPopup" @click="popupClick"><div id="popupContent"></div><div id="popupContenthover"></div></div>
</template><script>import {mapActions} from 'vuex';export default {name: "mapPopup-地图弹框部分",data() {return {}},mounted(){},methods: {...mapActions([]),popupClick(even){let eID = even.target.id;if(eID == 'gdxx'){$(".gdModelConO").show();$(".gdModelConT").hide();$("#"+eID).addClass("tabAct").siblings().removeClass("tabAct");}if(eID == 'yxfw'){$(".gdModelConO").hide();$(".gdModelConT").show();$("#"+eID).addClass("tabAct").siblings().removeClass("tabAct");}if(eID == 'czmc' || eID == 'czmc_div'){let czmc = $("#czmc").html();}},},}
</script><style lang="less">#mapPopup {background: url(../../assets/images/bgGis.png) no-repeat center;background-size: 100% 100%;text-align: left;#popupContent {.tyDiv{//默认样式width:300px;height: 80px;padding: 2%;box-sizing: border-box;}p {color: #6e6e6e;line-height: 30px;font-size: 16px;}p:first-child {text-align: center;color: #333333;}}// 供电服务弹框样式.gdPopoverCon{width: 500px;height: 500px;padding: 2%;box-sizing: border-box;ul{overflow: hidden;}.gdModelTab{height:10%;li{float:left;width:120px;text-align: center;line-height: 42px;background:#053da8;border:1px solid #0376db;font-size: 20px;color : #fff;margin-right:3%;&:hover{cursor: pointer;}}.tabAct{background:#1444d0;border:1px solid #049eff;}}.gdModelCon{height:90%;padding-top:3%;box-sizing: border-box;li{width:100%;height:44px;p{float:left;text-align: left!important;color:#31e4ff!important;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;&:nth-child(odd){width:40%;}&:nth-child(even){width:60%;}span{color:#cef9ff;}}}.tsStyle{p{width:100%!important;}}}.gzztDiv{//工作状态.gzztTit{width:28%!important;}.gzzt{position:relative;li{background: url(../../assets/images/mapLi.png) no-repeat center;background-size: 100%;margin-bottom:2%;padding-left: 10%;box-sizing: border-box;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;p{width:40%;color:#fff;}span{display: block;width:60%;padding-top:4%;box-sizing: border-box;float:right;color:#00EE7E;}}div{position: absolute;left: 2%;top: 10%;width:2px;height:80%;border-left:1px solid #00B2FC;}}}}//作业管控-智慧现场弹框.zygkZHXC{height: 400px;}}
</style>
***注意这是我的项目必须的组件,方法,请谨慎使用,基于openlayer5实现的gis地图,有不懂的可以一起讨论
这篇关于vue+pg库+openlayer5+geoserver+离线地图瓦片构建gis地图+地图撒点+点击点出现地图弹框(***完整流程***)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!