فهرست منبع

Merge branch 'master' of http://www.sysuimars.cn:3000/feiniao/adopt-mini-h5

wangsisi 1 هفته پیش
والد
کامیت
8eefb91365
57فایلهای تغییر یافته به همراه6425 افزوده شده و 7 حذف شده
  1. 4 0
      package.json
  2. 5 0
      src/api/eventBus.js
  3. 12 0
      src/api/modules/lj_home.js
  4. 8 0
      src/api/modules/region.js
  5. 12 0
      src/api/modules/system.js
  6. 8 0
      src/api/modules/variety.js
  7. BIN
      src/assets/img/map/active-bg.png
  8. BIN
      src/assets/img/map/checked.png
  9. BIN
      src/assets/img/map/here.png
  10. BIN
      src/assets/img/map/lichi.png
  11. BIN
      src/assets/img/map/progress-active.png
  12. BIN
      src/assets/img/map/progress.png
  13. BIN
      src/assets/img/map/tree-active.png
  14. BIN
      src/assets/img/map/tree.png
  15. BIN
      src/assets/img/old_mini/gybg.png
  16. 9 0
      src/common/commonFun.js
  17. 124 0
      src/common/ol_common.js
  18. 1 1
      src/components/BaseFooter.vue
  19. 6 0
      src/router/globalRoutes.js
  20. 2 0
      src/store/index.js
  21. 11 0
      src/store/modules/home/index.js
  22. 204 0
      src/utils/map.js
  23. 78 0
      src/utils/ol-map/Bounds.js
  24. 63 0
      src/utils/ol-map/Check.js
  25. 143 0
      src/utils/ol-map/Circle.js
  26. 145 0
      src/utils/ol-map/Common.js
  27. 37 0
      src/utils/ol-map/CustomLayer.js
  28. 9 0
      src/utils/ol-map/Enum.js
  29. 19 0
      src/utils/ol-map/Info.js
  30. 191 0
      src/utils/ol-map/InfoWindow.js
  31. 11 0
      src/utils/ol-map/KBaseObject.js
  32. 38 0
      src/utils/ol-map/KMap.js
  33. 313 0
      src/utils/ol-map/Label.js
  34. 78 0
      src/utils/ol-map/LngLat.js
  35. 1290 0
      src/utils/ol-map/Map.js
  36. 566 0
      src/utils/ol-map/Marker.js
  37. 108 0
      src/utils/ol-map/MultiPolygon.js
  38. 55 0
      src/utils/ol-map/Pixel.js
  39. 108 0
      src/utils/ol-map/Polygon.js
  40. 297 0
      src/utils/ol-map/Polyline.js
  41. 101 0
      src/utils/ol-map/Popup.js
  42. 39 0
      src/utils/ol-map/Size.js
  43. 88 0
      src/utils/ol-map/VTLayer.js
  44. 108 0
      src/utils/ol-map/VectorLayer.js
  45. 214 0
      src/utils/ol-map/VectorStyle.js
  46. 327 0
      src/utils/ol-map/WMSLayer.js
  47. 111 0
      src/utils/ol-map/WMTSLayer.js
  48. 113 0
      src/utils/ol-map/XYZLayer.js
  49. 88 0
      src/utils/ol-map/css/KMap.css
  50. BIN
      src/utils/ol-map/css/rotate.png
  51. 389 0
      src/views/adopt_map/index.vue
  52. 387 0
      src/views/adopt_map/map/clusterPointsLayer.js
  53. 23 0
      src/views/adopt_map/map/index.js
  54. 102 0
      src/views/adopt_map/map/regionInfoCard.css
  55. 94 0
      src/views/adopt_map/map/regionInfoCard.js
  56. 281 0
      src/views/adopt_map/map/regionLayer.js
  57. 5 6
      vite.config.ts

+ 4 - 0
package.json

@@ -11,9 +11,13 @@
   "dependencies": {
     "@iconify/vue": "^5.0.1",
     "axios": "^1.16.1",
+    "jsts": "^2.12.1",
     "less": "^4.6.4",
+    "mitt": "^3.0.1",
     "normalize.css": "^8.0.1",
     "nprogress": "^0.2.0",
+    "ol": "^6.15.1",
+    "ol-ext": "^3.2.4",
     "qs": "^6.15.2",
     "vue": "^3.5.34",
     "vue-router": "^5.0.7",

+ 5 - 0
src/api/eventBus.js

@@ -0,0 +1,5 @@
+import mitt from 'mitt'
+
+const eventBus = mitt()
+
+export default eventBus

+ 12 - 0
src/api/modules/lj_home.js

@@ -0,0 +1,12 @@
+import config from '../config.js'
+
+export default {
+  farmBuyInfo: {
+    url: config.base_dev_url + 'z_farm_buy/farmBuyInfo?isLighten=1',
+    type: 'get',
+  },
+  getLightTree: {
+    url: config.base_dev_url + 'z_farm_buy/getLightTree',
+    type: 'get',
+  },
+}

+ 8 - 0
src/api/modules/region.js

@@ -0,0 +1,8 @@
+import config from '../config.js'
+
+export default {
+  list: {
+    url: config.base_dev_url + 'region/list',
+    type: 'post',
+  },
+}

+ 12 - 0
src/api/modules/system.js

@@ -0,0 +1,12 @@
+import config from '../config.js'
+
+export default {
+  devLogin: {
+    url: config.base_dev_url + 'mini_min_app_user/dev_login',
+    type: 'get',
+  },
+  getCfg: {
+    url: config.base_dev_url + "cfg/get?key=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9",
+    type: "post",
+},
+}

+ 8 - 0
src/api/modules/variety.js

@@ -0,0 +1,8 @@
+import config from '../config.js'
+
+export default {
+  getBuyTreeList: {
+    url: config.base_dev_url + 'z_farm_buy/treeList2',
+    type: 'get',
+  },
+}

BIN
src/assets/img/map/active-bg.png


BIN
src/assets/img/map/checked.png


BIN
src/assets/img/map/here.png


BIN
src/assets/img/map/lichi.png


BIN
src/assets/img/map/progress-active.png


BIN
src/assets/img/map/progress.png


BIN
src/assets/img/map/tree-active.png


BIN
src/assets/img/map/tree.png


BIN
src/assets/img/old_mini/gybg.png


+ 9 - 0
src/common/commonFun.js

@@ -0,0 +1,9 @@
+function extractCoordinates(input) {
+  const match = input.match(/\(([^)]+)\)/)
+  if (match) {
+    return match[1].split(' ').map(Number)
+  }
+  return null
+}
+
+export { extractCoordinates }

+ 124 - 0
src/common/ol_common.js

@@ -0,0 +1,124 @@
+import sourceWMTS from 'ol/source/WMTS';
+import WMTSTileGrid from 'ol/tilegrid/WMTS';
+import * as olExtent from 'ol/extent';
+import TileLayer from 'ol/layer/Tile';
+import XYZ from 'ol/source/XYZ.js';
+import {WKT} from "ol/format";
+import BufferOp from 'jsts/org/locationtech/jts/operation/buffer/BufferOp.js'
+import {OL3Parser} from "jsts/org/locationtech/jts/io"
+import * as geom from "ol/geom";
+import Feature from "ol/Feature";
+let jstsParser = new OL3Parser();
+jstsParser.inject(geom.Point, geom.LineString, geom.LinearRing, geom.Polygon, geom.MultiPoint, geom.MultiLineString, geom.MultiPolygon);
+
+
+
+//Code To RGB
+function CodeToRGB(code){
+    let result = [];
+    result.push(parseInt(code.substring(1, 3), 16));
+    result.push(parseInt(code.substring(3, 5), 16));
+    result.push(parseInt(code.substring(5) , 16));
+    return result;
+}
+
+
+
+//创建图层(WMTS方式)
+function createGDWmts(){
+    let AMapLayer = new TileLayer({
+        source: new XYZ({
+            url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
+        }),
+        visible: false,
+    });
+    return AMapLayer;
+}
+
+
+function crtLayerWMTS(wmtsData, opacity, projection){
+    let projectionExtent = projection.getExtent();
+    let size = olExtent.getWidth(projectionExtent) / 256;
+    let resolutions = new Array(19);
+    let matrixIds = new Array(19);
+    for (var z = 1; z < 19; ++z) {
+        // generate resolutions and matrixIds arrays for this WMTS
+        resolutions[z] = size / Math.pow(2, z);
+        matrixIds[z] = z;
+    }
+
+    let source = new sourceWMTS({
+        url: wmtsData.url,
+        layer: wmtsData.layer,
+        matrixSet: wmtsData.matrixSet,
+        format: 'tiles',
+        projection: projection,
+        tileGrid: new WMTSTileGrid({
+            origin: olExtent.getTopLeft(projectionExtent),
+            resolutions: resolutions,
+            matrixIds: matrixIds
+        }),
+        style: 'default',
+        wrapX: true
+    })
+
+    let layer = new TileLayer({
+        zIndex:wmtsData.zIndex,
+        source: source,
+        visible: false,
+    });
+    layer.id = wmtsData.layer+"_"+wmtsData.matrixSet;
+    return layer;
+}
+
+function wktCastGeom(wkt){
+    return new WKT().readGeometry(wkt);
+}
+
+function fit(wkt,view,zoom){
+    let extent = wktCastGeom(wkt).getExtent()
+    extent[0] = extent[0] / zoom;
+    extent[1] = extent[1] / zoom;
+    extent[2] = extent[2] * zoom;
+    extent[3] = extent[3] * zoom;
+    view.fit(extent,{duration:700})
+}
+
+function fitExtent(extent, view, zoom){
+    let extent2 = [];
+    extent2[0] = extent[0] / zoom;
+    extent2[1] = extent[1] / zoom;
+    extent2[2] = extent[2] * zoom;
+    extent2[3] = extent[3] * zoom;
+    view.fit(extent2,{duration:700})
+}
+
+const fitBuffer = (pointWkt, map, meter)=>{
+    let geom = new WKT().readGeometry(pointWkt);
+    let degree = meter / (2 * Math.PI * 6371004) * 360;
+    geom = jstsParser.write(BufferOp.bufferOp(jstsParser.read(geom), degree, 8))
+    map.getView().fit(geom,{duration: 1000})
+}
+
+
+const newAreaFeature = (data,fieldGeom)=>{
+    let geom = new WKT().readGeometry(data[fieldGeom || "geom"])
+    let feature = new Feature({
+        geometry: geom
+    });
+    feature.set("nodeType","area");
+    feature.setId(data.id)
+    for(let key in data){
+        if(key != "geom"){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+
+export{
+    crtLayerWMTS,CodeToRGB,createGDWmts,wktCastGeom,fit,fitBuffer,fitExtent,newAreaFeature
+};
+
+

+ 1 - 1
src/components/BaseFooter.vue

@@ -53,7 +53,7 @@ const tabs = [
   },
   {
     id: 2,
-    path: '/guard-map',
+    path: '/adopt_map',
     label: '守护地图',
     icon: tab2,
     iconActive: tabAct2,

+ 6 - 0
src/router/globalRoutes.js

@@ -5,4 +5,10 @@ export default [
     meta: { title: '农场美景' },
     component: () => import('@/views/home/index.vue'),
   },
+  {
+    path: '/adopt_map',
+    name: 'AdoptMap',
+    meta: { title: '领养地图' },
+    component: () => import('@/views/adopt_map/index.vue'),
+  },
 ]

+ 2 - 0
src/store/index.js

@@ -1,10 +1,12 @@
 import { createStore } from 'vuex'
 import getters from './getters.js'
 import app from './modules/app/index.js'
+import home from './modules/home/index.js'
 
 export default createStore({
   getters,
   modules: {
     app,
+    home,
   },
 })

+ 11 - 0
src/store/modules/home/index.js

@@ -0,0 +1,11 @@
+export default {
+  namespaced: true,
+  state: () => ({
+    miniUserId: localStorage.getItem('MINI_USER_ID'),
+  }),
+  mutations: {
+    SET_MINI_USER_ID(state) {
+      state.miniUserId = localStorage.getItem('MINI_USER_ID')
+    },
+  },
+}

+ 204 - 0
src/utils/map.js

@@ -0,0 +1,204 @@
+import VectorSource from 'ol/source/Vector.js';
+import WKT from 'ol/format/WKT.js';
+import Feature from 'ol/Feature.js';
+import VectorLayer from 'ol/layer/Vector.js';
+import Draw from "ol/interaction/Draw";
+import Text from "ol/style/Text";
+import Icon from "ol/style/Icon";
+import {Circle, Fill, Stroke, Style} from 'ol/style.js';
+import * as proj from "ol/proj";
+import {getArea} from "ol/sphere.js";
+
+/*
+ * @Author: your name
+ * @Date: 2021-01-12 09:38:09
+ * @LastEditTime: 2022-01-20 10:37:39
+ * @LastEditors: Please set LastEditors
+ * @Description: In User Settings Edit
+ * @FilePath: \vue3-element-admin\src\utils\map.js
+ */
+/**
+ * 过滤不应该出现在属性列表的字段
+ * @param data
+ * @param key
+ * @returns {boolean}
+ */
+function filterWktProp(data,key){
+    if(key == "regionWkt" || key == "wkt" || key == "pointWkt" || key == "geom" || !data[key]){
+        return false
+    }
+    return true
+}
+
+export const newAreaFeature = (data)=>{
+    let geom = new WKT().readGeometry(data["wkt"])
+    let feature = new Feature({
+        geometry: geom
+    });
+    feature.set("nodeType","area");
+    feature.setId(data.id)
+    for(let key in data){
+        if(filterWktProp(data,key)){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+export const newAreaPoint = (data)=>{
+    let point = new WKT().readGeometry(data["pointWkt"])
+    let feature = new Feature({
+        geometry: point
+    });
+    feature.set("nodeType","area");
+    feature.set("isPoint",1)
+    for(let key in data){
+        if(filterWktProp(data,key)){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+export const newPolymerFeature = (data)=>{
+    let geom = new WKT().readGeometry(data["geom"])
+    let feature = new Feature({
+        geometry: geom
+    });
+    feature.set("nodeType","polymer");
+    feature.setId(data.id)
+    for(let key in data){
+        if(filterWktProp(data,key)){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+export const newRegionFeature = (data)=>{
+    let geom = new WKT().readGeometry(data["regionWkt"])
+    let feature = new Feature({
+        geometry: geom
+    });
+    feature.set("nodeType","region");
+    feature.setId(data.id)
+    for(let key in data){
+        if(filterWktProp(data,key)){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+
+export const newPoint = (data,dataName)=>{
+    let point = new WKT().readGeometry(data[dataName])
+    let feature = new Feature({
+        geometry: point
+    });
+    feature.setId(data.id)
+    feature.set("nodeType","tree")
+    for(let key in data){
+        if(filterWktProp(data,key)){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+/**
+ * 按treeId分组
+ * @param data
+ * @returns {[]}
+ */
+export const groupByTreeId = (data) => {
+    let res = []
+    let cur = {treeId:-1,data:[]}
+    for(let item of data){
+        if(cur.treeId != item.treeId){
+            cur = {treeId: item.treeId, data:[]}
+            res.push(cur)
+        }
+        cur.data.push(item)
+    }
+    for(let item of res){
+        item.data.sort((a,b) => {
+            return Date.parse(b.uploadDate) - Date.parse(a.uploadDate)
+        })
+    }
+    return res
+}
+
+/**
+ * 按createDate分组
+ * @param data
+ * @returns {[]}
+ */
+export const groupByCreateDate = (data) => {
+    let res = {}
+    let arr = []
+    for(let item of data){
+        if(res[item.uploadDate]){
+            res[item.uploadDate].push(item)
+        }else{
+            res[item.uploadDate] = [item]
+            arr.push({uploadDate:item.uploadDate, data: res[item.uploadDate]})
+        }
+    }
+    arr.sort((a,b) => {
+        return Date.parse(b.uploadDate) - Date.parse(a.uploadDate)
+    })
+    return arr
+}
+
+
+export const setPeriodAttr = (periodMap, data) => {
+    for(let item of data){
+        item["attrs"] = []
+        let periodObj = periodMap[item.periodId]
+        item["periodName"] = periodObj.name
+        for(let i=0;i<periodObj.attrFields.length;i++){
+            item["attrs"].push({name:periodObj.attrNames[i],field:periodObj.attrFields[i]})
+        }
+    }
+}
+
+
+export const bboxToFeature = ( x1,  y1,  x2,  y2) => {
+    let wkt = "POLYGON (("+x1+" "+y1+", "+x1+" "+y2+", "+x2+" "+y2+", "+x2+" "+y1+", "+x1+" "+y1+"))";
+    let feature = new Feature({
+        geometry: new WKT().readGeometry(wkt)
+    });
+    return feature
+}
+/**
+ * 红框样式
+ * @returns {Style}
+ */
+export const redBoxStyle = ()=>{
+    const fill = new Fill({
+        color: 'rgba(255,255,255,0.1)',
+    });
+    const stroke = new Stroke({
+        color: '#f5024f',
+        width: 0.5,
+    });
+    let  style = new Style({
+        fill: fill,
+        stroke: stroke,
+    })
+    return style
+}
+
+export const getAreaByWkt = (wkt)=>{
+    let area = 0;
+    let geom = new WKT().readGeometry(wkt)
+    // 获取图层上的Polygon,转成geoJson用于回显
+    geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:3857"));
+    let areaItem = getArea(geom);
+    areaItem = (areaItem + areaItem / 2) / 1000;
+    area = areaItem;
+    return area.toFixed(2)
+}
+
+

+ 78 - 0
src/utils/ol-map/Bounds.js

@@ -0,0 +1,78 @@
+import * as extent  from "ol/extent"
+import LngLat from "./LngLat"
+/**
+ * @description KMap.Bounds 经纬度矩形范围类
+ */
+class Bounds{
+  /**
+   * @param {KMap.LngLat} southWest 矩形范围西南角坐标,必填,格式new KMap.LngLat()
+   * @param {KMap.LngLat} northEast 矩形范围东北角坐标,必填,格式new KMap.LngLat()
+   */
+  constructor(southWest,northEast){
+		const vm = this
+		vm.southWest = southWest
+		vm.northEast = northEast
+    let mapSouthWest = [southWest.getLng(),southWest.getLat()]
+    let mapNorthEast = [northEast.getLng(),northEast.getLat()]
+    let bounds = new extent.boundingExtent([mapSouthWest,mapNorthEast])
+    this.bounds = bounds
+  }
+
+	/**
+	 * @description 判断指定点坐标是否在矩形范围内
+	 * @param {KMap.LngLat} point 经纬度点,KMap.LngLat格式,必填
+	 * @returns {boolean}在矩形范围内返回true,否则返回false
+	 */
+  contains(point) {
+		const vm = this
+		if( (point.getLng() >= vm.southWest.getLng() && point.getLng() <= vm.northEast.getLng())
+			&& (point.getLat() >= vm.southWest.getLat() && point.getLat() <= vm.northEast.getLat()) )
+		{
+			return true
+		}else{
+			return false
+		}
+  }
+
+  /**
+   * @description 获取中心点坐标
+   * @returns {KMap.LngLat}中心点坐标,KMap.LngLat格式
+   */
+	getCenter() {
+		const vm = this
+		let lng = (vm.southWest.getLng() + vm.northEast.getLng()) / 2
+		let lat = (vm.southWest.getLat() + vm.northEast.getLat()) / 2
+		var center = new LngLat(lng,lat)
+		return center
+	}
+
+	/**
+	 * @description 获取西南角坐标
+	 * @returns {KMap.LngLat}西南角坐标,KMap.LngLat格式
+	 */
+	getSouthWest() {
+		const vm = this
+		return vm.southWest
+	}
+
+	/**
+	 * @description 获取东北角坐标
+	 * @returns {KMap.LngLat}东北角坐标,KMap.LngLat格式
+	 */
+	getNorthEast() {
+		const vm = this
+		return vm.northEast
+	}
+
+	/**
+	 * @description 以字符串形式返回地物对象的矩形范围
+	 * @returns {String}西南角经度、西南角纬度:东北角经度、东北角纬度
+	 */
+  toString() {
+		const vm = this
+        return vm.southWest.getLng() + "," + vm.southWest.getLat() + ":"
+               + vm.northEast.getLng() + "," + vm.northEast.getLat()
+  }
+}
+
+export default Bounds

+ 63 - 0
src/utils/ol-map/Check.js

@@ -0,0 +1,63 @@
+import * as Info from './Info'
+class Check{
+  static lngLat(lng,lat){
+    let msg = ''
+    let result = false
+    if(lat == null || lng == null || lat == undefined || lng == undefined){
+      msg = Check.addHeader("经纬度不能为null")
+      return Check.message(msg,result)
+    }
+    if(Check.isNumber(lat) || Check.isNumber(lng)){
+      msg = Check.addHeader("经纬度应为数字")
+      return Check.message(msg,result)
+    }
+    if(lat<-90 || lat>90){
+      msg = Check.addHeader("纬度lat应该大于-90小于90")
+      return Check.message(msg,result)
+    }
+    if(lng<-180 || lng>180){
+      msg = Check.addHeader("经度lng应该大于-180小于180")
+      return  Check.message(msg,result)
+    }
+    if(msg == ''){
+      result = true
+    }
+    return Check.message(msg,result)
+  }
+
+  static isNumber(str){
+    
+    if(str == null || undefined){
+      return false
+    }
+    
+    if((typeof str=='string')&&str.constructor==String){
+      return false
+    }
+    
+    if(!isNaN(str)){
+      return false
+    }
+    return true
+  }
+  static notEmpty(name,str){
+    let result = false;
+    if(str == null || undefined){
+      result =  false
+    }
+    
+    if((typeof str=='string') && str.constructor==String && str !=''){
+      result =  true
+    }else{
+      result =  false
+    }
+    return Check.message(Check.addHeader(name+"必须为字符串且不能为空"),result);
+  }
+  static addHeader(str){
+    return Info.version+":"+str
+  }
+  static message(msg,isPass){
+    return {msg:msg,isPass:isPass}
+  }
+}
+export default Check

+ 143 - 0
src/utils/ol-map/Circle.js

@@ -0,0 +1,143 @@
+import Feature from 'ol/Feature'
+import Fill from 'ol/style/Fill'
+import Stroke from 'ol/style/Stroke'
+import Style from 'ol/style/Style'
+import * as olExtent from 'ol/extent'
+import * as proj from 'ol/proj'
+import OLCircle from 'ol/geom/Circle'
+import KBaseObject from './KBaseObject'
+import Common from './Common'
+
+/**
+ * @description KMap.Circle 圆标记
+ */
+class Circle extends KBaseObject{
+  /**
+   * @description KMap.Circle 构造函数
+   * @param {JSON Object} lng 必填 param.lat 必填 param
+   */
+  /**
+   * Creates an instance of Circle.
+   * @param {number} lng 经度 必填
+   * @param {number} lat 纬度 必填
+   * @param {number} radius 圆半径 必填
+   * @param {JSON} param param.storkeWidth圆外线宽度 选填;
+   * param.strokeColor圆外线颜色 选填;
+   * param.background 圆背景颜色 选填。
+   * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+   * @memberof Circle
+   */
+  constructor(lng,lat,radius,param,mapInstance = null){
+    super(mapInstance)
+    const vm = this
+    Common.checkLngLat(lng,lat)
+    vm.lng = lng
+    vm.lat = lat
+    vm.radius = radius
+    //创建默认圆标记图层
+    vm.source = vm.mapInstance.polygonLayer.getSource()
+    //构造函数和对象参数
+    vm.style = vm.initStyle(param)
+    vm.circle = vm.initFeature()
+    vm.circle.setStyle(vm.style)
+    vm.source.addFeature(vm.circle)
+  }
+  /**
+   * @description 初始化圆样式 供内部使用
+   * @param {JSON} param param.storkeWidth圆外线宽度 选填;
+   * param.strokeColor圆外线颜色 选填;
+   * param.background 圆背景颜色 选填。
+   * @return {Style} 
+   * @memberof Circle
+  */
+  initStyle(param){
+    const vm = this
+    let background = (param != undefined && param.background != undefined )? param.background : "rgba(255,0,0,.5)"
+    let strokeWidth = (param != undefined && param.strokeWidth != undefined )? param.strokeWidth : 1
+    let strokeColor = (param != undefined && param.strokeColor != undefined )? param.strokeColor : "rgba(255,0,0,0)"
+    let style = new Style({
+      fill: new Fill({ //矢量图层填充颜色,以及透明度
+        color: background
+      }),
+      stroke: new Stroke({ //边界样式
+        color: strokeColor,
+        width: strokeWidth
+      })
+    })
+    return style
+  }
+  
+  /**
+   * @description 初始化Feature 内部函数
+   * @return {Feature} 
+   * @memberof Circle
+   */
+  initFeature(){
+    const vm = this
+    vm.center = proj.fromLonLat([vm.lng,vm.lat])
+    var circle = new OLCircle(vm.center, vm.radius,'XY')
+    let feature = new Feature({
+      geometry: circle
+    })
+    return feature
+  }
+  
+  /**
+   * @description 修改半径
+   * @param {number} newRadius 新的半径值
+   * @memberof Circle
+   */
+  setRadius(newRadius) {
+    const vm = this
+    vm.radius = newRadius
+    vm.circle.getGeometry().setRadius(newRadius)
+	}
+	
+  /**
+  * @description 显示圆标记
+  * @memberof Circle
+  */
+  show() {
+    const vm = this
+    if(!vm.source.hasFeature(vm.circle)){
+      vm.source.addFeature(vm.circle)
+    }
+	}
+
+	/**
+   * @description 隐藏圆标记
+   * @memberof Circle
+   */
+  hide() {
+    const vm = this
+    if(vm.source.hasFeature(vm.circle)){
+      vm.source.removeFeature(vm.circle)
+    }
+	}
+  
+	/**
+   * @description 删除圆标记
+   * @memberof Circle
+   */
+  remove() {
+    const vm = this
+    if(vm.source.hasFeature(vm.circle)){
+      vm.source.removeFeature(vm.circle)
+    }
+	}
+  
+  /**
+	 * 地图视角缩放到圆标记范围
+	 * @param duration 动画持续时间(单位:毫秒) 选填,默认0毫秒
+	 */
+	zoomToExtent(duration) {
+    const vm = this
+		duration = (duration)? duration : 0
+    let extentBound = vm.circle.getGeometry().extent_
+		vm.map.getView().fit(extentBound,{
+			duration: duration
+		})
+	}
+}
+
+export default Circle

+ 145 - 0
src/utils/ol-map/Common.js

@@ -0,0 +1,145 @@
+import Size from './Size'
+import Pixel from './Pixel'
+import LngLat from './LngLat'
+import Bounds from './Bounds'
+import Check from './Check'
+import * as olProj from 'ol/proj';
+/**
+ * @description KMap.Common类 通用静态方法
+ */
+class Common{
+	static ShowLevel = [1,22]
+	/**
+	 *@description 底图Zoom限制
+	*/
+	static BaseLayerZoom = [1,22]
+	/**
+	 * @description 利通地图像素转OpenLayers地图像素
+	 * @param {KMap.Pixel} pixel KMap.Pixel格式的像素,必填
+	 * @returns {Array} OpenLayers格式的像素,包含两个元素的数组[x,y]
+	*/
+	static KMapPixel2MapPixel(pixel){
+		let mapPixel = [pixel.getX(),pixel.getY()]
+		return mapPixel
+	}
+
+	/**
+	 * @description OpenLayers地图像素转利通地图像素
+	 * @param {Array} pixel OpenLayers格式的像素,包含两个元素的数组[x,y],必填
+	 * @returns {KMap.Pixel} KMap.Pixel格式的像素
+	*/
+	static MapPixel2KMapPixel(pixel) {
+		let ltPixel = new Pixel(pixel[0], pixel[1])
+		return ltPixel
+	}
+
+	/**
+	 * @description 利通地图像素尺寸转OpenLayers地图像素尺寸
+	 * @param {KMap.Size} size KMap.Size格式的尺寸,必填
+	 * @returns {Array} OpenLayers地图像素尺寸,包含两个元素的数组[width,height]
+	*/
+	static KMapSize2MapSize(size) {
+		let mapSize = [size.getWidth(), size.getHeight()]
+		return mapSize
+	}
+
+	/**
+	 * @description OpenLayers地图像素尺寸转利通地图像素尺寸
+	 * @param {Array} size OpenLayers地图像素尺寸,包含两个元素的数组[width,height],必填
+	 * @returns {KMap.Size} 格式的尺寸
+	*/
+	static MapSize2KMapSize(size) {
+		let ltSize = new Size(size[0], size[1])
+		return ltSize
+	}
+
+	/**
+	 * @description 利通地图经纬度转OpenLayers地图经纬度
+	 * @param {KMap.LngLat} lnglat KMap.LngLat格式的经纬度,必填
+	 * @returns {Array} OpenLayers的经纬度格式,包含两个元素的数组[lng,lat]
+	*/
+	static KMapLngLat2MapLngLat(lnglat) {
+		let alnglat = [lnglat.getLng(),lnglat.getLat()]
+		return alnglat
+	}
+
+	/**
+	 * @description OpenLayers地图经纬度转利通地图经纬度
+	 * @param {Array} lnglat OpenLayers的经纬度格式,包含两个元素的数组[lng,lat],必填
+	 * @returns {KMap.LngLat} KMap.LngLat格式的经纬度
+	*/
+	static MapLngLat2KMapLngLat(lnglat) {
+		let ltlnglat = new LngLat(lnglat[0], lnglat[1])
+		return ltlnglat
+	}
+
+	/**
+	 * @description 利通地图经纬度矩形范围转OpenLayers地图经纬度矩形范围
+	 * @param {KMap.Bounds} bounds KMap.Bounds对象,必填
+	 * @returns {Array} 西南角经度、西南角纬度、东北角经度、东北角纬度构成的数组
+	*/
+	static KMapBounds2MapBounds(bounds) {
+		let array = new Array()
+		let southWest = bounds.getSouthWest()
+		let northEast = bounds.getNorthEast()
+		array.push(southWest.getLng())
+		array.push(southWest.getLat())
+		array.push(northEast.getLng())
+		array.push(northEast.getLat())
+		return array
+	}
+
+	/**
+	 * @description OpenLayers地图经纬度矩形范围转利通地图经纬度范围
+	 * @param {Array} bounds 西南角经度、西南角纬度、东北角经度、东北角纬度构成的数组,必填
+	 * @returns {KMap.Bounds} KMap.Bounds类型对象
+	*/
+	static MapBounds2KMapBounds(bounds) {
+		let southWest = [bounds[0],bounds[1]]
+		let northEast = [bounds[2],bounds[3]]
+		southWest = new LngLat(southWest[0],southWest[1])
+		northEast = new LngLat(northEast[0],northEast[1])
+		bounds = new Bounds(southWest,northEast)
+		return bounds
+	}
+	static toWGS84LngLat(map,coordinate){
+		map.getCoor
+		return olProj.transform(coordinate,map.getProjection(),"EPSG:4326")
+	}
+
+	/**
+	 * @description 扩展JSON对象属性
+	 * @param {JSON} des 目标JSON对象,必填
+	 * @param {JSON} src 源JSON对象,必填
+	 * @param {boolean} override 是否覆盖属性,选填
+	 * @returns {JSON} 目标JSON对象
+	*/
+	static extend(des, src, override){
+		if(src instanceof Array){
+			for(let i = 0, len = src.length; i < len; i++)
+				Common.extend(des, src[i], override)
+		}
+		for( let i in src){
+			if(override || !(i in des)){
+				des[i] = src[i]
+			}
+		}
+		return des
+	}
+	static checkLngLat(lng,lat){
+		let info = Check.lngLat(lng,lat)
+    if(!info.isPass){
+      throw new Error(info.msg)
+    }
+	}
+
+	static notEmpty(name,str){
+		let info = Check.notEmpty(name,str)
+    if(!info.isPass){
+      throw new Error(info.msg)
+    }
+	}
+
+}
+
+export default Common

+ 37 - 0
src/utils/ol-map/CustomLayer.js

@@ -0,0 +1,37 @@
+import Tile from 'ol/layer/WebGLTile'
+import XYZ from 'ol/source/XYZ'
+import KBaseObject from './KBaseObject'
+/**
+ * @description KMap.CustomLayer 自定义离线切片图层类
+ */
+class CustomLayer extends KBaseObject{
+  /**
+   * @description 离线切片图层类
+   * @param {string} layerUrl 切片url地址
+  */
+  constructor(layerUrl,mapInstance = null){
+    super(mapInstance)
+    const vm = this
+    let layer = new Tile()
+    let source = new XYZ({
+      url : layerUrl
+    })
+    layer.setSource(source)
+    vm.layer = layer
+    vm.map.addLayer(vm.layer)
+  }
+  hide(){
+    const vm = this
+    vm.layer.setVisible(false)
+  }
+  show(){
+    const vm = this
+    vm.layer.setVisible(true)
+  }
+  remove(){
+    const vm = this
+    vm.map.removeLayer(vm.layer)
+  }
+}
+
+export default CustomLayer

+ 9 - 0
src/utils/ol-map/Enum.js

@@ -0,0 +1,9 @@
+/**
+ * @description KMap.LayerTypeEnum 底图类型枚举
+*/
+export const LayerTypeEnum = {
+  'ARCGISTile':"ARCGISTile",
+  'GaoDeTile':"GaoDeTile",
+  "WGS84Tile":"WGS84Tile",
+  "BaiDuTile":"BaiDuTile"
+}

+ 19 - 0
src/utils/ol-map/Info.js

@@ -0,0 +1,19 @@
+/**
+ * @module KMap/Info
+ * @description api基本信息
+ */
+
+/**
+ * @const
+ * @version
+ * @type {string}
+ * @description 版本
+ */
+export const version = "KMap 1.0 Base On OpenLayers6.14.1"
+
+/**
+ * @const
+ * @type {string}
+ * @description 原生引擎名称
+ */
+export const srcApiName = "OpenLayers6.14.1"

+ 191 - 0
src/utils/ol-map/InfoWindow.js

@@ -0,0 +1,191 @@
+import Common from './Common'
+import * as proj from 'ol/proj'
+import KBaseObject from './KBaseObject'
+/**
+ * @description KMap.InfoWindow 弹窗类
+ */
+class InfoWindow extends KBaseObject{
+	/**
+	 * Creates an instance of InfoWindow.
+	 * @param {*} param
+	 * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @memberof InfoWindow
+	 */
+	constructor(param,mapInstance = null){
+    let {content,position,offsetX,offsetY,type} = param
+		super(mapInstance)
+		const vm = this
+		if(!offsetX){
+			offsetX = 0
+		}
+		if(!offsetY){
+			offsetY = 0
+		}
+    this.initInfoWindow(content,position,offsetX,offsetY,type)
+  }
+
+  initInfoWindow(content,position,offsetX,offsetY,type){
+    const vm = this
+    if(type == "click") {
+      vm.infoWindow = vm.mapInstance.infoWindow_click
+      vm.infoWindowBox = document.getElementById("infowindow-click")
+    }
+    else if(type == "mousemove") {
+      vm.infoWindow = vm.mapInstance.infoWindow_move
+      vm.infoWindowBox = document.getElementById("infowindow-move")
+      vm.infoWindowBox.style.zIndex = 1
+    }
+    vm.infoWindowBox.style.position = "absolute"
+    //获取弹窗
+    vm.InfoWindow = vm.infoWindowBox
+    vm.content = content.outerHTML
+    vm.position = position
+    vm.offsetX = offsetX
+    vm.offsetY = offsetY
+  }
+
+  /**
+	 * 在地图指定位置打开弹窗
+	 * @param clearState 打开新弹窗是否关闭其他弹窗(默认true),选填
+	*/
+	open(clearState) {
+    const vm = this
+		clearState = (clearState!=undefined)? clearState : true
+		if(clearState) {
+			vm.mapInstance.infoWindow_click.setPosition(undefined)
+			vm.mapInstance.infoWindow_move.setPosition(undefined)
+		} //清空地图弹窗
+
+		if(vm.content == undefined || vm.content == ""){return}
+		vm.infoWindowBox.innerHTML = vm.content
+    let vmPosition = proj.fromLonLat(Common.KMapLngLat2MapLngLat(vm.position))
+
+		vm.infoWindow.setPosition(vmPosition)
+		var left = -(vm.infoWindowBox.offsetWidth)/2 + vm.offsetX
+		var top = -(vm.infoWindowBox.offsetHeight) + vm.offsetY
+		vm.infoWindowBox.style.left = left + "px"
+		vm.infoWindowBox.style.top = top + "px"
+
+		//设置全局弹窗范围
+		var pixel = vm.mapInstance.lngLatToContainer(vm.position)
+		vm.mapInstance.infoWindowPixel = new Array()
+		vm.mapInstance.infoWindowPixel[0] = pixel.getX() + left
+		vm.mapInstance.infoWindowPixel[1] = pixel.getY() + top
+		vm.mapInstance.infoWindowPixel[2] = pixel.getX() + left + vm.infoWindowBox.offsetWidth
+		vm.mapInstance.infoWindowPixel[3] = pixel.getY() + top + vm.infoWindowBox.offsetHeight
+		
+    //弹窗超出屏幕位置矫正
+		var mapCenter = vm.mapInstance.getCenter()
+    
+    //地图中心
+		mapCenter = vm.mapInstance.lngLatToContainer(mapCenter)
+    
+    //将利通地图像素转换成OL地图像素
+		mapCenter = Common.KMapPixel2MapPixel(mapCenter)
+		
+    //弹窗坐标初始坐标
+    pixel = vm.mapInstance.lngLatToContainer(vm.position)
+		
+    //将利通地图像素转换成OL地图像素
+    pixel = Common.KMapPixel2MapPixel(pixel)
+		left = -(vm.infoWindowBox.offsetWidth)/2 + vm.offsetX
+		top = -(vm.infoWindowBox.offsetHeight) + vm.offsetY
+    
+    //弹窗左上角坐标
+		let infoWindow_left = [pixel[0]+left, pixel[1]+top]
+    
+    //弹窗右上角坐标
+		let infoWindow_right = [infoWindow_left[0]+vm.infoWindowBox.offsetWidth, pixel[1]+top]
+		if(infoWindow_left[1] < 0) {
+			mapCenter[1] -= -(infoWindow_left[1])
+			vm.mapInstance.infoWindowPixel[1] -= infoWindow_left[1]
+			vm.mapInstance.infoWindowPixel[3] -= infoWindow_left[1]
+		}
+
+		if(infoWindow_left[0] < 0) {
+			mapCenter[0] -= -(infoWindow_left[0]);
+			vm.mapInstance.infoWindowPixel[0] -= infoWindow_left[0]
+			vm.mapInstance.infoWindowPixel[2] -= infoWindow_left[0]
+		}
+    
+    //容器宽度
+		var containerWidth = vm.mapInstance.getTarget().offsetWidth
+		if(infoWindow_left[0] > 0 && infoWindow_right[0] > containerWidth) {
+			mapCenter[0] += (infoWindow_right[0]-containerWidth)
+		}
+    
+    //将OL地图像素转换成利通地图像素
+		mapCenter = Common.MapPixel2KMapPixel(mapCenter)
+		mapCenter = vm.mapInstance.containerToLngLat(mapCenter)
+		vm.mapInstance.panTo(mapCenter)
+	}
+
+	/**
+	 *关闭弹窗
+	*/
+	close() {
+    const vm = this
+		vm.infoWindow.setPosition(undefined)
+		vm.mapInstance.infoWindowPixel = null
+	}
+
+	/**
+	 *获取弹窗是否打开--暂无该方法
+	*/
+	getIsOpen() {}
+
+	/**
+	 * 获取弹窗内容
+	 * @returns 弹窗内容
+	*/
+	getContent() {
+    const vm = this
+		let content = vm.infoWindowBox.innerHTML
+		return content
+	}
+
+	/**
+	 * 设置弹窗内容--暂无该方法
+	*/
+	setContent(content) {
+    const vm = this
+		vm.infoWindowBox.innerHTML = content.outerHTML
+	}
+
+	/**
+	 * 获取弹窗坐标
+	 * @returns KMap.LngLat格式的弹窗坐标
+	*/
+	getPosition() {
+    const vm = this
+		let position = vm.infoWindow.getPosition()
+		position = proj.toLonLat(position)
+		return Common.MapLngLat2KMapLngLat(position)
+	}
+
+	/**
+	* 设置弹窗坐标--暂无该方法
+	*/
+	setPosition(position) {
+    const vm = this
+		vm.infoWindow.setPosition(proj.fromLonLat(Common.KMapLngLat2MapLngLat(position)))
+	}
+
+	/**
+	* 获取弹窗大小
+	* @returns 弹窗大小数组,[width,height]
+	*/
+	getSize() {
+    const vm = this
+		let size = {}
+		size.width = (vm.infoWindowBox.style.width)? vm.infoWindowBox.style.width : "auto"
+		size.height = (vm.infoWindowBox.style.height)? vm.infoWindowBox.style.height : "auto"
+		return size
+	}
+
+	/**
+	* 设置弹窗大小--暂无该方法
+	*/
+	setSize(width,height) {}
+}
+export default InfoWindow

+ 11 - 0
src/utils/ol-map/KBaseObject.js

@@ -0,0 +1,11 @@
+import Map from  './Map'
+class KBaseObject{
+  constructor(mapInstance){
+    const vm = this
+		//利通map实例
+		vm.mapInstance = mapInstance || Map.Instance
+		//获取ol map对象
+		vm.map = vm.mapInstance.map
+  }
+}
+export default KBaseObject

+ 38 - 0
src/utils/ol-map/KMap.js

@@ -0,0 +1,38 @@
+import * as Info from './Info'
+import Map from './Map'
+import Common from './Common'
+import Bounds from './Bounds'
+import Marker from './Marker'
+import Pixel from './Pixel'
+import Size from './Size'
+import LngLat from './LngLat'
+import InfoWindow from './InfoWindow'
+import Polyline from './Polyline'
+import Polygon from './Polygon'
+import Circle from './Circle'
+import CustomLayer from './CustomLayer'
+import WMTSLayer from './WMTSLayer'
+import VectorLayer from './VectorLayer'
+import XYZLayer from './XYZLayer'
+import VTLayer from './VTLayer'
+import VectorStyle from './VectorStyle'
+export {
+  Info,
+  Map,
+  Common,
+  Bounds,
+  Marker,
+  Polyline,
+  Polygon,
+  Circle,
+  Pixel,
+  Size,
+  LngLat,
+  InfoWindow,
+  CustomLayer,
+  WMTSLayer,
+  XYZLayer,
+  VectorLayer,
+  VTLayer,
+  VectorStyle,
+}

+ 313 - 0
src/utils/ol-map/Label.js

@@ -0,0 +1,313 @@
+import Common from './Common'
+import Feature from 'ol/Feature'
+import Point from 'ol/geom/Point'
+import Style from 'ol/style/Style'
+import Fill from 'ol/style/Fill'
+import Stroke from 'ol/style/Stroke'
+import Text from 'ol/style/Text'
+import KBaseObject from './KBaseObject'
+import * as proj from 'ol/proj'
+/**
+ * @description KMap.Label 文本标记类
+*/
+class Label extends KBaseObject{
+	/**
+	 * Creates an instance of Label.
+	 * @param {*} param
+	 * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @memberof Label
+	 */
+	constructor(param,options,mapInstance = null){
+		super(mapInstance)
+		const vm = this
+    let lng = (param.lng != undefined)? Number(param.lng) : vm.mapInstance.getCenter()[0]
+    let lat = (param.lat != undefined)? Number(param.lat) : vm.mapInstance.getCenter()[1]
+    let text = (param.text != undefined)? param.text : ""
+    let font = (param.font != undefined)? param.font : '13px sans-serif'
+    let offsetX = (param.offsetX != undefined)? param.offsetX : 0
+    let offsetY = (param.offsetY != undefined)? param.offsetY : 0
+    let rotation = (param.rotation != undefined)? param.rotation : 0
+    let fill = (param.fill != undefined)? param.fill : "black"
+    let backgroundColor = (param.backgroundColor != undefined)? param.backgroundColor : "#fff"
+    let backgroundStroke = (param.backgroundStroke != undefined)? param.backgroundStroke : "black"
+    let padding = (param.padding != undefined)? param.padding : [0,5,0,5]
+    Common.checkLngLat(lng,lat)
+		//创建文本标记
+    vm.label = new Feature({
+      geometry: new Point(proj.fromLonLat([lng,lat])),
+      properties: null
+    })
+    vm.style = new Style({
+      text: new Text({
+        text: text,
+        font: font,
+        offsetX: offsetX,
+        offsetY: offsetY,
+        rotateWithView: false,//文本是否可旋转
+        rotation: rotation,
+        fill: new Fill({
+            color: fill
+        }),
+        backgroundFill: new Fill({
+            color: backgroundColor
+        }),
+        backgroundStroke: new Stroke({
+            color: backgroundStroke
+        }),
+        padding: padding
+      }),
+      zIndex: 0
+    })
+    vm.label.setStyle(vm.style)
+    vm.text = vm.style.getText() //文本标记内容
+    vm.point = vm.label.getGeometry() //文本标记对象
+    vm.source = vm.mapInstance.labelLayer.getSource() //地图文本标记图层
+	
+	if(options && options.source){
+		vm.source = source;
+	}
+	//文本标记添加到地图
+    vm.source.addFeature(vm.label)
+  }
+
+  /**
+	 * 获取id
+	 * @returns id
+	*/
+	getId() {
+		const vm = this
+		let id = vm.label.getId()
+		return id
+	}
+
+	/**
+	 * 设置id
+	 * @param id 必填
+	 */
+	setId(id) {
+		const vm = this
+		vm.label.setId(id)
+	}
+
+	/**
+	 * 显示文本标记
+	 */
+	show() {
+		const vm = this
+		if(!vm.source.hasFeature(label)) {
+			vm.source.addFeature(label)
+		}
+	}
+
+	/**
+	 * 隐藏文本标记
+	*/
+	hide() {
+		const vm = this
+		if(vm.source.hasFeature(label)) {
+			vm.source.removeFeature(label)
+		}
+	}
+
+	/**
+	 * 删除文本标记
+	 */
+	remove() {
+		const vm = this
+		if(vm.source.hasFeature(label)) {
+			vm.source.removeFeature(label)
+		}
+	}
+
+	/**
+	 * 获取文本标记坐标
+	 * @param 返回KMap.LngLat格式的经纬度
+	*/
+	getPosition() {
+		const vm = this
+		let position = new proj.toLonLat(vm.point.getCoordinates())
+		position = Common.MapLngLat2KMapLngLat(position)
+		return position
+	}
+
+	/**
+	 * 设置文本标记坐标
+	 * @param KMap.LngLat格式的文本标记经纬度坐标,必填
+	*/
+	setPosition(lnglat){
+		lnglat = Common.KMapLngLat2MapLngLat(lnglat)
+		let position = proj.fromLonLat(lnglat)
+		point.setCoordinates(position)
+	}
+
+	/**
+	 * 获取文本标记缩放值
+	 * @returns 文本标记缩放值
+	 */
+	getScale(){
+		const vm = this
+		let scale = vm.text.getScale()
+		return scale
+	}
+
+	/**
+	 * 设置文本标记缩放值
+	 * @param scale 缩放值,必填
+	*/
+	setScale(scale) {
+		const vm = this
+		vm.text.setScale(scale)
+	}
+
+	/**
+	 * 获取文本标记X轴方向偏移量
+	 * @returns X轴方向偏移量
+	*/
+	getOffsetX(){
+		const vm = this
+		let offsetX = vm.text.offsetX_
+		return offsetX
+	}
+
+	/**
+	 * 设置文本标记X轴方向偏移
+	 * @param X轴方向偏移(正向右,负向左),必填
+	*/
+	setOffsetX(offsetX) {
+		const vm = this
+		vm.text.offsetX_ = offsetX
+	}
+
+	/**
+	 * 获取文本标记Y轴方向偏移量
+	 * @returns Y轴方向偏移量
+	*/
+	getOffsetY() {
+		const vm = this
+		let offsetY = vm.text.offsetY_
+		return offsetY
+	}
+
+	/**
+	 * 设置文本标记Y轴方向偏移
+	 * @param offsetY Y轴方向偏移(正向下,负向上),必填
+	*/
+	setOffsetY(offsetY) {
+		const vm = this
+		vm.text.offsetY_ = offsetY
+	}
+
+	/**
+	 * 获取文本标记旋转弧度
+	 * @returns 文本标记旋转弧度
+	*/
+	getAngle(){
+		const vm = this
+		let rotation = vm.text.rotation_
+		return rotation
+	}
+
+	/**
+	 * 设置文本标记旋转弧度
+	 * @param rotation 文本标记旋转弧度,必填
+	*/
+	setAngle(rotation){
+		const vm = this
+		vm.text.rotation_ = rotation
+	}
+
+	/**
+	 * 获取文本标记叠加顺序
+	 * @returns 文本标记叠加顺序
+	*/
+	getZIndex() {
+		const vm = this
+		let index = vm.style.getZIndex()
+		return index
+	}
+
+	
+  /**
+	 * 设置文本标记叠加顺序
+	 * @param index index值较大的点标记显示在上方 ,值相同时后创建的点标记显示在上方,必填
+	*/
+	setZIndex(index) {
+		const vm = this
+		vm.style.setZIndex(index)
+	}
+	
+  /**
+	 * 获取用户自定义属性
+	 * @returns 用户自定义属性(支持数字,字符串,json)
+	*/
+	getExtData(){
+		const vm = this
+		let extData = vm.label.values_.properties
+		return extData
+	}
+	
+  /**
+	 * 设置用户自定义属性
+	 * @param extData 用户自定义属性(支持数字,字符串,json),必填
+	*/
+	setExtData(extData){
+		const vm = this
+		extData = (extData)? extData :null
+		if(extData != null) {
+			vm.label.values_.properties = extData
+		}
+	}
+
+	/**
+	 * 注册文本标记事件
+	 * @param eventName 鼠标事件
+	 * "click":鼠标点击事件 "mousemove":鼠标悬停事件
+	 * @param callback 选中文本标记时触发函数
+	*/
+	on(eventName,callback) {
+    const vm = this
+		vm.label.on(eventName,function(e) {
+			if(eventName == "mousemove"){
+				if(vm.label.ol_uid != vm.mapInstance.InfowindowmoveUID)
+				{
+					vm.mapInstance.InfowindowmoveUID = vm.label.ol_uid
+					callback(e)
+				}
+			}
+			else{
+				callback(e)
+			}
+		})
+	}
+	
+  /**
+	 * 注销文本标记事件(暂无该方法)
+	*/
+	off(eventName,handler) {}
+	
+  /**
+	 * 获取文本标记地图对象
+	 * @returns 地图对象
+	*/
+	getMap() {
+    const vm = this
+		let map = vm.map
+		return map
+	}
+
+	/**
+	 * 设置文本标记地图对象,传入null时,移除点标记,
+	 * 建议不使用此API,使用remove方法
+	 * @param map 传入null,移除点标记
+	*/
+	setMap(map) {
+    const vm = this
+		map = (vm.map)? vm.map : null
+		if(map == null) {
+			if(vm.source.hasFeature(vm.abel)) {
+				vm.source.removeFeature(vm.label)
+			}
+		}
+	}
+}
+export default Label

+ 78 - 0
src/utils/ol-map/LngLat.js

@@ -0,0 +1,78 @@
+import Common from "./Common"
+/**
+ * @description KMap.LngLat 经纬度
+*/
+class LngLat{
+  /**
+   * @param {number} lng 纬度
+   * @param {number} lat 经度
+   * @constructor
+   */
+  constructor(lng,lat){
+    Common.checkLngLat(lng,lat)
+    let maplnglat = [Number(lng),Number(lat)]
+    this.lngLat = maplnglat
+  }
+
+
+	/**
+	 * @description 当前经纬度坐标值经度移动w,纬度移动s,得到新的坐标。 经度向右移为正值,纬度向上移为正值,单位为°
+	 * @param {number} w 经度移动量
+	 * @param {number} s 纬度移动量
+	 */
+  offset(w, s) {
+    let lng = this.lngLat[0]+w
+    let lat = this.lngLat[1]+s
+    return new LngLat(lng,lat)
+  }
+
+  /**
+   * @description 当前经纬度和传入经纬度之间的地面距离,单位为米----暂无该方法
+   * @param {number} lnglat 经纬度
+   */
+  distance(lnglat) {
+    return null
+  }
+
+  /**
+   * @description 获取经度
+   * @returns {number} 返回经度
+   */
+	getLng() {
+		let lng = this.lngLat[0]
+		return lng
+	}
+
+	/**
+	 * @description 获取纬度
+	 * @returns {number} 返回纬度
+	 */
+	getLat() {
+		let lat = this.lngLat[1]
+		return lat
+	}
+
+	/**
+	 * @description 判断当前坐标对象与传入坐标对象是否相等
+	 * @param {KMap.LngLat} lnglat 格式的经纬度,必填
+	 * @returns {boolean} 坐标相等返回true,坐标不相等返回false
+	 */
+  equals(lnglat) {
+    if(lnglat.getLng() == this.lngLat[0] && lnglat.getLat() == this.lngLat[1]){
+      return true
+    }
+    else{
+      return false
+    }
+  }
+
+  /**
+   * @description LngLat对象以字符串的形式返回。
+   * @returns {String} 返回经纬度格式的字符串,用逗号连接
+   */
+  toString() { 
+    return this.lngLat[0] + "," + this.lngLat[1]
+  }
+}
+
+export default LngLat

+ 1290 - 0
src/utils/ol-map/Map.js

@@ -0,0 +1,1290 @@
+import OLMap from 'ol/Map'
+import View from 'ol/View'
+import * as proj from 'ol/proj'
+import * as interaction from 'ol/interaction'
+import {Draw, Modify} from 'ol/interaction'
+import 'ol/ol.css'
+import './css/KMap.css'
+import * as Enum from './Enum'
+import Common from './Common'
+import VectorLayer from './VectorLayer'
+import * as Extent from 'ol/extent'
+import Overlay  from 'ol/Overlay'
+
+import {GeoJSON, WKT} from 'ol/format'
+import WMTSLayer from './WMTSLayer'
+import XYZLayer from './XYZLayer'
+import config from "@/api/config.js";
+import { Circle, Fill, Stroke } from 'ol/style.js';
+import { LineString, Point } from 'ol/geom';
+import { Style, Text } from 'ol/style';
+import {Feature} from "ol";
+import {getArea} from "ol/sphere"
+/**
+ * @description KMap.Map 地图类
+*/
+class Map {
+	/**
+	 * @description 地图实例
+	 * @static
+	 * @memberof Map
+	 */
+	Instance = null
+	/**
+	 * @description 信息窗体像素对象
+	 * @memberof Map
+	 */
+	infoWindowPixel = null
+	/**
+	 * @description 鼠标移动事件弹窗UID
+	 * @memberof Map
+	 */
+	InfowindowmoveUID = -1
+	/**
+	 * @description 鼠标点击事件弹窗UID
+	 * @memberof Map
+	 */
+	InfowindowclickUID = -1
+	/**
+	 * @description 是否有鼠标移动弹窗
+	 * @memberof Map
+	 */
+	ClearMouseMoveInfoWindow = false
+	/**
+	 * @description X方向缩放
+	 * @memberof Map
+	 */
+	scaleX = 1
+	/**
+	 * @description Y方向缩放
+	 * @memberof Map
+	 */
+	scaleY = 1
+  /**
+   * @param {string} id DOM元素ID
+   * @param {number} zoomLevel 地图层级
+   * @param {number} lng 纬度
+   * @param {number} lat 经度
+   * @description Map初始化方法
+	 * @constructor
+  */
+  constructor(id,zoomLevel,lng,lat,projection, minZoom, maxZoom, mapType, dragPan = true, mouseWheelZoom = true,isBaseMap = false, showCva = true){
+  		this.mapType = mapType || "img";
+		if(Map.Instance){
+			Map.Instance = false;
+		}
+		if(projection){
+			projection = proj.get(projection)
+		}
+		projection = projection || proj.get("EPSG:4326");
+		const vm = this
+		Map.Instance = this
+		let lnglat  = [lng,lat]
+
+		if(projection.getCode() == "EPSG:3857"){
+			lnglat = proj.fromLonLat(lnglat);
+		}
+
+		Common.checkLngLat(lng,lat)
+		let view = new View({
+			center: lnglat,
+			zoom: zoomLevel,
+			minZoom: minZoom || Common.ShowLevel[0],
+			maxZoom: maxZoom || Common.ShowLevel[1],
+			projection:projection,
+			enableRotation: false
+		})
+		this.view = view
+		this.map = new OLMap({
+			interactions: interaction.defaults({
+				dragPan,
+				mouseWheelZoom
+			}).extend([
+			new interaction.DragRotateAndZoom()]),
+			target: id,
+			layers: [],//vm.baseLayer
+			view: view,
+			control: []
+		})
+		if(!isBaseMap){
+			this.initBaseLayer(projection, showCva)
+		}
+		//初始化业务图层
+		this.initBusinessLayer()
+		//初始化地图信息弹窗
+		this.initInfoWindow()
+		//初始化地图基础事件
+		this.initMapBaseEvent()
+  }
+  /**
+	 * 初始化地图底图图层
+   * @return {array}
+	 * @memberof Map
+  */
+  async initBaseLayer(projection, showCva){
+  	  if(this.mapType == "img"){
+		  const img_wmts = await VE_API.system.getCfg({"k":"img_wmts_mkt","resultType":"json"});
+		  this.tdtImgLayer = new WMTSLayer(img_wmts.data, projection,this);
+		  if (showCva) {
+			  const cva_wmts = await VE_API.system.getCfg({"k":"cva_wmts_mkt","resultType":"json"});
+			  this.cva_torLayer = new  WMTSLayer(cva_wmts.data,projection,this);
+		  }
+	  }else{
+		  const img_wmts = await VE_API.system.getCfg({"k":"vec_c_wmts","resultType":"json"});
+		  this.tdtImgLayer = new WMTSLayer(img_wmts.data, projection,this);
+		  if (showCva) {
+			  const cva_wmts = await VE_API.system.getCfg({"k":"cva_c_wmts","resultType":"json"});
+			  this.cva_torLayer = new WMTSLayer(cva_wmts.data,projection,this);
+		  }
+	  }
+  }
+	addXYZLayer(url,options,zIndex){
+		let xyz = new XYZLayer(url,options, zIndex || 3);
+		return xyz;
+	}
+	/**
+	 * @description 初始化业务图层
+	 * @memberof Map
+	 */
+	initBusinessLayer(){
+		const vm = this
+		let map = vm.map
+		//创建默认点标记图层
+		vm.markerLayer = new VectorLayer("defaultMarkerLayer",101)
+		//创建默认线标记图层
+		vm.polyLineLayer = new VectorLayer("defaultPolylineLayer",101)
+		//创建默认面图层
+		vm.polygonLayer = new VectorLayer("defaultPolygonLayer",1000, {
+			style: vm.polygonStyle
+		})
+		//创建文本标记图层
+		vm.labelLayer = new VectorLayer("defaultLabelLayer",99)
+
+		map.addLayer(vm.polygonLayer.layer)
+
+		map.once('postrender', function(event) {
+			map.addLayer(vm.markerLayer.layer)
+			map.addLayer(vm.polyLineLayer.layer)
+			map.addLayer(vm.labelLayer.layer)
+			map.on('click',function(evt){
+				let coordinate = evt.coordinate;
+				// let newPoints = proj.transform(coordinate, 'EPSG:3857', 'EPSG:4326');
+				// debugger;
+			})
+			// map.addLayer(layer)
+		})
+	}
+
+	initDraw(callback){
+		const vm = this
+		this.draw = new Draw({
+			type: 'MultiPolygon',
+			source: this.polygonLayer.source,
+			free: true,
+			style: vm.drawStyleFunc
+		})
+		this.draw.setActive(false)
+		this.map.addInteraction(this.draw);
+		this.draw.on("drawend",callback)
+	}
+	startDraw(){
+		this.draw.setActive(true)
+	}
+
+	modifyDraw(callback) {
+		this.modify = new Modify({
+			source: this.polygonLayer.source,
+			pixelTolerance: 10, //设置吸附像素值
+		})
+        this.map.addInteraction(this.modify);
+		this.modify.on("modifyend",callback)
+	}
+
+	drawStyleFunc(feature) {
+		const styles = [];
+		const type = feature.getGeometry().getType();
+		const coord = feature.getGeometry().getCoordinates();
+		for (let i = 0; i < coord.length - 1; i++) {
+			if (i%2) {
+				styles.push(
+					new Style({
+						geometry: new Point(coord[i]),
+						image: new Circle({
+							radius: 4,
+							fill: new Fill({
+								color: '#54cb82'
+							}),
+							stroke: new Stroke({
+								color: '#54cb82',
+								width: 3
+							})
+						})
+					})
+				);
+			} else {
+				styles.push(
+					new Style({
+						geometry: new Point(coord[i]),
+						image: new Circle({
+							radius: 6,
+							fill: new Fill({
+								color: '#fff'
+							}),
+						})
+					})
+				);
+			}
+		}
+		if (type === 'LineString') {
+			for (let i = 0; i < coord.length - 1; i++) {
+				styles.push(
+					new Style({
+						geometry: new LineString([coord[i], coord[i + 1]]),
+						stroke: new Stroke({
+							color: '#54cb82',
+							width: 2
+						})
+					})
+				);
+			}
+		}
+		return styles;
+	}
+
+	polygonStyle(feature) {
+		const styles = [];
+		const coord = feature.getGeometry().getCoordinates()[0];
+		for (let i = 0; i < coord[0].length - 1; i++) {
+			if (i%2) {
+				styles.push(
+				  new Style({
+					geometry: new Point(coord[0][i]),
+					image: new Circle({
+					  radius: 4,
+					  fill: new Fill({
+						color: '#54cb82'
+					  }),
+					  stroke: new Stroke({
+						color: '#54cb82',
+						width: 3
+					  })
+					})
+				  })
+				);
+			} else {
+				styles.push(
+				  new Style({
+					geometry: new Point(coord[0][i]),
+					image: new Circle({
+					  radius: 6,
+					  fill: new Fill({
+						color: '#fff'
+					  })
+					})
+				  })
+				);
+			}
+		}
+		let fillStyle = new Style({
+			fill: new Fill({
+			  color: [1, 41, 52, 0.6]
+			}),
+			stroke: new Stroke({
+			  color: '#54cb82',
+			  width: 2
+			})
+		})
+		let geom = feature.getGeometry().clone()
+		geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"))
+        let area = getArea(geom)
+        area = (area + area / 2) / 1000;
+		let areaValStyle = new Style({
+			text: new Text({
+				font: "16px sans-serif",
+				text: area.toFixed(2) + "亩",
+				// offsetX: 28,
+				// offsetY: -100,
+				fill: new Fill({ color: "#fff" }), // 字体颜色
+			}),
+		})
+		styles.push(fillStyle, areaValStyle);
+		return styles;
+	}
+
+	getLayerFeatures() {
+		const vm = this
+		let features = vm.polygonLayer.source.getFeatures()
+		return features
+	}
+
+	// 传入geojson,回显到polygon
+	setLayerPolygon(geometry) {
+		const vm = this
+		vm.polygonLayer.source.addFeatures(new GeoJSON().readFeatures(geometry))
+	}
+
+	setLayerWkt(wkt) {
+		const vm = this
+		let f = new Feature({geometry:new WKT().readGeometry(wkt)})
+		const extent = f.getGeometry().getExtent()
+		vm.polygonLayer.source.addFeature(f)
+        vm.map.getView().fit(extent, { padding: [20, 20, 20, 20] });
+	}
+
+	addLayer(layer){
+		const vm = this
+		vm.map.addLayer(layer)
+	}
+	removeLayer(layer){
+		const vm = this
+		vm.map.removeLayer(layer)
+	}
+	/**
+	 * @description 初始化信息弹窗
+	 * @memberof Map
+	 */
+	initInfoWindow(){
+		const vm = this
+		//创建地图弹窗容器
+		let infoWindowBoxClick = document.createElement("div")
+		let infoWindowBoxMove = document.createElement("div")
+		let mapTarget = vm.map.getTargetElement()
+		infoWindowBoxClick.id = "infowindow-click"
+		infoWindowBoxMove.id = "infowindow-move"
+		infoWindowBoxClick.style.zIndex = 999
+		infoWindowBoxMove.style.zIndex = 999
+		mapTarget.appendChild(infoWindowBoxClick)
+		mapTarget.appendChild(infoWindowBoxMove)
+		vm.infoWindow_click = new Overlay({
+			element: infoWindowBoxClick
+		})
+		vm.infoWindow_move = new Overlay({
+			element: infoWindowBoxMove
+		})
+		//添加点击弹窗
+		vm.map.addOverlay(vm.infoWindow_click)
+		//添加悬停弹窗
+		vm.map.addOverlay(vm.infoWindow_move)
+	}
+	/**
+	 * @description 初始化地图基础事件
+	 * @memberof Map
+	 */
+	initMapBaseEvent(){
+		const vm = this
+		var allowTriggerEvent = function(pixel) {
+			var infoWindowPixel = vm.infoWindowPixel
+			if(infoWindowPixel == null){
+				return true
+			}
+			var x = pixel[0]
+			var y = pixel[1]
+			if(x>=infoWindowPixel[0] && x<=infoWindowPixel[2] &&
+				 y>=infoWindowPixel[1] && y<=infoWindowPixel[3]) {
+				return false
+			}
+			return true
+		}
+		vm.map.on('click',function(event){
+			event.pixel[0] = (event.pixel[0] / vm.scaleX)
+			event.pixel[1] = (event.pixel[1] / vm.scaleY)
+			var clickFeature = vm.map.forEachFeatureAtPixel(event.pixel, function(feature){
+				if(!allowTriggerEvent(event.pixel)) return
+				// 为点击到的feature发送自定义的click消息
+				if(feature.dispatchEvent != undefined){
+					feature.dispatchEvent({type: 'click', event: event})
+				}
+				return feature
+			})
+			//点击在地图空白处时清空弹窗
+			if(clickFeature == undefined){
+				vm.clearInfoWindow()
+			}
+		})
+
+		//为地图注册鼠标点击事件的监听
+		vm.map.on('pointermove', function(event) {
+			event.pixel[0] = (event.pixel[0] / vm.scaleX)
+			event.pixel[1] = (event.pixel[1] / vm.scaleY)
+			var mousemoveFeature = vm.map.forEachFeatureAtPixel(event.pixel, function(feature){
+				if(!allowTriggerEvent(event.pixel)){
+					return
+				}
+				// 为点击到的feature发送自定义的mousemove消息
+				if(feature.dispatchEvent != undefined){
+					feature.dispatchEvent({type: 'mousemove', event: event})
+				}
+				return feature
+			})
+			//悬停在地图空白处时清空悬停弹窗
+			if(mousemoveFeature == undefined)
+			{
+				vm.clearMouseMoveInfoWindow()
+			}
+			//设置鼠标悬停到覆盖物上的样式
+			var mapContainer = vm.getTarget()
+			if(mousemoveFeature) {
+				mapContainer.style.cursor = "pointer"
+			}
+			else {
+				mapContainer.style.cursor = "default"
+			}
+		})
+	}
+
+	setScale(x, y) {
+		const vm = this
+		// var mapContainer = vm.getTarget()
+		// mapContainer.style.overflow = 'hidden'
+		// var mapContent = mapContainer.getElementsByClassName('ol-viewport')[0]
+		// var scaleX = 1 / Number(x);
+		// var scaleY = 1 / Number(y);
+		vm.scaleX = Number(x)
+		vm.scaleY = Number(y)
+		// mapContent.style.transform = "scale("+scaleX+","+scaleY+")"
+	}
+
+	/**
+	 * @description 清除鼠标点击弹窗
+	 * @memberof Map
+	*/
+	clearInfoWindow() {
+		const vm = this
+		vm.infoWindow_click.setPosition(undefined)
+		vm.infoWindow_move.setPosition(undefined)
+		vm.infoWindowPixel = null
+		vm.InfowindowmoveUID = -1
+		vm.InfowindowclickUID = -1
+	}
+
+	/**
+	 * @description 清除鼠标移动弹窗
+	 * @memberof Map
+	*/
+	clearMouseMoveInfoWindow() {
+		const vm = this
+		if(vm.ClearMouseMoveInfoWindow) {
+			vm.infoWindow_move.setPosition(undefined)
+			vm.infoWindowPixel = null
+			vm.InfowindowmoveUID = -1
+		}
+	}
+
+  /**
+	 * @description 获取地图容器div
+	 * @returns 地图容器div
+	 * @memberof Map
+	 */
+	getTarget() {
+		let target = this.map.getTargetElement()
+		return target
+	}
+
+	/**
+	 * @description 获取地图容器尺寸
+	 * @returns KMap.Size格式的尺寸
+	 * @memberof Map
+	 */
+	getSize() {
+		let size = this.map.getSize()
+		console.log(Common)
+		size = Common.MapSize2KMapSize(size)
+		return size
+	}
+
+	/**
+	 * @description 获取地图投影EPSG类型
+	 * @returns 地图投影类型
+	 * @memberof Map
+	 */
+	getProjection() {
+		let projection = this.view.getProjection()
+		return projection
+	}
+
+	/**
+	 * @description 获取地图中心
+	 * @returns 地图中心,KMap.LngLat对象格式
+	 * @memberof Map
+	 */
+	getCenter() {
+		let center = this.view.getCenter()
+		center = proj.toLonLat(center)
+		return Common.MapLngLat2KMapLngLat(center)
+	}
+	/**
+	 * @description 获取地图中心
+	 * @returns 地图中心,KMap.LngLat对象格式
+	 * @memberof Map
+	 */
+	 getCenter2() {
+		let center = this.view.getCenter()
+		center = proj.toLonLat(center)
+		return center
+	}
+
+	/**
+	 * @description 设置地图中心
+	 * @param {KMap.LngLat} position 地图中心位置,KMap.LngLat对象格式,必填
+	 * @memberof Map
+	 */
+	setCenter(position) {
+		let centerlnglat = Common.KMapLngLat2MapLngLat(position)
+		let center = proj.fromLonLat(centerlnglat)
+		this.view.setCenter(center)
+	}
+	getView(){
+		return this.view;
+	}
+  	/**
+	 * @description 设置地图中心
+	 * @param {KMap.LngLat} position 地图中心位置,KMap.LngLat对象格式,必填
+	 * @memberof Map
+ 	*/
+	setCenter2(position) {
+		this.view.setCenter(position)
+	}
+	fitToView(center,zoom,dera) {
+		this.view.fit(center,{duration: dera})
+	}
+	/**
+	 * @description 地图中心点平移至指定点位置
+	 * @param {KMap.LngLat} point 指定点经纬度坐标,KMap.LngLat对象格式,必填
+	 * @param {number} zoom 缩放级别,选填参数,不填则使用当前缩放级别
+	 * @memberof Map
+	 */
+	panTo(point,zoom) {
+		point = Common.KMapLngLat2MapLngLat(point)
+		let center = proj.fromLonLat(point)
+		if(zoom) {
+			this.view.animate({center:center},{zoom:zoom})
+		}
+		else {
+			this.view.animate({center:center})
+		}
+	}
+
+	/**
+	 * @description 地图放大一级显示
+	 * @memberof Map
+	 */
+  zoomIn() {
+		this.view.setZoom( this.getZoom() + 1 )
+		return this.getZoom()
+	}
+
+	/**
+	 * @description 地图缩小一级显示
+	 * @memberof Map
+	 */
+  zoomOut() {
+		this.view.setZoom( this.getZoom() - 1)
+		return this.getZoom()
+	}
+
+	/**
+	 * @description 缩放到点标记图层范围
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToMarkerLayer(duration) {
+		const vm = this
+		duration = (duration != undefined)? duration : 0
+		let markers = vm.markerLayer.getSource().getFeatures()
+		let coordinateArray = new Array()
+		for(let i=0; i<markers.length; i++) {
+			coordinateArray.push(markers[i].getGeometry().getCoordinates())
+		}
+		let extentBound = new Extent.boundingExtent(coordinateArray)
+		this.view.fit(extentBound,{
+			duration: duration
+		})
+
+		this.view.fit(vm.markerLayer.getSource().getExtent(),{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 缩放到点标记集合范围
+	 * @param {Array} markerArray 点标记集合,必填
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToMarkerArray(markerArray,duration) {
+		duration = (duration != undefined)? duration : 0
+		let coordinateArray = new Array()
+		for(let i=0; i<markerArray.length; i++) {
+			coordinateArray.push(markerArray[i].Marker.getGeometry().getCoordinates())
+		}
+		let extentBound = new Extent.boundingExtent(coordinateArray)
+		this.view.fit(extentBound,{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 缩放到文本标记图层范围
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToLabelLayer(duration) {
+		const vm = this;
+		duration = (duration != undefined)? duration : 0
+		this.view.fit(vm.labelLayer.getSource().getExtent(),{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 缩放到文本标记集合范围
+	 * @param {Array}  labelArray 文本标记集合,必填
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToLabelArray(labelArray,duration) {
+		duration = (duration != undefined)? duration : 0
+		let coordinateArray = new Array()
+		for(let i=0; i<labelArray.length; i++) {
+			coordinateArray.push(labelArray[i].Label.getGeometry().getCoordinates())
+		}
+		let extentBound = new Extent.boundingExtent(coordinateArray)
+		this.view.fit(extentBound,{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 缩放到线图层范围
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToPolylineLayer(duration) {
+		const vm = this
+		duration = (duration != undefined)? duration : 0
+		this.view.fit(vm.polyLineLayer.getSource().getExtent(),{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 缩放到线标记集合范围
+	 * @param {Array} lineArray 线标记集合,必填
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToPolylineArray(lineArray,duration) {
+		duration = (duration != undefined)? duration : 0
+		let coordinateArray = new Array()
+		for(let i=0; i<lineArray.length; i++) {
+			let coordinates = lineArray[i].polyline.getGeometry().getCoordinates()
+			for(let z=0; z<coordinates.length; z++) {
+				coordinateArray.push(coordinates[z])
+			}
+		}
+		let extentBound = new Extent.boundingExtent(coordinateArray)
+		this.view.fit(extentBound,{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 缩放到经纬度数组范围
+	 * @param {Array} lngLatArray KMap.LngLat格式的经纬度坐标数组,必填
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	zoomToLngLatArray(lngLatArray,duration) {
+		duration = (duration != undefined)? duration : 0
+		let coordinateArray = new Array()
+		for(let i=0; i<lngLatArray.length; i++) {
+			let point = Common.KMapLngLat2MapLngLat(lngLatArray[i])
+			coordinateArray.push(proj.fromLonLat(point))
+		}
+		let extentBound = new Extent.boundingExtent(coordinateArray)
+		this.view.fit(extentBound,{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 调整地图视角到能够显示所有覆盖物的合适矩形范围
+	 * @param {number} duration 选填参数,动画时长(单位:毫秒),不填则使用默认的0毫秒
+	 * @memberof Map
+	 */
+	setFitView(duration) {
+		const vm = this
+		//获取所有元素坐标点集合
+		let LonLatArray = new Array()
+		let markers = vm.markerLayer.getSource().getFeatures()
+		let labels = vm.labelLayer.getSource().getFeatures()
+		let polylines = vm.polyLineLayer.getSource().getFeatures()
+		let features = [markers,labels,polylines]
+		for(let i=0; i<features.length; i++) {
+			for(let z=0; z<features[i].length; z++) {
+				let featureLonLats = features[i][z].getGeometry().getCoordinates()
+				if(features[i] != polylines) {
+					LonLatArray.push(featureLonLats)
+				}
+				else {
+					for(let m=0; m<featureLonLats.length; m++) {
+						LonLatArray.push(featureLonLats[m])
+					}
+				}
+			}
+		}
+		//地图视角切换到坐标点集合的矩形范围
+		duration = (duration != undefined)? duration : 0
+		let extentBound = new Extent.boundingExtent(LonLatArray)
+		this.view.fit(extentBound,{
+			duration: duration
+		})
+	}
+
+	/**
+	 * @description 获取地图分辨率
+	 * @returns {number} 地图分辨率
+	 * @memberof Map
+	 */
+	getResolution() {
+		let resolution = this.view.getResolution()
+		return resolution
+	}
+
+	/**
+	 * @description 获取地图当前缩放值
+	 * @returns {number} 地图缩放级别
+	 * @memberof Map
+	 */
+	getZoom() {
+		let zoom = this.view.getZoom()
+		return zoom
+	}
+
+	/**
+	 * @description 设置地图当前缩放值
+	 * @param {number}zoom 缩放值,必填
+	 * @memberof Map
+	 */
+	setZoom(zoom) {
+		this.view.setZoom(zoom)
+	}
+
+	/**
+	 * @description 获取地图最大缩放值
+	 * @returns {number} 最大缩放值
+	 * @memberof Map
+	 */
+	getMaxZoom() {
+		let maxZoom = this.view.getMaxZoom()
+		return maxZoom
+	}
+
+	/**
+	 * @description 设置地图最大缩放值
+	 * @param {number} zoom 最大缩放值,必填
+	 * @memberof Map
+	 */
+	setMaxZoom(zoom) {
+		this.view.setMaxZoom(zoom)
+	}
+
+	/**
+	 * @description 获取地图最小缩放值
+	 * @returns {number} 最小缩放值
+	 * @memberof Map
+	 */
+	getMinZoom () {
+		let minZoom = this.view.getMinZoom()
+		return minZoom
+	}
+
+	/**
+	 * @description 设置地图最小缩放值
+	 * @param {number} zoom 最小缩放值,必填
+	 * @memberof Map
+	 */
+	setMinZoom(zoom) {
+		this.view.setMinZoom(zoom)
+	}
+
+	/**
+	 * @description 设置地图中心和缩放级别
+	 * @param {number} zoom 缩放级别,必填
+	 * @param {KMap.LngLat} center 地图中心 KMap.LngLat对象格式,必填
+	 * @param {boolean} animate 选填,是否使用缓冲动画,默认为false
+	 * @memberof Map
+	 */
+	setZoomAndCenter(zoom,center,animate) {
+		let centerlnglat = Common.KMapLngLat2MapLngLat(center)
+		center = proj.fromLonLat(centerlnglat)
+		if(animate) {
+			this.view.animate({center:center,zoom:zoom})
+		}
+		else {
+			this.view.setCenter(center)
+			this.view.setZoom(zoom)
+		}
+	}
+
+	/**
+	 * @description 获取地图经纬度矩形范围
+	 * @returns {KMap.Bounds} 地图经纬度矩形范围,KMap.Bounds格式
+	 * @memberof Map
+	 */
+	getBounds() {
+		const vm = this;
+		let bounds = vm.view.calculateExtent(vm.map.getSize())
+		let southWest = proj.toLonLat([bounds[0],bounds[1]])
+		let northEast = proj.toLonLat([bounds[2],bounds[3]])
+		bounds = [southWest[0],southWest[1],northEast[0],northEast[1]]
+		let mapBound = Common.MapBounds2KMapBounds(bounds)//将OL的Bounds格式转换成KMap的Bounds格式
+		return mapBound
+	}
+
+	/**
+	 * @description 设置地图经纬度矩形范围
+	 * @param {KMap.Bounds} bound 地图经纬度矩形范围,KMap.Bounds格式,必填
+	 * @memberof Map
+	 */
+	setBounds(bound) {
+		let lnglatArray = new Array()
+		let mapBound = Common.KMapBounds2MapBounds(bound)//将KMap的Bounds格式转换成OL的Bounds格式
+		lnglatArray.push(proj.fromLonLat([mapBound[0],mapBound[1]]))
+		lnglatArray.push(proj.fromLonLat([mapBound[2],mapBound[3]]))
+		let bounds = new Extent.boundingExtent(lnglatArray)
+		this.view.fit(bounds) //地图视角切换到矩阵范围
+	}
+	/**
+	 * @description 设置地图经纬度矩形范围
+	 * @param {[minLng,minLat,maxLng,maxLat]} bound 地图经纬度矩形范围,KMap.Bounds格式,必填
+	 * @memberof Map
+	 */
+	 fitBounds(bound) {
+		let lnglatArray = new Array();
+		lnglatArray.push(proj.fromLonLat([bound[0],bound[1]]))
+		lnglatArray.push(proj.fromLonLat([bound[2],bound[3]]))
+		let bounds = new Extent.boundingExtent(lnglatArray)
+		this.view.fit(bounds) //地图视角切换到矩阵范围
+	}
+	fit(geometryOrExtent,padding){
+		this.view.fit(geometryOrExtent,{ duration:500,padding})
+	}
+	/**
+	 * @description 平面地图像素坐标转经纬度坐标
+	 * @param {KMap.Pixel} pixel 平面地图像素坐标,格式为KMap.Pixel对象,必填
+	 * @returns {KMap.LngLat} 经纬度坐标,格式为KMap.LngLat对象
+	 * @memberof Map
+	 */
+	pixelToLngLat(pixel) {
+		pixel = Common.KMapPixel2MapPixel(pixel)
+		let lnglat = new proj.toLonLat(pixel)
+		return Common.MapLngLat2KMapLngLat(lnglat)
+	}
+
+	/**
+	 * @description 经纬度坐标转平面地图像素坐标
+	 * @param {KMap.LngLat} lnglat 经纬度坐标,格式为KMap.LngLat对象,必填
+	 * @returns {KMap.Pixel} 地图像素坐标,格式为KMap.Pixel对象
+	 * @memberof Map
+	 */
+	lnglatToPixel(lnglat) {
+		lnglat = Common.KMapLngLat2MapLngLat(lnglat)
+		let pixel = proj.fromLonLat(lnglat)
+		return Common.MapPixel2KMapPixel(pixel)
+	}
+
+	/**
+	 * @description 地图容器屏幕坐标转经纬度坐标
+	 * @param {KMap.Pixel} pixel 地图容器像素,格式为KMap.Pixel对象,必填
+	 * @returns {KMap.LngLat} 返回KMap.LngLat格式的经纬度
+	 * @memberof Map
+	 */
+	containerToLngLat(pixel) {
+		pixel = Common.KMapPixel2MapPixel(pixel)
+		let lnglat =this.map.getCoordinateFromPixel(pixel)
+		lnglat = proj.toLonLat(lnglat)
+		lnglat = Common.MapLngLat2KMapLngLat(lnglat)
+		return lnglat
+	}
+
+	/**
+	 * @description 经纬度坐标转地图容器屏幕坐标
+	 * @param {KMap.LngLat} lnglat 经纬度坐标,KMap.LngLat格式的经纬度,必填
+	 * @returns {KMap.Pixel} 返回地图容器像素,格式为KMap.Pixel对象
+	 * @memberof Map
+	 */
+	lngLatToContainer(lnglat) {
+		lnglat = Common.KMapLngLat2MapLngLat(lnglat)
+		let coordinate = proj.fromLonLat(lnglat)
+		let container =this.map.getPixelFromCoordinate(coordinate)
+		return Common.MapPixel2KMapPixel(container)
+	}
+
+	/**
+	 * @description 获取地图顺时针旋转角度
+	 * @returns {number} 顺时针旋转角度(弧度)
+	 * @memberof Map
+	 */
+	getRotation() {
+		let rotation = this.view.getRotation()
+		return rotation
+	}
+
+	/**
+	 * @description 设置地图顺时针旋转角度
+	 * @param {number} rotation 顺时针旋转角度(弧度),必填
+	 * @memberof Map
+	 */
+	setRotation(rotation) {
+		this.view.setRotation(rotation)
+	}
+
+	/**
+	 * @description 获取地图插件集合
+	 * @returns {Array} 地图插件集合数组
+	 * @memberof Map
+	 */
+	getControls() {
+		let controls =this.map.getControls().array_
+		return controls
+	}
+
+	/**
+	 * @description 添加插件
+	 * @param {ol.control} control OL原生control对象
+	 * @memberof Map
+	 */
+	addControl(control) {
+		let state = true
+		let controls = this.map.getControls().array_
+		for(let i=0; i<controls.length; i++) {
+			if(control == controls[i]) {
+				state = false
+				break
+			}
+		}
+		if(state){
+			this.map.addControl(control)
+		}
+	}
+
+	/**
+	 * @description 删除插件
+	 * @param {ol.control} control 插件,必填
+	 * @memberof Map
+	 */
+	removeControl(control) {
+		let controls = this.map.getControls().array_
+		for(let i=0; i<controls.length; i++) {
+			if(control == controls[i]) {
+				this.map.removeControl(controls[i])
+				return
+			}
+		}
+	}
+
+	/**
+	 * @description 清空默认插件 注意如果要清除默认插件需要在加载其他插件前调用此函数
+	 * @memberof Map
+	 */
+	removeOriginControls() {
+		let controls = this.map.getControls().array_
+		for(let i=0; i<controls.length; i++) {
+			this.map.removeControl(controls[i])
+		}
+	}
+
+	/**
+	 * @description 获取地图指针样式
+	 * @memberof Map
+	 */
+	getDefaultCursor() {
+		let mapContainer = this.map.getTargetElement()
+		let cursor = mapContainer.style.cursor
+		return cursor
+	}
+
+	/**
+	 * @description 设置地图指针样式
+	 * @param {String} cursorStyle 鼠标样式("default"默认指针,"pointer"小手,"move"移动指针, "text"文本指针,"wait"等待状态,"help"帮助),必填
+	 * @memberof Map
+	*/
+	setDefaultCursor(cursorStyle) {
+		let mapContainer = this.map.getTargetElement()
+		if(cursorStyle != undefined) {
+			mapContainer.style.cursor = cursorStyle
+		}
+		else {
+			mapContainer.style.cursor = "default"
+		}
+	}
+
+	/**
+	 * @description 设置鼠标悬停在元素上时的样式
+	 * @param {String} cursorStyle 鼠标样式("default"默认指针,"pointer"小手,"move"移动指针, "text"文本指针,"wait"等待状态,"help"帮助),必填
+	 * @memberof Map
+	*/
+	setFeatureCursor(cursorStyle) {
+		cursorStyle = (cursorStyle == undefined)? "default" : cursorStyle
+		let mapContainer = this.map.getTargetElement()
+		let defaultCursor = mapContainer.style.cursor
+		this.map.on("pointermove",function(e){
+			let features = this.map.forEachFeatureAtPixel(e.pixel,function(feature) { return feature })
+			if(features) {
+				mapContainer.style.cursor = cursorStyle
+			}
+			else {
+				mapContainer.style.cursor = defaultCursor
+			}
+		})
+	}
+
+	/**
+	 * @description 获取地图显示元素种类
+	 * @returns {Array} 地图显示元素种类集合
+	 * @memberof Map
+	 */
+	getFeatures() {
+		const vm = this
+		let features = new Array()
+		if(vm.baseLayer.getVisible() == true) {
+			features.push("Tile")
+		}
+		if(vm.markerLayer.getVisible() == true){
+			features.push("Marker")
+		}
+		if(vm.labelLayer.getVisible() == true){
+			features.push("Label")
+		}
+		if(vm.polyLineLayer.getVisible() == true){
+			features.push("PolyLine")
+		}
+		return features
+	}
+
+	/**
+	 * @description 设置地图显示元素种类
+	 * @param {JSON} param param 地图元素显示参数,JSON对象,必填
+	 * param.marker true/false,点标记是否显示,选填
+	 * param.label true/false,文本标记是否显示,选填
+	 * param.polyline true/false,线标记是否显示,选填
+	 * @memberof Map
+	 */
+	setFeatures(param) {
+		const vm = this
+		if(param.marker == true || param.marker == false){
+			vm.markerLayer.setVisible(param.marker)
+		}
+
+		if(param.label == true || param.label == false){
+			vm.labelLayer.setVisible(param.label)
+		}
+
+		if(param.polyline == true || param.polyline == false){
+			vm.polyLineLayer.setVisible(param.polyline)
+		}
+
+	}
+
+	/**
+	 * @description 获取地图状态(双击缩放/拖拽/滚动鼠标中间缩放)
+	 * @returns {JSON} 地图状态
+	 * {"DoubleClickZoom": true, "DragAndDrop": true, "MouseWheelZoom": true}
+	 * @memberof Map
+	 */
+	getStates() {
+		let interactions =this.map.getInteractions().array_
+		let DoubleClickZoom,DragAndDrop,MouseWheelZoom
+		for(let i=0; i<interactions.length; i++) {
+			if(i==1) DoubleClickZoom = interactions[i].getActive()
+			if(i==2) DragAndDrop = interactions[i].getActive()
+			if(i==7) MouseWheelZoom = interactions[i].getActive()
+		}
+		let states = {
+			"DoubleClickZoom": DoubleClickZoom,
+		  "DragAndDrop": DragAndDrop,
+		  "MouseWheelZoom": MouseWheelZoom
+		}
+		return states
+	}
+
+	/**
+	 * @description 设置地图状态(双击缩放/拖拽/滚动鼠标中间缩放)
+	 * @param {JSON} param 地图状态 JSON对象,必填
+	 * param.DoubleClickZoom true/false(双击缩放地图),选填
+	 * param.DragAndDrop true/false(地图拖拽),选填
+	 * param.MouseWheelZoom true/false(滚动鼠标中间缩放地图),选填
+	 * @memberof Map
+	 */
+	setStates(param) {
+		let interactions =this.map.getInteractions().array_
+		if(param.DoubleClickZoom == true || param.DoubleClickZoom == false)
+		interactions[1].setActive(param.DoubleClickZoom)
+		if(param.DragAndDrop == true || param.DragAndDrop == false)
+		interactions[2].setActive(param.DragAndDrop)
+		if(param.MouseWheelZoom == true || param.MouseWheelZoom == false)
+		interactions[7].setActive(param.MouseWheelZoom)
+	}
+
+	/**
+	 * @description 清空地图所有元素
+	 * @memberof Map
+	 */
+	clearMap() {
+		const vm = this
+		//清空图层元素
+		vm.markerLayer.getSource().clear()
+		vm.labelLayer.getSource().clear()
+		vm.polyLineLayer.getSource().clear()
+		vm.polygonLayer.getSource().clear()
+		//清空地图弹窗
+		vm.clearInfoWindow()
+	}
+
+	/**
+	 * @description 清除地图对象并清空地图容器(建议少使用此API,容易造成报错)
+	 * @memberof Map
+	 */
+	destroy() {
+		//清空地图对象
+		// this.map.destroy()
+		//清空地图容器
+		let target =this.map.getTargetElement()
+		target.innerHTML = ""
+		Common.UseBaiDuOnlineLayer = false;
+		Common.UseGaoDeOnlineLayer = false;
+		Common.UseWGS84OnlineLayer = false;
+	}
+
+	/**
+	 * @description 获取地图图层数组
+	 * @return {Array} 地图图层数组
+	 * @memberof Map
+	 */
+	getLayers() {
+		let layers =this.map.getLayers().array_
+		return layers
+	}
+
+	/**
+	 * @description 设置地图各个图层显示/隐藏
+	 * @param {JSON} param 地图图层显示参数,JSON对象,必填
+	 * param.Marker true/false,点标记图层显示/隐藏,选填
+	 * param.Label true/false,文本标记图层显示/隐藏,选填
+	 * param.PolyLine true/false,线标记图层显示/隐藏,选填
+	 * @memberof Map
+	 */
+	setLayers(param) {
+		const vm = this
+		if(param.Marker == true || param.Marker == false){
+			vm.markerLayer.setVisible(param.Marker)
+		}
+		if(param.Label == true || param.Label == false){
+			vm.labelLayer.setVisible(param.Label)
+		}
+		if(param.PolyLine == true || param.PolyLine == false){
+			vm.polyLineLayer.setVisible(param.PolyLine)
+		}
+	}
+
+	/**
+	 * @description 获取地图各个图层index值
+	 * @returns {JSON} JSON对象
+	 * {"markerLayer": 0, "labelLayer": 0, "polyLineLayer": 0}
+	 * @memberof Map
+	 */
+	getLayersIndex() {
+		const vm = this
+		let index = {}
+		index.markerLayer = vm.markerLayer.getZIndex()
+		index.labelLayer = vm.labelLayer.getZIndex()
+		index.polyLineLayer = vm.polyLineLayer.getZIndex()
+		return index
+	}
+
+	/**
+	 * @description 设置地图各个图层index值,index值大的图层显示在上方,值相同时后加载的图层显示在上方
+	 * @param {JSON} param 图层索引JSON对象,必填
+	 * param.marker 点标记图层index值,选填
+	 * param.label 文字标记图层index值,选填
+	 * param.polyLine 线标记图层index值,选填
+	 * @memberof Map
+	 */
+  setLayersIndex(param) {
+		const vm = this
+		let markerIndex = (param.marker)? param.marker : vm.markerLayer.getZIndex()
+		let labelIndex = (param.label)? param.label : vm.labelLayer.getZIndex()
+		let polyLineIndex = (param.polyLine)? param.polyLine : vm.polyLineLayer.getZIndex()
+		vm.markerLayer.setZIndex(markerIndex)
+		vm.labelLayer.setZIndex(labelIndex)
+		vm.polyLineLayer.setZIndex(polyLineIndex)
+	}
+
+	/**
+	 * @description 注册地图事件
+	 * @param {String} eventName 地图操作事件类型,必填
+	 * "click":单击地图,双击将触发两次;"singleclick":单击地图;"dblclick":双击地图;"movestart":开始移动地图;
+	 * "moveend":移动地图结束;"postrender":渲染地图后;"pointerdrag":拖动指针时;"mousemove":移动指针时
+	 * @param {funciton} callback 操作事件触发时调用的函数,必填
+	 * @memberof Map
+	 */
+	on(eventName,callback) {
+		if(eventName == "mousemove"){
+			eventName = "pointermove"
+		}
+		if(eventName == "load"){
+			eventName = "postrender"
+		}
+		this.map.on(eventName,callback)
+	}
+
+	/**
+	 * @description 注册地图事件(事件仅执行一次)
+	 * @param {Strig} eventName 地图操作事件类型,必填
+	 * "click":单击地图,双击将触发两次;"singleclick":单击地图;"dblclick":双击地图;"movestart":开始移动地图;
+	 * "moveend":移动地图结束;"postrender":渲染地图后;"pointerdrag":拖动指针时;"mousemove":移动指针时
+	 * @param {function} callback 操作事件触发时调用的函数,必填
+	 * @memberof Map
+	 */
+	once(eventName,callback) {
+		if(eventName == "mousemove"){
+			eventName = "pointermove"
+		}
+		if(eventName == "load"){
+			eventName = "postrender"
+		}
+		this.map.once(eventName,callback)
+	}
+
+	/**
+	 * @description 取消地图绑定事件
+	 * @param {Strig} eventName 地图操作事件类型,必填
+	 * "click":单击地图,双击将触发两次;"singleclick":单击地图;"dblclick":双击地图;"movestart":开始移动地图;
+	 * "moveend":移动地图结束;"postrender":渲染地图后;"pointerdrag":拖动指针时;"mousemove":移动指针时
+	 * @param {function} callback 操作事件触发时调用的函数,必填
+	 * @memberof Map
+	 */
+	off(eventName,callback) {
+		if(eventName == "mousemove"){
+			eventName = "pointermove"
+		}
+		if(eventName == "load"){
+			eventName = "postrender"
+		}
+		this.map.un(eventName,callback)
+	}
+	setMarkerLayerZoomInfo(minZoom,maxZoom){
+		this.markerLayer.setMaxZoom(maxZoom);
+		this.markerLayer.setMinZoom(minZoom);
+	}
+	setPolylineLayerZoomInfo(minZoom,maxZoom){
+		this.polylineLayer.setMaxZoom(maxZoom);
+		this.polylineLayer.setMinZoom(minZoom);
+	}
+	setPolygonLayerZoomInfo(minZoom,maxZoom){
+		this.polygonLayer.setMaxZoom(maxZoom);
+		this.polygonLayer.setMinZoom(minZoom);
+	}
+}
+export default Map

+ 566 - 0
src/utils/ol-map/Marker.js

@@ -0,0 +1,566 @@
+import Feature from 'ol/Feature'
+import Point from 'ol/geom/Point'
+import * as proj from 'ol/proj'
+import Style from 'ol/style/Style'
+import Text from 'ol/style/Text'
+import Fill from 'ol/style/Fill'
+import Stroke from 'ol/style/Stroke'
+import Icon from 'ol/style/Icon'
+import Overlay from 'ol/Overlay'
+import Common from './Common'
+import KBaseObject from './KBaseObject'
+import LngLat from './LngLat'
+import InfoWindow from './InfoWindow'
+/**
+ * @description KMap.Marker 点标记类
+ */
+class Marker extends KBaseObject{
+	/**
+	 * @description 初始化Marker构造函数
+	 * @param {number} lng lng 经度 必填
+	 * @param {number} lat lat 纬度 必填
+	 * @param {String} markerImgUrl 点标记图标路径 必填
+	 * @param {number} offsetX X方向偏移量(正向右,负向左) 必填
+	 * @param {number} offsetY Y方向偏移量(正向下,负向上) 必填
+	 * @param {number} width  点标记尺寸宽度 必填
+	 * @param {number} height 点标记尺寸高度 必填
+	 * @param {JSON} param param.source 指定source 选填
+	 * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @constructor
+	 */
+	constructor(lng,lat,url,offsetX,offsetY,width,height,param,mapInstance = null){
+		super(mapInstance)
+		const vm = this
+		//对于必填的参数进行验证
+		let lnglat = [lng,lat];
+		if(vm.map.getView().getProjection() == proj.get("EPSG:3857")){
+			lnglat = proj.fromLonLat([lng,lat]);
+		}
+		vm.point = new Point(lnglat)
+		let marker = new Feature({
+			geometry: vm.point,
+			properties: null
+		})
+		vm.marker = marker
+		vm.source = vm.mapInstance.markerLayer.getSource()
+		if(param && param.source){
+			vm.source = param.source
+		}
+		vm.source.addFeature(marker)
+		Common.notEmpty("url",url)
+		vm.url = url
+		vm.style = vm.initSyle(vm.url,offsetX,offsetY,width,height)
+		vm.image = vm.style.getImage()
+		vm.contentInfo = {}
+		Common.checkLngLat(lng,lat)
+		vm.lngLat = [lng,lat]
+	}
+
+	/**
+	 * @description 初始化Marker样式
+	 * @param {String} url marker 图片地址
+	 * @param {number} offsetX  X方向偏移量(正向右,负向左) 必填
+	 * @param {number} offsetY  Y方向偏移量(正向下,负向上) 必填
+	 * @param {number} width  点标记尺寸宽度 必填
+	 * @param {number} height 点标记尺寸高度 必填
+	 * @memberof Marker
+	 */
+	initSyle(url,offsetX,offsetY,width,height){
+		const vm = this
+		offsetX = -offsetX/width
+		offsetY = -offsetY/height
+		let style = new Style({
+			image: new Icon({
+				src: url,
+				anchor: [offsetX,offsetY] //点标记偏移
+			}),
+			zIndex: 0
+		})
+		vm.marker.setStyle(style)
+		return style
+	}
+
+	/**
+	 * @description 获取点标记id
+	 * @returns {String} 点标记id
+	 */
+	getId() {
+		const vm = this
+		let id = vm.marker.getId()
+		return id
+	}
+
+	/**
+	 * @description 设置点标记id
+	 * @param {String}id 点标记id 必填
+	 */
+	setId(id) {
+		const vm = this
+		vm.marker.setId(id)
+	}
+
+	/**
+	 * 点标记添加文本
+	 * @param {JSON} param json对象,文本参数,必填
+	 * param.text 文本内容,必填
+	 * param.font 字体大小,默认为13px sans-serif,选填
+	 * param.offsetX X轴偏移(正向右,负向左),选填,默认为0
+	 * param.offsetY Y轴偏移(正向下,负向上),选填,默认为25
+	 * param.rotation 旋转角度,选填,默认0
+	 * param.fill 填充颜色,选填,默认black
+	 * param.backgroundColor 背景色,选填,默认#fff
+	 * param.backgroundStroke 边框颜色,选填,默认black
+	 * param.padding padding值,选填,默认[2,5,2,5]
+	 */
+	setText(param) {
+		const vm = this
+		let text = (param.text)? param.text : ""
+		let font = (param.font)? param.font : '13px sans-serif'
+		let offsetX = (param.offsetX)? param.offsetX : 0
+		let offsetY = (param.offsetY)? param.offsetY : 25
+		let rotation = (param.rotation)? param.rotation : 0
+		let fill = (param.fill)? param.fill : "black"
+		let backgroundColor = (param.backgroundColor)? param.backgroundColor : "#fff"
+		let backgroundStroke = (param.backgroundStroke)? param.backgroundStroke : "black"
+		let padding = (param.padding)? param.padding : [2,5,2,5]
+		text = new Text({
+			text: text,
+			font: font,
+			offsetX: offsetX,
+			offsetY: offsetY,
+			rotateWithView: false,//文本是否可旋转
+			rotation: rotation,
+			fill: new Fill({
+				color: fill
+			}),
+			backgroundFill: new Fill({
+				color: backgroundColor
+			}),
+			backgroundStroke: new Stroke({
+				color: backgroundStroke
+			}),
+			padding: padding
+		})
+		if(text){
+			vm.style.setText(text)
+		}
+	}
+
+	/**
+	 * @description 显示点标记
+	 */
+	show(){
+		const vm = this
+		if(!vm.source.hasFeature(vm.marker)) {
+			vm.source.addFeature(vm.marker)
+			if(vm.markerPopup){
+				vm.map.removeOverlay(vm.markerPopup)
+			}
+			vm.showContent()
+		}
+	}
+
+	/**
+	 * @description 隐藏点标记
+	 */
+	hide(){
+		const vm = this
+		if(vm.source.hasFeature(vm.marker)) {
+			vm.source.removeFeature(vm.marker);
+			if(vm.markerPopup){
+				vm.map.removeOverlay(vm.markerPopup)
+			}
+			vm.hideContent()
+		}
+	}
+
+	/**
+	 * @description 删除点标记
+	 */
+	remove(){
+		const vm = this
+		if(vm.source.hasFeature(vm.marker)) {
+			vm.source.removeFeature(vm.marker)
+			if(vm.markerPopup){
+				vm.map.removeOverlay(vm.markerPopup)
+			}
+			vm.hideContent()
+		}
+	}
+
+	/**
+	 * @description 设置点标记为透明样式
+	 */
+	setHideStyle(){
+		const vm = this
+		let hideStyle = new Style({
+			stroke: new Stroke({
+				color: "transparent",
+				width: 1
+			})
+		})
+		vm.marker.setStyle(hideStyle)
+	}
+
+	/**
+	 * @description 获取点标记图标地址
+	 * @returns {String} 点标记图标地址
+	 */
+	getIcon(){
+		const vm = this
+		let icon = vm.image.iconImage_.src_
+		return icon
+	}
+
+	/**
+	 * @description 设置点标记图标
+	 * @param {String} url 点标记图标地址,必填
+	 */
+	setIcon(url,offsetX,offsetY){
+		const vm = this
+		let style = new Style({
+			image: new Icon({
+				src: url,
+				anchor: [offsetX,offsetY] //点标记偏移
+			}),
+			zIndex: 0
+		})
+		vm.marker.setStyle(style)
+	}
+
+	/**
+	 * @description 获取点标记坐标
+	 * @returns {KMap.LngLat} 返回KMap.LngLat格式的经纬度
+	 */
+	getPosition() {
+		const vm = this
+		let position = proj.toLonLat(vm.point.getCoordinates())
+		position = Common.MapLngLat2KMapLngLat(position)
+		return position
+	}
+
+	/**
+	 * @description 设置点标记坐标
+	 * @param {KMap.LngLat} lnglat 格式的点标记经纬度,必填
+	 */
+	setPosition(lnglat) {
+		const vm = this
+		lnglat = Common.KMapLngLat2MapLngLat(lnglat)
+		let position = proj.fromLonLat(lnglat)
+		vm.point.setCoordinates(position)
+		// console.log(vm.source.getState())
+		if(vm.markerPopup){
+			vm.markerPopup.setPosition(vm.point.getCoordinates())
+		}
+	}
+
+	/**
+	 * @description 获取点标记缩放值
+	 * @returns 点标记缩放值
+	 */
+	getScale() {
+		const vm = this
+		let scale = vm.image.getScale()
+		return scale
+	}
+
+	/**
+	 * @description 设置点标记缩放值
+	 * @param {number}scale 缩放值,必填
+	 */
+	setScale(scale) {
+		const vm = this
+		vm.image.setScale(scale)
+	}
+
+
+	/**
+	 * @description 获取点标记旋转弧度
+	 * @returns 点标记旋转弧度
+	 */
+	getAngle() {
+		const vm = this
+		let angle = vm.image.getRotation()
+		return angle
+	}
+
+	/**
+	 * @description 设置点标记旋转弧度
+	 * @param {number} rotation 旋转弧度,必填
+	 */
+	setAngle(rotation) {
+		const vm = this
+		vm.image.setRotation(rotation)
+	}
+
+	/**
+	 * @description 获取点标记叠加顺序
+	 * @returns 点标记叠加顺序
+	 */
+	getZIndex() {
+		const vm = this
+		let index = vm.style.getZIndex()
+		return index
+	}
+
+	/**
+	 * @description 设置点标记叠加顺序
+	 * @param {number} index 叠加顺序,必填,index值较大的点标记显示在上方 ,值相同时后创建的点标记显示在上方
+	 */
+	setZIndex(index) {
+		const vm = this
+		vm.style.setZIndex(index)
+	}
+
+	/**
+	 * @description 设置点标记置顶,当地图有多个marker时,当isTop为true时marker将显示在最上层;当isTop为false时取消置顶
+	 * @param {boolean} isTop 必填,true:显示在最上层,false:取消置顶
+	 */
+	setTop(isTop) {
+		const vm = this
+		let markers = vm.source.getFeatures()
+		let maxIndex = 0
+		for(let i=0; i<markers.length; i++) {
+			if(markers[i].getStyle().getZIndex() > maxIndex)
+				maxIndex = markers[i].getStyle().getZIndex()
+		}
+		if(isTop == true) {
+			vm.style.setZIndex(++maxIndex)
+		}
+		else if(isTop == false) {
+			vm.style.setZIndex(0)
+		}
+	}
+
+	/**
+	 * @description 获取用户自定义属性
+	 * @returns {number,String,JSON} 用户自定义属性(支持数字,字符串,json)
+	 */
+	getExtData() {
+		const vm = this
+		let extData = vm.marker.values_.properties
+		return extData
+	}
+
+	/**
+	 * @description 设置用户自定义属性
+	 * @param {number/String/JSON} extData 用户自定义属性(支持数字,字符串,json),必填
+	 */
+	setExtData(extData) {
+		const vm = this
+		extData = (extData)? extData : null
+		if(extData != null) {
+			vm.marker.values_.properties = extData
+		}
+	}
+
+	/**
+	 * @description 获取点标记地图对象
+	 * @returns 地图对象
+	 */
+	getMap() {
+		const vm = this
+		return vm.mapInstance
+	}
+
+	/**
+	 * @description 设置点标记地图对象,传入null时,移除点标记,
+	 * 建议不使用此API,使用remove方法
+	 * @param {KMap.Map} map 传入null,移除点标记
+	 */
+	setMap(mapInstance){
+		const vm = this
+		mapInstance = (mapInstance)? mapInstance : null
+		if(mapInstance == null) { //当map为null时,删除点标记
+			if(vm.source.hasFeature(vm.marker)) {
+				vm.source.removeFeature(vm.marker)
+				if(vm.markerPopup){
+					vm.map.removeOverlay(vm.markerPopup)
+				}
+				vm.hideContent()
+			}
+		}else{
+			if(mapInstance != vm.mapInstance){
+				vm.mapInstance = mapInstance
+
+			}
+		}
+	}
+
+	/**
+	 * @description 注册点标记事件
+	 * @param {String} eventName 鼠标事件
+	 * "click":鼠标点击事件 "mousemove":鼠标悬停事件
+	 * @param {function} callback 选中点标记时触发函数
+	 */
+	on(eventName,callback){
+		const vm = this
+		if(eventName == "dblclick"){
+			vm.map.on(eventName,function(e){
+				let feature = vm.map.forEachFeatureAtPixel(e.pixel,function(feature) {
+					return feature
+				},{
+					layerFilter:function(e){
+						if(e.values_.name == "defaultMarkerLayer"){
+							return true
+						}else{
+							return false
+						}
+					}
+				})
+				if(feature && feature == vm.marker){
+					callback(e)
+				}
+			})
+		}else{
+			vm.marker.on(eventName,function(e) {
+				if(eventName == "mousemove"){
+					if(vm.marker.ol_uid != vm.mapInstance.InfowindowmoveUID)
+					{
+						vm.mapInstance.InfowindowmoveUID = vm.marker.ol_uid
+						callback(e)
+					}
+				}
+				else
+				{
+					callback(e)
+				}
+			})
+		}
+	}
+
+	/**
+	 * @description 设置鼠标悬停时,点标记显示内容
+	 * @param {title} title 鼠标悬停内容(支持div模块/字符串格式),必填
+	 * @param {number} offsetX X轴方向偏移量(正向右,负向左),选填参数,默认为0
+	 * @param {number} offsetY Y轴方向偏移量(正向下,负向上),选填参数,默认为-25
+	 */
+	setTitle(title,offsetX,offsetY) {
+		const vm = this
+		title = (title != undefined)? title : ""
+		let lnglat = vm.getPosition()
+		vm.on("mousemove",function(){
+			let content
+			//if(title.indexOf("div")<0) { content = "<div class='olText'>"+title+"</div>" }
+			if(title.indexOf("div")<0) {
+				content = "<div class='olText'><p class='olTextP' title='"+title+"'>"+title+"</p></div>"
+			}else {
+				content = title
+			}
+			let infoWindow = new InfoWindow({
+				content: content,
+				position: lnglat,
+				offsetX: offsetX,
+				offsetY: offsetY,
+				type: "mousemove"
+			})
+			infoWindow.open(false) //显示标题内容
+		})
+	}
+
+	/**
+	 * @description 设置挂载Label
+	 * @param {DOM} content DOM元素
+	 * @param {number} offsetX DOM元素X偏移,向左为负,向右为正
+	 * @param {number} offsetY DOM元素X偏移,向上为负,向下为正
+	 * @memberof Marker
+	 */
+	setLabel(content,offsetX,offsetY){
+		const vm = this
+		let contentParent = document.createElement('div')
+		contentParent.insertAdjacentHTML('beforeend',content)
+		let offset = [0,0]
+		if(offsetX){
+			offset[0] = offsetX
+		}
+		if(offsetY){
+			offset[1] = offsetY
+		}
+		vm.markerPopup = new Overlay({
+			element: contentParent,
+			position: proj.fromLonLat([vm.lng,vm.lat]),
+			offset: offset//图片偏移量
+		})
+		vm.map.addOverlay(vm.markerPopup)
+	}
+
+	/**
+	 * @description 移除
+	 * @memberof Marker
+	 */
+	removeLabel(){
+		const vm = this
+		if(vm.markerPopup){
+			vm.map.removeOverlay(vm.markerPopup)
+		}
+	}
+
+	/**
+	 * @description 隐藏挂载dom元素
+	 */
+	hideContent(){
+		const vm = this
+		if(vm.contentInfo.overlay && vm.contentInfo.show){
+			vm.contentInfo.show = false
+			vm.map.removeOverlay(vm.contentInfo.overlay)
+		}
+	}
+
+	/**
+	 * @description 显示挂载dom元素
+	 */
+	showContent(){
+		const vm = this
+		if(vm.contentInfo.overlay && !vm.contentInfo.show){
+			vm.contentInfo.show = true
+			vm.map.addOverlay(vm.contentInfo.overlay)
+		}
+	}
+
+	/**
+	 * @description 设设置挂载的DOM,
+	 * @param {DOM} content 挂载到Map的DOM元素
+	 * @param {number} offsetX DOM元素在地图上X偏移,向左为负,向右为正
+	 * @param {number} offsetY DOM元素在地图上Y偏移,向上为负,向下为正
+	 * @memberof Marker
+	 */
+	setContent(content,offsetX,offsetY){
+		const vm = this
+		let overlay = new Overlay({
+			element: content,
+			offset:[offsetX,offsetY]
+		})
+		vm.contentInfo.content = content
+		vm.contentInfo.offsetX = offsetX
+		vm.contentInfo.offsetY = offsetY
+		vm.contentInfo.overlay = overlay
+		vm.contentInfo.show = true
+		vm.map.addOverlay(overlay)
+		let coordinate = vm.lngLat
+		overlay.setPosition(proj.fromLonLat(coordinate))
+	}
+
+	/**
+	 * @description 设置挂载的DOM可变长度的,
+	 * @param {DOM} content DOM 挂载到Map的DOM元素
+	 * @param {number} offsetY DOM元素在地图上Y偏移,向上为负,向下为正
+	 * @memberof Marker
+	 */
+	setContentChangeAbleWidth(content,offsetY){
+		const vm = this
+		let overlay = new Overlay({
+			element: content,
+			positioning:"bottom-center",
+			offset:[0,offsetY]
+		})
+		vm.contentInfo.content = content
+		vm.contentInfo.offsetX = 0
+		vm.contentInfo.offsetY = offsetY
+		vm.contentInfo.overlay = overlay
+		vm.contentInfo.show = true
+		vm.map.addOverlay(overlay)
+		let coordinate = vm.lngLat
+		overlay.setPosition(proj.fromLonLat(coordinate))
+	}
+}
+export default Marker

+ 108 - 0
src/utils/ol-map/MultiPolygon.js

@@ -0,0 +1,108 @@
+import OLPolygon from 'ol/geom/MultiPolygon'
+import Feature from 'ol/Feature'
+import Fill from 'ol/style/Fill'
+import Stroke from 'ol/style/Stroke'
+import Style from 'ol/style/Style'
+import * as proj from 'ol/proj'
+import * as olExtent from 'ol/extent'
+import KBaseObject from './KBaseObject'
+/**
+ * @description KMap.Polygon 面标记类
+*/
+class MultiPolygon extends KBaseObject{
+  /**
+   * @param {*} positions
+   * @param {*} style
+   * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+   * @memberof Polygon
+   */
+  constructor(positions,style,options,mapInstance = null){
+    super(mapInstance)
+		const vm = this
+    const layer = vm.mapInstance.polygonLayer
+    vm.source = layer.getSource();
+    vm.style = vm.initStyle(style)
+    vm.polygon = vm.initFeature(positions,vm.style,options)
+    vm.source.addFeature(vm.polygon)
+  }
+  initStyle(style){
+    let fillColor = (style!=undefined && style.fillColor)?style.fillColor:'rgba(255,0,0,0.5)'
+    let strokeColor = (style!=undefined && style.strokeColor)?style.strokeColor:'rgba(255,0,0,1)'
+    let strokeWidth = (style!=undefined && style.strokeWidth)?style.strokeWidth:1
+    let fill = new Fill({
+      color: fillColor
+    })
+    let stroke = new Stroke({
+      color: strokeColor,
+      width: strokeWidth
+    })
+    let newStyle= new Style({
+      fill: fill,
+      stroke: stroke
+    })
+    return newStyle
+  }
+  initFeature(positions,style,options){
+    let geometry = new OLPolygon(positions);
+    if(options && options.projection){
+      let mapProjection  = this.map.getView().getProjection().getCode();
+      geometry.applyTransform(proj.getTransform(options.projection, mapProjection))
+    }
+    this.geometry = geometry;
+    //创建面标记
+    let polygon = new Feature({geometry:geometry})
+    polygon.setStyle(style)
+    return polygon
+  }
+
+  /**
+	 * 地图视角缩放到线标记范围
+	 * @param duration 动画持续时间(单位:毫秒) 选填,默认0毫秒
+	 */
+	zoomToExtent(duration) {
+    const vm = this
+		duration = (duration)? duration : 0
+		let extent = vm.polygon.getGeometry().getExtent()
+		// let extentBound = olExtent.boundingExtent(LonLatArray)
+		vm.map.getView().fit(extent,{
+			duration: duration
+		})
+	}
+
+  /**
+   * @description 显示
+   * @memberof Polygon
+   */
+  show(){
+    const vm = this
+		if(!vm.source.hasFeature(vm.polygon)) {
+			vm.source.addFeature(vm.polygon)
+		}
+  }
+
+  /**
+   * @description 隐藏
+   * @memberof Polygon
+   */
+  hide(){
+    const vm = this
+		if(vm.source.hasFeature(vm.polygon)) {
+			vm.source.removeFeature(vm.polygon)
+		}
+  }
+  /**
+   * @description 移除
+   * @memberof Polygon
+   */
+  remove(){
+    const vm = this
+		if(vm.source.hasFeature(vm.polygon)) {
+			vm.source.removeFeature(vm.polygon)
+		}
+  }
+  getGeometry(){
+    const vm = this;
+    return vm.geometry;
+  }
+}
+export default MultiPolygon

+ 55 - 0
src/utils/ol-map/Pixel.js

@@ -0,0 +1,55 @@
+/**
+ * @description KMap.Pixel 像素类
+*/
+class Pixel{
+  /**
+   * @description 像素类构造函数
+   * @param {number} x X像素,必填
+   * @param {number} y Y像素,必填
+   */
+  constructor(x,y){
+    let mapPixel = [Number(x),Number(y)];
+    this.pixel = mapPixel;
+  }
+
+  /**
+   * @description 获得X方向像素坐标
+   * @returns {number} 返回X方向像素坐标
+   */
+  getX() { 
+    return this.pixel[0]; 
+  }
+
+  /**
+   * @description 获得Y方向像素坐标
+   * @returns {number} 返回Y方向像素坐标
+   */
+  getY() { 
+    return this.pixel[1]; 
+  }
+
+  /**
+   * @description 当前像素坐标与传入像素坐标是否相等,必填
+   * @returns {boolean} 相等返回true,不相等返回false
+   */
+  equals(point) {
+      if(point.getX() == this.pixel[0] && point.getY() == this.pixel[1])
+      {
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+  }
+
+  /**
+   * @description 以字符串形式返回像素坐标对象
+   * @returns {String} 像素坐标字符串,用逗号连接
+   */
+  toString() { 
+    return this.pixel[0] + "," + this.pixel[1] ; 
+  }
+}
+
+export default Pixel

+ 108 - 0
src/utils/ol-map/Polygon.js

@@ -0,0 +1,108 @@
+import OLPolygon from 'ol/geom/Polygon'
+import Feature from 'ol/Feature'
+import Fill from 'ol/style/Fill'
+import Stroke from 'ol/style/Stroke'
+import Style from 'ol/style/Style'
+import * as proj from 'ol/proj'
+import * as olExtent from 'ol/extent'
+import KBaseObject from './KBaseObject'
+/**
+ * @description KMap.Polygon 面标记类
+*/
+class Polygon extends KBaseObject{
+  /**
+   * @param {*} positions
+   * @param {*} style
+   * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+   * @memberof Polygon
+   */
+  constructor(positions,style,options,mapInstance = null){
+    super(mapInstance)
+		const vm = this
+    const layer = vm.mapInstance.polygonLayer
+    vm.source = layer.getSource();
+    vm.style = vm.initStyle(style)
+    vm.polygon = vm.initFeature(positions,vm.style)
+    vm.source.addFeature(vm.polygon)
+  }
+  initStyle(style){
+    let fillColor = (style!=undefined && style.fillColor)?style.fillColor:'rgba(255,0,0,0.5)'
+    let strokeColor = (style!=undefined && style.strokeColor)?style.strokeColor:'rgba(255,0,0,1)'
+    let strokeWidth = (style!=undefined && style.strokeWidth)?style.strokeWidth:1
+    let fill = new Fill({
+      color: fillColor
+    })
+    let stroke = new Stroke({
+      color: strokeColor,
+      width: strokeWidth
+    })
+    let newStyle= new Style({
+      fill: fill,
+      stroke: stroke
+    })
+    return newStyle
+  }
+  initFeature(positions,style){
+    let newPositions = []
+    positions.forEach(function(position){
+      if(position instanceof Array)
+        newPositions.push(position) 
+      else{
+        newPositions.push([position.getLng(),position.getLat()])
+      }
+    })
+    let geometry = new OLPolygon([newPositions])
+    geometry.applyTransform(proj.getTransform('EPSG:4326', 'EPSG:3857'))
+    //创建面标记
+    let polygon = new Feature({geometry:geometry})
+    polygon.setStyle(style)
+    return polygon
+  }
+
+  /**
+	 * 地图视角缩放到线标记范围
+	 * @param duration 动画持续时间(单位:毫秒) 选填,默认0毫秒
+	 */
+	zoomToExtent(duration) {
+    const vm = this
+		duration = (duration)? duration : 0
+		let extent = vm.polygon.getGeometry().getExtent()
+		// let extentBound = olExtent.boundingExtent(LonLatArray)
+		vm.map.getView().fit(extent,{
+			duration: duration
+		})
+	}
+
+  /**
+   * @description 显示
+   * @memberof Polygon
+   */
+  show(){
+    const vm = this
+		if(!vm.source.hasFeature(vm.polygon)) {
+			vm.source.addFeature(vm.polygon)
+		}
+  }
+
+  /**
+   * @description 隐藏
+   * @memberof Polygon
+   */
+  hide(){
+    const vm = this
+		if(vm.source.hasFeature(vm.polygon)) {
+			vm.source.removeFeature(vm.polygon)
+		}
+  }
+  /**
+   * @description 移除
+   * @memberof Polygon
+   */
+  remove(){
+    const vm = this
+		if(vm.source.hasFeature(vm.polygon)) {
+			vm.source.removeFeature(vm.polygon)
+		}
+  }
+}
+export default Polygon

+ 297 - 0
src/utils/ol-map/Polyline.js

@@ -0,0 +1,297 @@
+import Common from './Common'
+import Style from 'ol/style/Style'
+import Stroke from 'ol/style/Stroke'
+import * as olExtent from 'ol/extent'
+import * as proj from 'ol/proj'
+import InfoWindow from './InfoWindow'
+import LngLat from './LngLat'
+import Feature from 'ol/Feature'
+import LineString from 'ol/geom/LineString'
+import KBaseObject from './KBaseObject'
+/**
+ * @description KMap.Polyline 线标记类
+*/
+class Polyline extends KBaseObject{
+	/**
+	 * @param {*} points
+	 * @param {*} style
+	 * @param {*} extData
+	 * @param {KMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @memberof Polyline
+	 */
+	constructor(points,style,extData,options,mapInstance = null) {
+    super(mapInstance)
+		//地图map对象
+    const vm = this
+    //地图线标记图层
+    vm.source = vm.mapInstance.polyLineLayer.getSource()
+		//线标记样式
+    extData = (extData)? extData : null
+
+    let point_Array = new Array()
+    for(let i=0; i<points.length; i++) {
+      let point = Common.KMapLngLat2MapLngLat(points[i])
+      point_Array.push(proj.fromLonLat(point))
+    }
+    
+    //创建线标记
+    let polyline = new Feature({
+      geometry: new LineString(point_Array),
+      properties: extData
+    })
+    vm.style = vm.initStyle(style)
+    polyline.setStyle(vm.style)
+
+    //线标记添加到地图图层
+    vm.source.addFeature(polyline)
+
+    //获取线标记
+    vm.polyline = polyline
+    vm.pointmoveFeature =null
+  }
+  initStyle(style){
+    //默认线路样式
+    let defStyle = {
+      color: "red",
+      width: 3,
+      lineCap: "round"
+    }
+    //需增加兼容老版本的代码
+    if(style != undefined && style.strokeColor != undefined){
+      defStyle.color = style.strokeColor
+    }
+    if(style != undefined && style.strokeWidth != undefined){
+      defStyle.width = style.strokeWidth
+    }
+    style = ( style != undefined ) ? Common.extend(defStyle,style,true) : defStyle
+    //设置线标记初始样式
+    let defaultStyle = new Style({
+      stroke: new Stroke(style),
+      zIndex: 0
+    })
+    return defaultStyle
+  }
+
+	/**
+	 * 获取id
+	 * @returns id
+	 */
+	getId() {
+    const vm = this
+		let id = vm.polyline.getId()
+		return id
+	}
+
+	/**
+	 * 设置id
+	 * @param id 线标记id值(尽量添加前缀不要纯数字),必填
+	 */
+	setId(id) {
+    const vm = this
+		vm.polyline.setId(id)
+	}
+
+	/**
+	 * 设置线标记样式
+	 * @param param JSON对象,线样式配置,选填,为空时使用默认配置
+	 * param.color 线颜色,选填,默认red
+	 * param.width 线宽度 选填,默认3
+	 * param.lineCap 线帽样式("butt"对接,"round"圆形,"square"方形) 选填,默认round
+	 */
+	setStyle(param) {
+    const vm = this
+		let color = (param != undefined && param.color != undefined )? param.color : "red"
+		let width = (param != undefined && param.width != undefined )? param.width : 3
+		let lineCap = (param != undefined && param.lineCap != undefined )? param.lineCap : "round"
+		let style = new Style({
+			stroke: new Stroke({
+				color: color,
+				width: width,
+				lineCap: lineCap
+			}),
+			zIndex: 0
+		})
+		vm.polyline.setStyle(style)
+	}
+
+	/**
+	 * 获取线标记端点坐标数组
+	 * @returns KMap.LngLat格式的经纬度数组集合
+	 */
+	getCoordinates() {
+    const vm = this
+		let points = []
+		let coordinates = vm.polyline.getGeometry().getCoordinates()
+		for(let i = 0 ; i < coordinates.length ; i ++)
+		{
+			let coordinate =  proj.toLonLat(coordinates[i]) //平面坐标转经纬度坐标
+			coordinate = Common.MapLngLat2KMapLngLat(coordinate)//Openlayers经纬度坐标转利通地图经纬度坐标
+			points.push(coordinate)
+		}
+		return points
+	}
+
+	/**
+	 * 显示线标记
+	 */
+	show() {
+    const vm = this
+		if(!vm.source.hasFeature(vm.polyline)) {
+			vm.source.addFeature(vm.polyline)
+		}
+	}
+
+	/**
+	 * 隐藏线标记
+	 */
+	hide() {
+    const vm = this
+		if(vm.source.hasFeature(vm.polyline)) {
+			vm.source.removeFeature(vm.polyline)
+		}
+	}
+
+	/**
+	 * 删除线标记
+	 */
+	remove() {
+    const vm = this
+		if(vm.source.hasFeature(vm.polyline)) {
+			vm.source.removeFeature(vm.polyline)
+		}
+	}
+
+	/**
+	 * 获取线标记叠加顺序
+	 * @param 线标记叠加顺序
+	 */
+	getZIndex() {
+    const vm = this
+		let index = vm.polyline.getStyle().getZIndex()
+		return index
+	}
+
+	/**
+	 * 设置线标记叠加顺序
+	 * @param index index值大的显示在上方,值相同时后加载的标记显示在上方,必填
+	 */
+	setZIndex(index) {
+    const vm = this
+		vm.polyline.getStyle().setZIndex(index)
+	}
+
+	/**
+	 * 地图视角缩放到线标记范围
+	 * @param duration 动画持续时间(单位:毫秒) 选填,默认0毫秒
+	 */
+	zoomToExtent(duration) {
+    const vm = this
+		duration = (duration)? duration : 0
+		let LonLatArray = vm.polyline.getGeometry().getCoordinates()
+		let extentBound = olExtent.boundingExtent(LonLatArray)
+		vm.map.getView().fit(extentBound,{
+			duration: duration
+		})
+	}
+
+	/**
+	 * 获取获取用户自定义属性
+	 * @param 用户自定义属性(支持数字,字符串,json格式)
+	 */
+	getExtData() {
+    const vm = this
+		let extData = vm.polyline.values_.properties
+		return extData
+	}
+
+	/**
+	 * 设置用户自定义属性
+	 * @param extData 用户自定义属性(支持数字,字符串,json格式),必填
+	 */
+	setExtData(extData) {
+    const vm = this
+		extData = (extData)? extData : null
+		if(extData != null) {
+			vm.polyline.values_.properties = extData
+		}
+	}
+
+	/**
+	 * 设置文本标记地图对象,传入null时,移除点标记,
+	 * 建议不使用此API,使用remove方法
+	 * @param map 传入null,移除点标记
+	 */
+	setMap(map) {
+    const vm = this
+		map = (map)? map : null;
+		if(map == null) {//当传入null时删除线标记
+			if(vm.source.hasFeature(vm.polyline)) {
+				vm.source.removeFeature(vm.polyline);
+			}
+		}
+	}
+
+	/**
+	 * 注册线标记事件
+	 * @param eventName 鼠标事件
+	 * "click":鼠标点击事件 "mousemove":鼠标悬停事件
+	 * @param callback 选中线标记时触发函数
+	 */
+	on(eventName,callback) {
+		let that = this
+    const vm = this
+		if(eventName == "pointermove"){
+			vm.map.on('pointermove',function(e) {
+				let pixel=vm.map.getEventPixel(e.originalEvent)
+				let feature=vm.map.forEachFeatureAtPixel(pixel,function (feature) {
+					return feature
+				})
+				if(feature != null && feature == vm.polyline){
+					that.pointmoveFeature = feature
+					callback(vm.polyline)
+				}
+				if(feature==undefined){
+					if(that.pointmoveFeature){
+						that.pointmoveFeature = null
+						callback(null)
+					}
+				}
+			})
+		}else{
+			vm.polyline.on(eventName,callback)
+		}
+	}
+
+	/**
+	 * 注销线标记事件(暂无该方法)
+	 */
+	off(eventName,callback) {}
+
+	/**
+	 * 线标记点击弹窗
+	 * @param param JSON对象,弹窗参数信息,必填
+	 * param.content 生成弹窗内容的回调函数,必填
+	 * param.offsetX X轴方向偏移量(正向右,负向左),选填参数,默认为0
+	 * param.offsetY Y轴方向偏移量(正向下,负向上),选填参数,默认为-25
+	 */
+	infoWindow(param){
+    const vm = this
+		let eventName = "click"
+		vm.polyline.on(eventName,function(e){
+			let lnglat = proj.toLonLat(e.event.coordinate)
+			lnglat = new LngLat(lnglat[0],lnglat[1])
+			let content = (param.content != undefined)? param.content : ""
+			let offsetX = (param.offsetX != undefined)? param.offsetX : ""
+			let offsetY = (param.offsetY != undefined)? param.offsetY : ""
+			let infoWindow = new InfoWindow({
+				type: "click",
+				position: lnglat,
+				content: content,
+				offsetX: offsetX,
+				offsetY: offsetY
+			})
+			infoWindow.open()
+		})
+  }
+}
+export default Polyline

+ 101 - 0
src/utils/ol-map/Popup.js

@@ -0,0 +1,101 @@
+import Overlay from 'ol/Overlay'
+import KBaseObject from './KBaseObject';
+import * as proj from 'ol/proj'
+class Popup extends KBaseObject{
+  constructor(lng,lat,url,offsetX,offsetY,width,height,mapInstance = null){
+    super(mapInstance)
+    const vm = this;
+    vm.position = [lng,lat];
+    vm.initPopup(lng,lat,url,offsetX,offsetY,width,height);
+
+  }
+  initPopup(lng,lat,url,offsetX,offsetY,width,height){
+    //创建popup的容器
+    const vm = this;
+    vm.popupContainer = document.createElement("div");
+    vm.popupContainer.style.position = "absolute";
+    vm.popupContainer.style.width = width + "px";
+    vm.popupContainer.style.height = height + "px";
+    vm.popupContainer.style.top = "0px";
+    vm.popupContainer.style.left = "0px";
+    vm.popupContainer.style.zIndex = 1;
+    // vm.popupContainer.style.display = "none";
+    vm.popupContainer.style.background = "url(" + url + ") no-repeat";
+    vm.popupContainer.style.backgroundSize = "100% 100%";
+    // vm.popupContainer.innerHTML = "点击查看";
+    //创建popup 
+    vm.popup = new Overlay({
+      element: vm.popupContainer,
+      positioning: 'center-center',
+      stopEvent: false,
+      position: proj.fromLonLat([lng,lat]),
+      offset: [offsetX,offsetY]
+    });
+    vm.mapInstance.map.addOverlay(vm.popup);    
+  }
+  setPostionAnimate(lng,lat,duration = 1000){
+    const vm = this;
+    let curLng = vm.position[0];
+    let curLat = vm.position[1];
+    let nextLng  = lng;
+    let nextLat  = lat;
+    // 计算经纬度的中间值差距
+    let latDiff = (nextLng - curLng) / 100;
+    let lngDiff = (nextLat - curLat) / 100;
+    let j = 0;
+    // 在当前坐标和下一个坐标之间插入 100 个中间值的经纬度
+    let timer = setInterval(function (){
+        let x = curLng + j * latDiff;
+        let y = curLat + j * lngDiff;
+        vm.popup.setPosition(proj.fromLonLat([x,y]));
+        j++;
+        if(j == 100){
+          clearInterval(timer)
+        }
+    }, duration / 100)
+
+  }
+  setPosition(x,y){
+    this.popup.setPosition(proj.fromLonLat([x,y]));
+  }
+
+  insertMiddleValues(coordinates) {
+    // 初始化一个新的数组
+    let result = [];
+
+    // 循环遍历所有的坐标
+    for (let i = 0; i < coordinates.length - 1; i++) {
+      // 获取当前坐标和下一个坐标
+      let current = coordinates[i];
+      let next = coordinates[i + 1];
+
+      // 计算经纬度的中间值差距
+      let latDiff = (next[0] - current[0]) / 200;
+      let lngDiff = (next[1] - current[1]) / 200;
+
+      // 将当前坐标添加到结果数组
+      result.push(current);
+
+      // 在当前坐标和下一个坐标之间插入 200 个中间值的经纬度
+      for (let j = 1; j < 200; j++) {
+        let lat = current[0] + j * latDiff;
+        let lng = current[1] + j * lngDiff;
+        result.push([lat, lng]);
+      }
+    }
+    // 添加最后一个坐标到结果数组
+    result.push(coordinates[coordinates.length - 1]);
+
+    return result;
+  }
+
+  remove(){
+    const vm = this;
+    if(vm.popup){
+      vm.mapInstance.map.removeOverlay(vm.popup);
+      vm.popup = null;
+    }
+  }
+}
+
+export default Popup;

+ 39 - 0
src/utils/ol-map/Size.js

@@ -0,0 +1,39 @@
+/**
+ * @description KMap.Size 大小类
+ */
+class Size{
+  /**
+   * @param {number} width 宽度
+   * @param {number} height 高度
+   * @constructor
+   */
+  constructor(width,height){
+    let mapSize = [Number(width),Number(height)]
+    this.size = mapSize
+  }
+
+  /**
+  * @description 获得宽度
+  * @returns {number} 宽度
+  */
+  getWidth() { 
+    return this.size[0]
+  };
+
+  /**
+  * @description 获取高度
+  * @returns {number} 高度
+  */
+  getHeight() { 
+    return this.size[1]
+  }
+
+  /**
+  * @description 以字符串形式返回尺寸大小对象
+  * @returns {number} 像素尺寸字符串,用逗号连接
+  */
+  toString() { 
+    return this.size[0] + "," + this.size[1]
+  }
+}
+export default Size

+ 88 - 0
src/utils/ol-map/VTLayer.js

@@ -0,0 +1,88 @@
+import Layer from 'ol/layer/VectorTile';
+import Vector from 'ol/layer/Vector';
+import VectorSource from 'ol/source/Vector';
+import Source from 'ol/source/VectorTile';
+import Common from './Common';
+import MVT from 'ol/format/MVT';
+import {createXYZ} from 'ol/tilegrid'
+import KBaseObject from './KBaseObject'
+/**
+ * @description KMap.VectorLayer 矢量图层
+ */
+class VTLayer extends KBaseObject{
+  /**
+   * @param {string} name 图层名称
+   * @param {string} zIndex 图层层级
+  */
+  constructor(url,options,zIndex,callback,mapInstance = null){
+    super(mapInstance)
+    const vm = this;
+    let styleFunVT = options.styleFunVT;
+    let styleFunV = options.styleFunV;
+    let funThis = options.funThis;
+    var source = new Source({
+        format: new MVT(),
+        url: url,
+        tileGrid: createXYZ({
+            tileSize: options.tileSize || 4096 // 瓦片大小
+        }),
+        tileLoadFunction:(tile, url)=>{
+            tile.setLoader(function(extent, resolution, projection) {
+                fetch(url).then(function(response) {
+                    response.arrayBuffer().then(function(data) {
+                        const format = tile.getFormat();
+                        const features = format.readFeatures(data, {
+                            extent: extent,
+                            featureProjection: projection
+                        });
+                        tile.setFeatures(features);
+                        callback(features);
+                    });
+                });
+            });
+        }
+    });
+
+    let ShowLevel = Common.ShowLevel
+    let minZoom = ShowLevel[0]
+    let maxZoom = ShowLevel[1]
+    if(options && options.minZoom){
+      minZoom = options.minZoom
+    }
+    if(options && options.maxZoom){
+      maxZoom = options.maxZoom
+    }
+    let layer = new Layer({
+      source: source,
+      zIndex: zIndex,
+      minZoom: minZoom,
+      maxZoom: maxZoom,
+      style:(evt)=>{
+        return styleFunVT.call(funThis,evt);
+      }
+    })
+    this.layer = layer
+    this.source = source,
+    this.vLayer = this.initVectorLayer(styleFunV,funThis,minZoom,maxZoom)
+  }
+  setMaxZoom(maxZoom){
+    this.layer.setMaxZoom(maxZoom);
+  }
+  setMinZoom(minZoom){
+    this.layer.setMinZoom(minZoom);
+  }
+  initVectorLayer(styleFunV,funThis,minZoom,maxZoom){
+    let layer = new Vector({
+        source:new VectorSource({}),
+        style:(evt)=>{
+            return styleFunV.call(funThis,evt);
+        },
+        minZoom: minZoom,
+        maxZoom: maxZoom,
+        zIndex:101
+    })
+    return layer;
+  }
+}
+
+export default VTLayer

+ 108 - 0
src/utils/ol-map/VectorLayer.js

@@ -0,0 +1,108 @@
+import Layer from 'ol/layer/Vector'
+import Source from 'ol/source/Vector'
+import Common from './Common'
+import Select from 'ol/interaction/Select.js';
+import {singleClick} from 'ol/events/condition'
+import * as olEvents from 'ol/events';
+/**
+ * @description KMap.VectorLayer 矢量图层
+ */
+class VectorLayer {
+  /**
+   * @param {string} name 图层名称
+   * @param {string} zIndex 图层层级
+  */
+  constructor(name,zIndex,options){
+    let source = new Source({
+    })
+    let ShowLevel = Common.ShowLevel
+    let minZoom = ShowLevel[0]
+    let maxZoom = ShowLevel[1]
+    let style = null;
+    let visible = true
+    if(options && options.source){
+      source = options.source
+    }
+    if(options && options.minZoom){
+      minZoom = options.minZoom
+    }
+    if(options && options.maxZoom){
+      maxZoom = options.maxZoom
+    }
+    if(options && options.style){
+      style = options.style
+    }
+    if(options && options.visible){
+      visible = options.visible
+    }
+    const layerOptions = {
+      source: source,
+      name: name,
+      visible: visible,
+      zIndex: zIndex,
+      minZoom: minZoom,
+      maxZoom: maxZoom,
+      className: 'ol-layer-' + name,
+    }
+    if (options && options.renderMode) {
+      layerOptions.renderMode = options.renderMode
+    }
+    if (options && options.updateWhileAnimating === false) {
+      layerOptions.updateWhileAnimating = false
+    }
+    if (options && options.updateWhileInteracting === false) {
+      layerOptions.updateWhileInteracting = false
+    }
+    let layer = new Layer(layerOptions)
+    if(style){
+      layer.setStyle(style)
+    }
+    this.layer = layer
+    this.source = source
+  }
+  setMaxZoom(maxZoom){
+    this.layer.setMaxZoom(maxZoom);
+  }
+  setMinZoom(minZoom){
+    this.layer.setMinZoom(minZoom);
+  }
+  addFeature(feature){
+    this.source.addFeature(feature)
+  }
+  refresh(){
+    this.source.refresh()
+  }
+  getFeatureById(id){
+    return this.source.getFeatureById(id)
+  }
+  addSingleSelect(callback,map,style){
+    let option = {
+        condition:singleClick,
+        layers:[this.layer],
+        multi:false
+    };
+    if(style){
+      option["style"] = style
+    }
+    this.singleSelect = new Select(option)
+    this.singleSelect.on("select",callback)
+    map.addInteraction(this.singleSelect)
+  }
+  addToggleSelect(callback,map,style){
+    let option = {
+      condition:singleClick,
+      toggleCondition: singleClick,
+      layers:[this.layer],
+      multi:true
+    };
+    if(style){
+      option["style"] = style
+    }
+    this.toggleSelect = new Select(option)
+    this.toggleSelect.on("select",callback)
+    map.addInteraction(this.toggleSelect)
+  }
+
+}
+
+export default VectorLayer

+ 214 - 0
src/utils/ol-map/VectorStyle.js

@@ -0,0 +1,214 @@
+
+import LTBaseObject from './KBaseObject'
+import XYZ from 'ol/source/XYZ'
+import Tile from 'ol/layer/Tile'
+import Common from './Common'
+import Style from "ol/style/Style";
+import Fill from "ol/style/Fill";
+import Stroke from "ol/style/Stroke";
+import Text from "ol/style/Text";
+import Icon from "ol/style/Icon";
+import Circle from "ol/style/Circle";
+import Photo from "ol-ext/style/Photo";
+import config from '../../api/config.js'
+import gybgImg from '@/assets/img/old_mini/gybg.png'
+
+const { base_img_url2 } = config
+/**
+ * @description 样式类
+*/
+class VectorStyle{
+	constructor(){
+		this.polymerImgLayerStyleCache = {}
+		this.getPointTextStyleCache = {}
+		this.resize = "?x-oss-process=image/resize,w_400";
+  	}
+
+  	getPolymerImgLayerStyle(feature){
+		let that = this
+		// let zoom = that.kmap.view.getZoom();
+		// let r = getRadius(zoom);
+		let r = 25;
+		let img = feature.get("src");
+		let count = feature.get("count");
+		let k = img + r;
+		if (img && img != "") {
+			let style = that.polymerImgLayerStyleCache[k];
+			if (!style) {
+				style = new Style({
+					image: new Photo({
+						src: base_img_url2 + img + that.resize,
+						radius: r,
+						shadow: 0,
+						crop: true,
+						kind: "square",
+						onload: function () {
+							that.polymerImgLayer.layer.changed();
+						},
+						displacement: [0, 30 + 15],
+						stroke: new Stroke({
+							width: 3,
+							color: "#fdfcfc",
+						}),
+					}),
+				});
+				that.polymerImgLayerStyleCache[k] = style;
+			}
+			return [style, that.getCount(count + "")];
+		} else {
+			return that.getPointSimpleStyle(10, "#00000000", "#00000000", 1);
+		}
+	}
+
+  	getGardenReportStyle(gardenPointLayer){
+		let style1 = new Style({
+			image: new Photo({
+				src: gybgImg,
+				radius: 36,
+				shadow: 0,
+				crop: false,
+				onload: function () {
+					gardenPointLayer.layer.changed();
+				},
+				displacement: [70, 0],
+				stroke: new Stroke({
+					width: 2,
+					color: "#fdfcfc00",
+				}),
+			}),
+		});
+		return style1
+	}
+
+	getLineStyle(fillColor, strokeColor, strokeWidth){
+		let style = new Style({
+			stroke: new Stroke({
+				color: strokeColor,
+				width: strokeWidth || 1,
+			}),
+		});
+		return style;
+	}
+
+	getPolygonStyle(fillColor, strokeColor, strokeWidth){
+		let style = new Style({
+			fill: new Fill({
+				color: fillColor
+			}),
+			stroke: new Stroke({
+				color: strokeColor,
+				width: strokeWidth || 1,
+			}),
+		});
+		return style;
+	}
+	getPointStyle(src, scale, anchor, text, font, textColor){
+		let textObj = null;
+		text &&	(textObj = new Text({
+			text:text,
+			stroke: new Stroke({
+				color: textColor,
+				width: 1,
+			}),
+			fill: new Fill({
+				color: textColor,
+			}),
+			font:font
+		}))
+		let style = new Style({
+			image: new Icon({
+				text:textObj,
+				src:src,
+				scale:scale,
+				anchor:anchor,
+			})
+		});
+		return style
+	}
+
+	/**
+	 *
+	 * @param radius 半径
+	 * @param fillColor 填充颜色
+	 * @param strokeColor 边框颜色
+	 * @param strokeWidth 边框宽度
+	 * @returns {Style}
+	 */
+	getPointSimpleStyle(radius, fillColor, strokeColor, strokeWidth){
+		let style = new Style({
+			image: new Circle({
+				radius: radius,                             // 半径
+				stroke: new Stroke({           // 边界样式
+					color: strokeColor,                    // 边界颜色
+					width: strokeWidth                           // 边界宽度
+				}),
+				fill: new Fill({               // 填充样式
+					color: fillColor                       // 填充颜色
+				})
+			})
+		});
+		return style
+	}
+
+	/**
+	 *
+	 * @param text
+	 * @param fillColor 填充颜色
+	 * @param strokeColor 边框颜色
+	 * @param strokeWidth 边框宽度
+	 * @returns {Style}
+	 */
+	getPointTextStyle(text, fillColor, strokeColor, strokeWidth, fontSize){
+		if(this.getPointTextStyleCache[text]){
+			return this.getPointTextStyleCache[text]
+		}
+		let style = new Style({
+			text: new Text({
+				text:text,
+				stroke: new Stroke({
+					color: strokeColor,
+					width: strokeWidth,
+				}),
+				fill: new Fill({
+					color: fillColor,
+				}),
+				font: fontSize+"px sans-serif"
+			}),
+		});
+		this.getPointTextStyleCache[text] = style
+		return style
+	}
+
+	/**
+	 *
+	 * @param text
+	 * @returns {Style}
+	 */
+	getCount(text){
+		let style = new Style({
+			text: new Text({
+				text:text,
+				stroke: new Stroke({
+					color: "#ffffff",
+					width: 1,
+				}),
+				fill: new Fill({
+					color: "#ffffff",
+				}),
+				font: "24rpx sans-serif",
+				backgroundFill: new Fill({
+						color: "#0c0c0c",
+				}),
+				backgroundStroke: new Stroke({
+					color: "#cccccc",
+					width: 1,
+				}),
+				offsetX:20,
+				offsetY:-70	,
+				padding:[4, 5, 0, 5]
+			}),
+		});
+		return style
+	}
+}
+export default VectorStyle

+ 327 - 0
src/utils/ol-map/WMSLayer.js

@@ -0,0 +1,327 @@
+import Image from 'ol/layer/Image'
+import ImageWMS from 'ol/source/ImageWMS'
+import {GeoJSON,WFS} from 'ol/format'
+import * as filter from 'ol/format/filter'
+import * as extent from 'ol/extent'
+import KBaseObject from './KBaseObject'
+import Projection from 'ol/proj/Projection'
+import TileWMS from 'ol/source/TileWMS'
+import Tile from 'ol/layer/Tile'
+/**
+ * @description LTMap.WMSLayer WMS图层类
+*/
+class WMSLayer extends KBaseObject{
+	/**
+	 * @description 构造函数
+	 * @param {String} url wms图层服务地址
+	 * @param {LTMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @memberof WMSLayer
+	 */
+	constructor(url,layerName,mapInstance = null){
+    super(mapInstance)
+		const vm = this
+		vm.format = "image/png"
+		vm.initImageLayer(url,layerName)
+		// vm.initTileLayer(url,layerName)
+  }
+	
+	/**
+		* @description 初始化ImageLayer
+		* @memberof WMSLayer
+		*/
+	initImageLayer(url,layerName){
+		const vm = this
+
+		var projection = new Projection({
+			code: 'EPSG:4326',
+			units: 'degrees',
+			axisOrientation: 'neu',
+			global: true
+		})
+		
+    vm.source = new ImageWMS({
+      url: url,
+			ratio: 1,
+      params: {
+				'FORMAT':vm.format,
+        "LAYERS": layerName,
+				"STYLES": '',
+				'VERSION': '1.1.1',
+				"exceptions": 'application/vnd.ogc.se_inimage'
+      },
+      serverType: 'geoserver',
+      crossOrigin: 'anonymous',
+			projection:projection
+    })
+
+		vm.layer = new Image({
+			source:vm.source
+		})
+
+    vm.state = true
+
+    //地图加载WMS图层
+    vm.map.addLayer(vm.layer)
+	}
+
+	/**
+	 * @description 切片加载wms图层
+	 * @param {*} url
+	 * @param {*} layerName
+	 * @memberof WMSLayer
+	 */
+	initTileLayer(url,layerName){
+		const vm = this
+
+		var projection = new Projection({
+			code: 'EPSG:4326',
+			units: 'degrees',
+			axisOrientation: 'neu',
+			global: true
+		})
+
+		vm.source = new TileWMS({
+			url: url,
+			params: {
+				'FORMAT': vm.format, 
+				'VERSION': '1.1.1',
+				tiled: true,
+				"STYLES": '',
+				"LAYERS": layerName,
+				"exceptions": 'application/vnd.ogc.se_inimage',
+				// tilesOrigin: -124.73142200000001 + "," + 24.955967
+			},
+			projection:projection
+		})
+
+		vm.layer = new Tile({
+			source:vm.source
+		})
+
+		vm.map.addLayer(vm.layer)
+	}
+
+  /**
+	* @description 添加WMS图层到地图
+	* @memberof WMSLayer
+	*/
+  add(){
+		const vm = this
+		if(!vm.state) {
+			vm.map.addLayer(vm.layer)
+			vm.state = true
+		}
+	}
+
+	/**
+	 * @description 从当前地图中移除WMS图层
+	 * @memberof WMSLayer
+	 */
+	remove() {
+		const vm = this
+		if(vm.state) {
+			vm.map.removeLayer(vm.layer)
+			vm.state = false
+		}
+	}
+
+	/**
+	 * @description 显示WMS图层数据
+	 * @memberof WMSLayer
+	 */
+	show(){
+		const vm = this
+		if(vm.state) {
+			vm.layer.setVisible(true)
+		}
+	}
+
+	/**
+	 * @description 隐藏WMS图层数据
+	 * @memberof WMSLayer
+	 */
+	hide(){
+		const vm = this
+		if(vm.state) {
+			vm.layer.setVisible(false)
+		}
+	}
+
+	/**
+	 * @description 更新WMS图层内容
+	 * @param {String} data 属性过滤参数(例如:RoadNo=11),选填,不传值默认加载全部数据
+	 * @memberof WMSLayer
+	 */
+	update(data) {
+		const vm = this
+		if(data!=null && data!="" && data!=undefined && state) {
+			vm.source.updateParams({
+				"CQL_FILTER": data
+			})
+		}
+		else{
+			vm.source.updateParams({
+				"CQL_FILTER": null
+			})
+		}
+	}
+
+	/**
+	 * @description 缩放到过滤后的地图要素对应范围
+	 * @memberof WMSLayer
+	 */
+	zoomTo() {
+    const vm = this
+		//组装查询过滤条件
+		let cqlfilter = vm.source.getParams().CQL_FILTER
+		if(cqlfilter == null){
+			//缩放到图层范围
+		}
+		else{
+			let cqlfilters = cqlfilter.split("=")
+			//实现思路为通过WFS进行属性查询,查找到要素后再缩放到该要素
+			let wfsurl = url.substr(0, url.lastIndexOf("/") + 1 ) + "wfs"
+			let featureRequest = new WFS().writeGetFeature({
+        //srsName: 'EPSG:3857',
+        //featureNS: 'http://openstreemap.org',
+        //featurePrefix: 'skyline',
+        featureTypes: [layer],
+        outputFormat: 'application/json',
+        filter: filter.equalTo(cqlfilters[0],cqlfilters[1])
+			})
+			let myfilter = WFS.writeFilter(filter.equalTo(cqlfilters[0],cqlfilters[1]))
+			$.ajax({
+				type: 'get',
+				url: wfsurl,
+				data: {
+					service: 'WFS',
+          request: 'GetFeature',
+          typeNames: layer,//查询图层
+          filter:new XMLSerializer().serializeToString(myfilter),//查询条件
+          outputFormat: 'application/json'
+				},
+				async : false,
+				success: function(res) {
+					let features = new GeoJSON().readFeatures(res)
+					  //let coordinateArray = new Array();
+					  let extentBound = new extent.createEmpty()
+						for(let i=0; i<features.length; i++) {
+							/*let coordinates =  features[i].getGeometry().getCoordinates();//由多个分部构成,与Polyline函数生成的结构不一致
+							for(j=0; j<coordinates.length; j++) {
+								let pathcoordinates = coordinates[j];
+								for(k = 0 ; k < pathcoordinates.length ; k ++)
+								coordinateArray.push(pathcoordinates[k]);
+							}*/
+							extentBound = new extent.extend(extentBound,features[i].getGeometry().getExtent());
+						}
+						//let extentBound = new extent.boundingExtent(coordinateArray);
+						if(features.length > 0){
+							vm.map.getView().fit(extentBound,{
+								duration: 1
+							})
+						}
+				},
+				error: function(e) {
+					console.log("failed")
+				}
+			})
+			/*fetch(wfsurl, {
+				method: 'POST',
+				body: new XMLSerializer().serializeToString(featureRequest)
+			}).then(function(response) {
+				return response.json();
+			}).then(function(json) {
+				let features = new GeoJSON().readFeatures(json);
+				let extentBound = new extent.createEmpty();
+				for(let i=0; i<features.length; i++) {
+					extentBound = new extent.extend(extentBound,features[i].getGeometry().getExtent());
+				}
+				if(features.length > 0)
+				{
+					map.getView().fit(extentBound,{
+						duration: 1
+					});
+				}
+			});*/
+		}
+	}
+	
+	/**
+	 *@description 根据经纬度坐标查询坐标点所在位置的要素
+	 *@param coordinate LTMap.LngLat格式的经纬度 必填
+	 *@returns 查询到要素的结果集合,json数组格式,json中包含对应要素的所有字段名和字段值
+	*/
+	getFeaturesByCoor(coordinate){
+    const vm = this
+		//将经纬度坐标转平面坐标
+		coordinate = vm.mapInstance.lnglatToPixel(coordinate)
+    //将LT平面坐标转换为OL的平面坐标
+		coordinate = Common.LTMapPixel2MapPixel(coordinate)
+		let resolution = vm.mapInstance.getResolution()
+		let projection = vm.mapInstance.getProjection()
+    let url = vm.source.getGetFeatureInfoUrl(coordinate, resolution, projection,
+    {'INFO_FORMAT': 'application/json', 'FEATURE_COUNT': 10})
+      let geojsonFormat = new GeoJSON({
+    	defaultDataProjection: "EPSG:3857"
+		})
+    let result = []
+		if(url) {
+			$.ajax({
+				type: 'get',
+				url: url,
+				async : false,
+				success: function(res) {
+          //获取所有的要素
+					let features = geojsonFormat.readFeatures(res)
+					let jsonDatas = []
+					for(let i = 0 ; i < features.length ; i ++){
+						let jsonData = {}
+					    for(let key in features[i].getProperties()){
+						    if (key == 'geometry'){
+	                continue
+	              }
+	              jsonData[key] = features[i].get(key)
+					    }
+					    jsonDatas.push(jsonData)
+					}
+					result = jsonDatas
+				},
+				error:function(e) {
+					console.log("faile")
+				}
+			})
+		}
+		return result
+	}
+
+	/**
+	 *@description 设置鼠标悬停在元素上时的样式
+	 *@param {String} cursorStyle 鼠标样式("default"默认指针,"pointer"小手,
+   *"move"移动指针,"text"文本指针,
+   *"wait"等待状态,"help"帮助)
+   *必填,由于跨域问题,该方法存在问题
+	*/
+	setFeatureCursor(cursorStyle){
+    const vm = this
+		cursorStyle = (cursorStyle == undefined)?"default":cursorStyle
+		let mapContainer = vm.map.getTargetElement()
+		let defaultCursor = mapContainer.style.cursor
+		vm.map.on("pointermove",function(e){
+			//let features = map.forEachFeatureAtPixel(e.pixel,function(feature) { return feature; })
+			if (e.dragging){
+		    return
+		  }
+			let pixel = vm.map.getEventPixel(e.originalEvent)
+	    let hit = vm.map.forEachLayerAtPixel(pixel,function(){
+	      return true
+	    })
+			if(hit){
+				mapContainer.style.cursor = cursorStyle
+			}
+			else{
+				mapContainer.style.cursor = defaultCursor
+			}
+		})
+	}
+}
+export default WMSLayer

+ 111 - 0
src/utils/ol-map/WMTSLayer.js

@@ -0,0 +1,111 @@
+import LTBaseObject from './KBaseObject'
+import TileLayer from 'ol/layer/Tile'
+import sourceWMTS from 'ol/source/WMTS';
+import WMTSTileGrid from 'ol/tilegrid/WMTS';
+import * as olExtent from 'ol/extent';
+/**
+ * @description KMap.WMTSLayer WMS图层类
+*/
+class WMTSLayer extends LTBaseObject{
+	/**
+	 * @description 构造函数
+	 * @param {String} url wms图层服务地址
+	 * @param {LTMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @memberof WMTSLayer
+	 */
+	constructor(wmtsData,projection,mapInstance = null){
+		super(mapInstance)
+		const vm = this;
+		vm.initLayer(wmtsData,projection)
+	}
+
+	/**
+		* @description 初始化ImageLayer
+		* @memberof WMTSLayer
+		*/
+	initLayer(wmtsData,projection){
+		const vm = this;
+		let projectionExtent = projection.getExtent();
+    let size = olExtent.getWidth(projectionExtent) / 256;
+    let resolutions = new Array(19);
+    let matrixIds = new Array(19);
+    for (var z = 1; z < 19; ++z) {
+        // generate resolutions and matrixIds arrays for this WMTS
+        resolutions[z] = size / Math.pow(2, z);
+        matrixIds[z] = z;
+    }
+
+    let source = new sourceWMTS({
+        url: wmtsData.url,
+        layer: wmtsData.layer,
+        matrixSet: wmtsData.matrixSet,
+        format: 'tiles',
+        projection: projection,
+        tileGrid: new WMTSTileGrid({
+            origin: olExtent.getTopLeft(projectionExtent),
+            resolutions: resolutions,
+            matrixIds: matrixIds
+        }),
+        style: 'default',
+        wrapX: true
+    })
+    let layer = new TileLayer({
+        zIndex:wmtsData.zIndex,
+        source: source,
+        // visible: false,
+    });
+    layer.id = wmtsData.layer+"_"+wmtsData.matrixSet;
+		vm.layer = layer;
+		vm.source = source;
+		vm.map.addLayer(vm.layer)
+    return layer;
+    //地图加载WMS图层
+	}
+
+  /**
+	* @description 添加WMS图层到地图
+	* @memberof WMTSLayer
+	*/
+  add(){
+		const vm = this
+		if(!vm.state) {
+			vm.map.addLayer(vm.layer)
+			vm.state = true
+		}
+	}
+
+	/**
+	 * @description 从当前地图中移除WMS图层
+	 * @memberof WMTSLayer
+	 */
+	remove() {
+		const vm = this
+		if(vm.state) {
+			vm.map.removeLayer(vm.layer)
+			vm.state = false
+		}
+	}
+
+	/**
+	 * @description 显示WMS图层数据
+	 * @memberof WMTSLayer
+	 */
+	show(){
+		const vm = this
+		if(vm.state) {
+			vm.layer.setVisible(true)
+		}
+	}
+
+	/**
+	 * @description 隐藏WMS图层数据
+	 * @memberof WMTSLayer
+	 */
+	hide(){
+		const vm = this
+		if(vm.state) {
+			vm.layer.setVisible(false)
+		}
+	}
+}
+export default WMTSLayer

+ 113 - 0
src/utils/ol-map/XYZLayer.js

@@ -0,0 +1,113 @@
+
+import LTBaseObject from './KBaseObject'
+import XYZ from 'ol/source/XYZ'
+import Tile from 'ol/layer/Tile'
+import Common from './Common'
+/**
+ * @description LTMap.XYZLayer XYZ图层类
+*/
+class XYZLayer extends LTBaseObject{
+	/**
+	 * @description 构造函数
+	 * @param {String} url XYZ图层服务地址
+	 * @param {LTMap.Map} [mapInstance=null] map对象,单地图的时候可不传,多地图时候需要传
+	 * @memberof XYZLayer
+	 */
+	constructor(url,options,zIndex,mapInstance = null){
+    super(mapInstance)
+		const vm = this
+		vm.initXYZLayer(url,options,zIndex)
+  }
+	/**
+	 * @description 切片加载XYZ图层
+	 * @param {*} url
+	 * @param {*} layerName
+	 * @memberof XYZLayer
+	 */
+	 initXYZLayer(url,options,zIndex){
+		var minZoom = Common.BaseLayerZoom[0];
+		var maxZoom = Common.BaseLayerZoom[1];
+		var className = "ol-layer"
+		if(options && options.minZoom != undefined){
+			minZoom = options.minZoom
+		}
+		if(options && options.maxZoom != undefined){
+			maxZoom = options.maxZoom
+		}
+		if(options && options.name) {
+			className = options.name
+		}
+		const vm = this
+		vm.source = new XYZ({
+      url : url
+    })
+
+		vm.layer = new Tile({
+			source:vm.source,
+			maxZoom:maxZoom,
+			minZoom:minZoom,
+			zIndex:zIndex,
+			className: className,
+		})
+
+		vm.map.addLayer(vm.layer)
+	}
+	setProperty(properties){
+		const vm = this;
+		vm.properties = properties;
+	}
+	getProperty(){
+		const vm = this;
+		return vm.properties?vm.properties:null;
+	}
+  /**
+	* @description 添加XYZ图层到地图
+	* @memberof XYZLayer
+	*/
+  add(){
+		const vm = this
+		if(!vm.state) {
+			vm.map.addLayer(vm.layer)
+			vm.state = true
+		}
+	}
+
+	/**
+	 * @description 从当前地图中移除XYZ图层
+	 * @memberof XYZLayer
+	 */
+	remove() {
+		const vm = this
+		if(vm.state) {
+			vm.map.removeLayer(vm.layer)
+			vm.state = false
+		}
+	}
+
+	/**
+	 * @description 显示XYZ图层数据
+	 * @memberof XYZLayer
+	 */
+	show(){
+		const vm = this
+		vm.layer.setVisible(true)
+	}
+
+	/**
+	 * @description 隐藏XYZ图层数据
+	 * @memberof XYZLayer
+	 */
+	hide(){
+		const vm = this
+		vm.layer.setVisible(false)
+	}
+
+	/**
+	 * @description 缩放到过滤后的地图要素对应范围
+	 * @memberof XYZLayer
+	 */
+	zoomTo() {
+    const vm = this
+	}
+}
+export default XYZLayer

+ 88 - 0
src/utils/ol-map/css/KMap.css

@@ -0,0 +1,88 @@
+.ol-mouse-position-KMap{
+  color:#66b166;
+  position: absolute;
+  font-weight: 500;
+  bottom:8px;
+  right:8px;
+}
+/*鹰眼控件展开时的控件外边框*/
+.myOverview:not(.ol-collapsed){
+  border:1px solid black;
+}
+/*鹰眼控件地图容器边框样式*/
+.myOverview .ol-overviewmap-map{
+  border:none;
+  width:200px;
+  height: 100px;
+}
+/*鹰眼控件中显示当前窗口区域的边框样式*/
+.myOverview .ol-overviewmap-box{
+  border:2px solid red;
+}
+ /*鹰眼控件展开时其控件按钮图标的样式*/
+.myOverview:not(.ol-collapsed) button{
+  bottom:auto;
+  left:auto;
+  right:1px;
+  top:1px;
+}
+.ol-overviewmap{
+  left: auto !important;
+  right:0.5em;
+  bottom: 0.5em;
+}
+/*2D视图中添加自定义视图旋转控制器的样式*/
+.brightmap2d-rotate-control-custom {
+  position: absolute;
+  bottom: 90px;
+  right: 9px;
+  width: 52px;
+  height: 54px;
+  background: url(./rotate.png) 0% 0% / 266px no-repeat;
+}
+
+.center2d-button-custom {
+  position: absolute;
+  outline: none;
+  border: none;
+  background: url(./rotate.png) -56px -4px / 266px no-repeat;
+  cursor: pointer;
+  left: 19px;
+  top: 4px;
+  width: 14px;
+  height: 44px;
+  transform: rotate(0deg);
+}
+
+.right2d-button-custom {
+  position: absolute;
+  outline: none;
+  border: none;
+  background: url(./rotate.png) -75px -5px / 266px no-repeat;
+  right: 2px;
+  top: 5px;
+  width: 15px;
+  height: 42px;
+  transform: scaleX(-1);
+}
+
+.left2d-button-custom {
+  position: absolute;
+  outline: none;
+  border: none;
+  background: url(./rotate.png) -75px -5px / 266px no-repeat;
+  left: 2px;
+  top: 5px;
+  width: 15px;
+  height: 42px;
+}
+
+.left2d-button-custom:hover {
+  cursor: pointer;
+  background: url(./rotate.png) -89px -5px / 266px no-repeat;
+}
+
+.right2d-button-custom:hover {
+  cursor: pointer;
+  background: url(./rotate.png) -89px -5px / 266px no-repeat;
+}

BIN
src/utils/ol-map/css/rotate.png


+ 389 - 0
src/views/adopt_map/index.vue

@@ -0,0 +1,389 @@
+<template>
+    <div class="adopt-map-page">
+        <div class="garden-info" v-if="gardenObj">
+            <div class="panel-title">
+                <div class="title-l">
+                    <div class="title-info">
+                        <div class="title-garden">{{ gardenObj.farmName || '果园地图' }}</div>
+                        <div class="btn-second" v-if="gardenObj.youweiIndex != null">
+                            有味指数
+                            <text>{{ gardenObj.youweiIndex }}分</text>
+                        </div>
+                    </div>
+                </div>
+                <div class="title-r" v-if="gardenObj.people != null">
+                    <span class="guard-label">守护人数</span>
+                    <span class="guard-val">{{ gardenObj.people }}</span>
+                </div>
+            </div>
+        </div>
+
+        <div class="select-tips" v-if="enterSelectTree">请选择你想守护的树</div>
+
+        <div ref="mapRef" class="map"></div>
+
+        <div class="top-mask"></div>
+
+        <BaseFooter />
+    </div>
+</template>
+
+<script setup>
+import IndexMap from './map/index.js'
+import RegionLayer from './map/regionLayer.js'
+import ClusterPointsLayer from './map/clusterPointsLayer.js'
+import { onMounted, onUnmounted, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { useStore } from 'vuex'
+import { SET_TOKEN } from '@/store/modules/app/type.js'
+import BaseFooter from '@/components/BaseFooter.vue'
+
+const store = useStore()
+const route = useRoute()
+const mapRef = ref(null)
+const indexMap = new IndexMap()
+let regionLayer = null
+let clusterPointsLayer = null
+
+const enterSelectTree = ref(false)
+const gardenObj = ref(null)
+const currenFarmId = ref(null)
+
+const defaultCenter = 'POINT(111.0105596 21.77287165)'
+
+function isEnterSelectTreeQuery(val) {
+    if (val === undefined || val === null || val === '') return false
+    if (val === '0' || val === 'false') return false
+    return true
+}
+
+async function initAuth() {
+    const userId = route.query.userId
+    if (!userId) return
+    const token = route.query.token
+    if (!token) {
+        const { data } = await VE_API.system.devLogin({ userId })
+        store.dispatch(`app/${SET_TOKEN}`, data.token)
+    } else {
+        store.dispatch(`app/${SET_TOKEN}`, token)
+    }
+    localStorage.setItem('MINI_USER_ID', userId)
+    store.commit('home/SET_MINI_USER_ID')
+}
+
+function getGardenInfo() {
+    return VE_API.lj_home
+        .farmBuyInfo({ farmId: currenFarmId.value })
+        .then(({ data }) => {
+            gardenObj.value = data || {}
+        })
+}
+
+function getBlueRegionList() {
+    return VE_API.region
+        .list({ farmId: currenFarmId.value })
+        .then((res) => {
+            //   const list = Array.isArray(res?.data) ? res.data : []
+            const list = [
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 2,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "分区2",
+                    "pointCount": 131,
+                    "pointWkt": "POINT (113.6142086995688 23.585836479509055)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-2",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61348988377155 23.58617369800422, 113.61395837633405 23.58622555407246, 113.61414076654707 23.58622555407246, 113.61436964832843 23.5862774101407, 113.61478628474084 23.586468741036786, 113.61491681896747 23.58652417337007, 113.61512424307672 23.586449071517652, 113.61491503077356 23.585742756367924, 113.61479880177082 23.585596129050916, 113.61473800497858 23.585451289764112, 113.61438931780664 23.585286780835393, 113.61410858003889 23.585299297865365, 113.61385645239149 23.585249229909184, 113.61378135053907 23.585288569029306, 113.61357571446003 23.585283204611276, 113.61347200232355 23.585342213209604, 113.61338080721704 23.585506721974614, 113.61335934954492 23.585665866485304, 113.61342193444922 23.58616296916816, 113.61348988377155 23.58617369800422)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 9,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "分区9",
+                    "pointCount": 63,
+                    "pointWkt": "POINT (113.61725048541882 23.585262743588892)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-9",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61697301904715 23.585749021838886, 113.617320420354 23.585742359348075, 113.61752600578478 23.585708095109652, 113.61772112158744 23.5856785897931, 113.61789720170187 23.58559102562808, 113.61787340709186 23.585330236701996, 113.61793812843106 23.58526932250014, 113.61795526055036 23.585167481569073, 113.61793146594025 23.585022810339957, 113.61790671954574 23.58491525870246, 113.61779345720193 23.58473346788171, 113.61779821612386 23.584601169849783, 113.61771921801855 23.584509798547092, 113.61763260563801 23.584517412822354, 113.61755741467023 23.584640193010298, 113.61736515422085 23.584823887399946, 113.61726902399624 23.58487623554194, 113.61709484745067 23.584864814129187, 113.61707105284067 23.584962847922696, 113.61699586187265 23.585023762124397, 113.61689877986377 23.585071351344393, 113.61675981934107 23.58515796372515, 113.61663132844662 23.585192227963518, 113.61646000725422 23.58524076896813, 113.61634484134152 23.585320718857897, 113.61628392713976 23.585360693802862, 113.6162163504473 23.585397813394497, 113.61601552393823 23.585480618637533, 113.61596127222741 23.585499654325552, 113.61597174185577 23.585538677485918, 113.61598125969975 23.585561520311693, 113.61608690776846 23.585591025628073, 113.61610689524092 23.585611013100582, 113.61619350762157 23.58557960421532, 113.6162886860618 23.58552820785755, 113.61632104673151 23.585492040050294, 113.61638196093314 23.585453968674216, 113.61646095903859 23.585480618637543, 113.6164771393734 23.585505365031985, 113.61649617506146 23.585564375664962, 113.61648760900198 23.58561006131614, 113.61645048941011 23.58567192730245, 113.61649807863034 23.585676686224392, 113.61662466595578 23.585595784550236, 113.61671698904298 23.585495847187836, 113.61676553004737 23.585587218490588, 113.61681787818966 23.585559616742817, 113.61689211737293 23.5854558722431, 113.61695588692798 23.585499654325556, 113.61697301904715 23.585749021838886)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 29,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "分区8",
+                    "pointCount": 40,
+                    "pointWkt": "POINT (113.61587084394066 23.58618878122994)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-8",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61516876894466 23.58617727723074, 113.6153952936325 23.58616014511155, 113.61551426668275 23.586215348606817, 113.61564275757719 23.586290539574595, 113.61565798612749 23.58640951262483, 113.61577695917796 23.58637905552401, 113.61584168051715 23.586392380505654, 113.615912112563 23.58637048946448, 113.61613483011318 23.586200120056528, 113.6162547549478 23.586365730542376, 113.61623762282863 23.586448535785365, 113.61615196223237 23.586476137533136, 113.61634435485792 23.586946782901485, 113.61654695275945 23.586932042261843, 113.61651364030526 23.58667220511998, 113.61648127963554 23.586552280285296, 113.61640894402115 23.58641903046899, 113.61636135480092 23.586292443143435, 113.6163308977001 23.586121121951034, 113.61631471736528 23.58605735239606, 113.61618622647107 23.586071629162152, 113.61612911940675 23.58603260600161, 113.61610342122786 23.58595265611185, 113.61601109814079 23.585864140162432, 113.61585310193013 23.585803225960632, 113.61557898802205 23.585730890346124, 113.6153714990225 23.585723276070855, 113.61520493675204 23.585882224066058, 113.61516591359151 23.586056400611785, 113.61516876894466 23.58617727723074)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 12828,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "分区5",
+                    "pointCount": 45,
+                    "pointWkt": "POINT (113.61692837797158 23.58435388968608)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-5",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61726902399624 23.584875938109313, 113.61732981922493 23.584649032707855, 113.61741785928217 23.58458954618279, 113.6176284415812 23.584287354634938, 113.61761892373723 23.584146966435544, 113.61762249292879 23.584041080420736, 113.61758442155264 23.584010147427676, 113.61752374529698 23.583939953328095, 113.6174499820059 23.583857861923292, 113.61723702024568 23.5839113997961, 113.61706331959229 23.583947091711153, 113.61689794705232 23.583967317129648, 113.61673019505157 23.58399468093132, 113.61659218631306 23.584025613924393, 113.61641848565968 23.584054167456387, 113.61624716446725 23.584093428562902, 113.61621623147425 23.584286164904498, 113.61622218012678 23.584531249388036, 113.6162186109351 23.58466211974327, 113.61629713314835 23.584684724622857, 113.6166052733487 23.58470970896351, 113.61669926205845 23.584731124112597, 113.61678016373266 23.584778713332653, 113.61680276861217 23.584819164169826, 113.61683846052725 23.58481916416982, 113.61689080866942 23.584775144141148, 113.61697884872665 23.58482035390024, 113.61709484745067 23.584864516696555, 113.61726902399624 23.584875938109313)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 172047,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "认养分区",
+                    "pointCount": null,
+                    "pointWkt": "POINT (113.61573550741103 23.586606696698798)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY认养分区",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61553138381385 23.586555391814727, 113.61578836560238 23.586770495089638, 113.61593018147823 23.586692924660838, 113.61571507820327 23.5864349910878, 113.61553138381385 23.586555391814727)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 175094,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "12",
+                    "pointCount": null,
+                    "pointWkt": "POINT (113.61860504330416 23.583859757151316)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-12",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61790764544605 23.58437349638575, 113.61810434755591 23.58448136528483, 113.61834039008784 23.58457781277093, 113.61887973458238 23.58461715319288, 113.61901298439875 23.584476289101246, 113.61925029597637 23.584407760624345, 113.61937973865497 23.58437349638575, 113.61932390063691 23.584164103817216, 113.61923506742585 23.5838836446801, 113.61920587937084 23.583660292607078, 113.61927060071025 23.58345216908441, 113.61940004338908 23.5832846550295, 113.6191449651692 23.58325927411221, 113.61896095351813 23.583223740827805, 113.61884166320624 23.58314759807564, 113.61873379430745 23.58305115058954, 113.6185180565094 23.583087952919755, 113.61840257333529 23.58318947658927, 113.61825028783096 23.583260543158076, 113.61815510939073 23.583216126552543, 113.61807262140928 23.583183131360045, 113.61779216227194 23.583299883580025, 113.61792541208831 23.58360953077215, 113.61817668317042 23.583615876001545, 113.61831374012445 23.583843035212283, 113.61829216634476 23.583925523193727, 113.61821348550086 23.5839014113223, 113.6179825191524 23.583770699597665, 113.6178530764738 23.583793542423393, 113.61783784792328 23.583905218459847, 113.617946985868 23.58400166594589, 113.61795713823494 23.584085422973374, 113.61786703597829 23.58419963710162, 113.61785815265716 23.58428847031246, 113.61790764544605 23.58437349638575)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 175095,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "13",
+                    "pointCount": null,
+                    "pointWkt": "POINT (113.61907779775447 23.582774628335315)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-13",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.6185180565094 23.583087952919755, 113.61873379430756 23.58305115058954, 113.61884166320624 23.58314759807564, 113.61896095351813 23.583223740827805, 113.6191449651692 23.58325927411221, 113.61940004338908 23.5832846550295, 113.61952853428329 23.58303148037845, 113.61974554112703 23.582682175502953, 113.61942574156774 23.58233191884284, 113.61909737594897 23.58227005285653, 113.61872713181651 23.58242614549863, 113.61843207865172 23.58267265765886, 113.61843112686734 23.583051467850964, 113.6185180565094 23.583087952919755)))"
+                },
+                {
+                    "areaMu": null,
+                    "farmCode": "LBY",
+                    "farmId": 766,
+                    "id": 175096,
+                    "isGenerateReport": 1,
+                    "lastViewTime": null,
+                    "name": "15",
+                    "pointCount": null,
+                    "pointWkt": "POINT (113.61729343325402 23.581078231145653)",
+                    "regionAreaId": null,
+                    "regionCode": "LBY-MR-15",
+                    "schemeId": null,
+                    "typeId": null,
+                    "wkt": "MULTIPOLYGON (((113.61579634969576 23.580895993441118, 113.61623258421344 23.58139885286704, 113.61657522659812 23.581547965756727, 113.61743341886756 23.5818731587608, 113.61737155288142 23.58216345300349, 113.61798704012836 23.582252286214498, 113.61852479831566 23.58088806190449, 113.61799973058703 23.58035347633188, 113.61769991850042 23.580115530231183, 113.61644039047451 23.580399479244647, 113.61574558786083 23.580797642386187, 113.61579634969576 23.580895993441118)))"
+                }
+            ]
+            if (!list.length) {
+                console.warn('[adopt_map] 分区列表为空', res)
+            }
+            regionLayer.initData(list)
+        })
+        .catch((err) => {
+            console.error('[adopt_map] region/list 请求失败', err)
+        })
+}
+
+function getPointList() {
+    if (enterSelectTree.value || !clusterPointsLayer) {
+        return Promise.resolve()
+    }
+    return VE_API.variety
+        .getBuyTreeList({ farmId: currenFarmId.value })
+        .then(({ data }) => {
+            clusterPointsLayer.setData(data, false)
+        })
+}
+
+
+onMounted(async () => {
+    currenFarmId.value = route.query.farmId || 766
+
+    await initAuth()
+
+    // URL 带 enterSelectTree 即进入选分区模式(不再依赖 getLightTree 是否有 id)
+    if (isEnterSelectTreeQuery(route.query.enterSelectTree)) {
+        enterSelectTree.value = true
+        try {
+            const { data } = await VE_API.lj_home.getLightTree({ speciesId: '1' })
+            if (data?.id) {
+                // 已有分配的树时可在此扩展其它逻辑,选分区 UI 仍保持开启
+                console.log('[adopt_map] 用户已有点亮树', data.id)
+            }
+        } catch (e) {
+            console.warn('[adopt_map] getLightTree', e)
+        }
+    }
+
+    indexMap.initMap(defaultCenter, mapRef.value)
+    regionLayer = new RegionLayer(indexMap.kmap, {
+        enterSelectTree: enterSelectTree.value,
+        onRegionSelect: (region) => {
+            console.log('[adopt_map] 选中分区', region)
+        },
+    })
+    if (!enterSelectTree.value) {
+        clusterPointsLayer = new ClusterPointsLayer(indexMap.kmap)
+    }
+
+    await Promise.all([getGardenInfo(), getBlueRegionList(), getPointList()])
+
+    regionLayer.setEnterSelectTree(enterSelectTree.value)
+
+})
+
+</script>
+
+<style scoped lang="less">
+.adopt-map-page {
+    width: 100%;
+    height: 100vh;
+    background: #fff;
+    position: relative;
+    overflow: hidden;
+
+    .map {
+        width: 100%;
+        height: 100%;
+    }
+
+    .select-tips {
+        position: absolute;
+        z-index: 3;
+        top: 100px;
+        left: 50%;
+        transform: translateX(-50%);
+        pointer-events: none;
+        background: rgba(255, 255, 255, 0.6);
+        color: #000;
+        font-size: 12px;
+        padding: 7px 25px;
+        border-radius: 30px;
+    }
+
+    .garden-info {
+        position: absolute;
+        z-index: 3;
+        top: 0;
+        left: 0;
+        width: 100%;
+
+        .panel-title {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 12px;
+
+            .title-l {
+                flex: 1;
+
+                .title-garden {
+                    color: #fff;
+                    font-size: 14px;
+                    font-weight: 600;
+                    padding-bottom: 5px;
+                }
+
+                .btn-second {
+                    padding: 0 15px;
+                    border-radius: 20px;
+                    font-size: 12px;
+                    color: #f3c11d;
+                    border: 1px solid #f3c11d;
+                    background: rgba(255, 217, 94, 0.28);
+                    height: 21px;
+                    line-height: 20px;
+                    width: fit-content;
+                }
+            }
+
+            .title-r {
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                font-size: 12px;
+                padding: 6px 12px;
+                background: rgba(255, 255, 255, 0.9);
+                border-radius: 8px;
+
+                .guard-val {
+                    color: #f3c11d;
+                    font-weight: 600;
+                    font-size: 16px;
+                }
+            }
+        }
+    }
+
+    .top-mask {
+        position: absolute;
+        z-index: 2;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 120px;
+        pointer-events: none;
+        background: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
+    }
+
+    .bottom-mask {
+        position: absolute;
+        z-index: 2;
+        bottom: 0;
+        left: 0;
+        width: 100%;
+        height: 80px;
+        pointer-events: none;
+        background: linear-gradient(180deg,
+                rgba(255, 255, 255, 0) 0%,
+                rgba(255, 255, 255, 0.7) 100%);
+    }
+}
+</style>
+
+<style lang="less">
+/* 仅压暗底图瓦片,避免 .ol-layer 把分区和点位一起压到看不见 */
+// .dark .ol-map-clearness {
+//   filter: brightness(0.35);
+// }
+</style>

+ 387 - 0
src/views/adopt_map/map/clusterPointsLayer.js

@@ -0,0 +1,387 @@
+import Style from 'ol/style/Style'
+import Photo from 'ol-ext/style/Photo'
+import { newPoint } from '@/utils/map.js'
+import VectorLayer from 'ol/layer/Vector.js'
+import { Cluster, Vector as VectorSource } from 'ol/source.js'
+import { Vector } from 'ol/layer.js'
+import * as KMap from '@/utils/ol-map/KMap.js'
+import { Fill, Text, Icon, Stroke } from 'ol/style.js'
+// import eventBus from '@/api/eventBus.js'
+import { extractCoordinates } from '@/common/commonFun.js'
+
+import treeImg from '@/assets/img/map/tree.png'
+import treeActiveImg from '@/assets/img/map/tree-active.png'
+import activeBgImg from '@/assets/img/map/active-bg.png'
+import hereImg from '@/assets/img/map/here.png'
+import progressImg from '@/assets/img/map/progress.png'
+import progressActiveImg from '@/assets/img/map/progress-active.png'
+
+/**
+ * 果树聚合点位图层
+ */
+class ClusterPointsLayer {
+  constructor(kmap) {
+    const that = this
+    this.cloudFilenameCache = {}
+    this.statusTitleStyleCache = {}
+    this.textBgStyleCache = {}
+    this.bgStyleCache = {}
+    this.activeBgStyleCache = {}
+    this.progressBgStyleCache = {}
+    this.enterSelectTree = false
+    this.selectableActiveTree = null
+    that.initLayer(kmap)
+    that.kmap = kmap
+
+    // that.addMapSingerClick()
+
+    this.currentPoint = null
+  }
+
+  addMapSingerClick() {
+    const that = this
+    that.kmap.on('singleclick', (evt) => {
+      that.kmap.map.forEachFeatureAtPixel(evt.pixel, function (feature, layer) {
+        if (layer instanceof VectorLayer && layer.get('name') === 'clusterPointsLayer') {
+          const fs = that.selectFeature(feature)
+          // if (that.selectableActiveTree) {
+          //   that.selectableActiveTree.set('selectableActive', false)
+          // }
+          // if (that.enterSelectTree) {
+          //   // eventBus.emit('showTreePopup', fs.get('sampleId'))
+          //   that.selectableActiveTree = fs
+          //   fs.set('selectableActive', true)
+          //   return
+          // }
+          // const miniUserId = fs.get('miniUserId') && !fs.get('virtual')
+          // const isMy = fs.get('my')
+          // if (isMy) {
+          //   localStorage.setItem('showMapText', true)
+          // } else if (miniUserId) {
+          //   // eventBus.emit('adoptMap:friendTree', {
+          //   //   sampleId: fs.get('sampleId'),
+          //   // })
+          // } else {
+          //   // eventBus.emit('showTreePopup', fs.get('sampleId'))
+          //   that.selectableActiveTree = fs
+          //   fs.set('selectableActive', true)
+          // }
+        }
+      })
+    })
+  }
+
+  initLayer(kmap) {
+    const that = this
+    this.vectorStyle = new KMap.VectorStyle()
+    this.clusterSource = new VectorSource({})
+    this.clusterLayer = new Vector({
+      source: new Cluster({
+        distance: 40,
+        source: this.clusterSource,
+      }),
+      className: 'ol-layer-cluster',
+      name: 'clusterPointsLayer',
+      minZoom: 14.6,
+      maxZoom: 22,
+      zIndex: 1001,
+      style: function (feature) {
+        const f = that.selectFeature(feature)
+        const isVisible = f.get('visible') !== false
+        const selectableActive = f.get('selectableActive')
+        if (!isVisible) {
+          return []
+        }
+        if (that.enterSelectTree || selectableActive) {
+          return new Style({
+            image: new Icon({
+              src: selectableActive ? treeActiveImg : treeImg,
+              scale: 0.55,
+            }),
+          })
+        }
+        const isOther = f.get('miniUserId') ? true : false
+        const isHigh = f.get('my')
+        if (!isHigh && !isOther) {
+          return new Style({
+            image: new Icon({
+              src: treeImg,
+              scale: 0.55,
+            }),
+          })
+        }
+        const img = f.get('icon')
+        const count = 30
+        const startColor = isHigh ? '#FFD887' : '#FFFFFFb3'
+        const endColor = isHigh ? '#ED9E1E' : '#FFFFFFb3'
+        const styles = [
+          that.pointStyle(img, isHigh),
+          that.progressStyle(count, isHigh),
+          that.textBgStyle(count, startColor, endColor, isHigh),
+        ]
+        if (isHigh) {
+          styles.push(
+            that.activePointBgStyle(),
+            that.statusTitleStyle(
+              f.get('treeName'),
+              0,
+              -36,
+              '#FFFFFF',
+              '#B38A00',
+              14,
+              isHigh,
+            ),
+          )
+          if (!localStorage.getItem('showMapText')) {
+            styles.push(that.activeTextStyle())
+          }
+        }
+        return styles
+      },
+    })
+    kmap.addLayer(this.clusterLayer)
+    this.kmap = kmap
+  }
+
+  setVisible(visible) {
+    if (this.clusterLayer) {
+      this.clusterLayer.setVisible(visible)
+    }
+  }
+
+  setData(data, enterSelectTreeVal) {
+    const that = this
+    this.enterSelectTree = enterSelectTreeVal
+    if (enterSelectTreeVal) {
+      this.setVisible(false)
+      this.clusterSource.clear()
+      return
+    }
+    this.setVisible(true)
+    this.clusterSource.clear()
+    const features = []
+    if (!data || !data.length) return
+    const myPoint = data.find((item) => item.my)
+    for (const item of data) {
+      try {
+        const f = newPoint(item, 'geom', 'cluster-point')
+        features.push(f)
+      } catch (err) {
+        console.log('err', err)
+      }
+    }
+    this.clusterSource.addFeatures(features)
+    this.kmap.getView().fit(this.clusterSource.getExtent(), {
+      duration: 1000,
+      padding: [0, 10, 0, 10],
+    })
+    if (myPoint) {
+      setTimeout(() => {
+        const pointPosition = extractCoordinates(myPoint.geom)
+        const view = this.kmap.getView()
+        view.animate({
+          duration: 1200,
+          zoom: 20,
+          center: pointPosition,
+        })
+      }, 1100)
+    }
+  }
+
+  textBgStyle(count, startColor, endColor, isHigh) {
+    const key = count + startColor + endColor + isHigh
+    let style = this.textBgStyleCache[key]
+    let length = 5
+    let offsetX = 0
+    let highOffsetX = 0
+
+    if (count === 0) {
+      length = 0
+    } else if (count < 7) {
+      length = 10
+      offsetX = 14
+      highOffsetX = 14
+    } else if (count > 7 && count <= 14) {
+      length = 20
+      offsetX = 6
+      highOffsetX = 7
+    } else if (count > 14 && count <= 21) {
+      length = 28
+      offsetX = 4
+      highOffsetX = 5
+    } else if (count > 21 && count <= 29) {
+      length = 34
+      offsetX = 2
+      highOffsetX = 3
+    } else if (count === 30) {
+      length = 38
+      offsetX = 0
+      highOffsetX = 0
+    }
+    if (!style) {
+      style = new Style({
+        renderer: function (coordinates, state) {
+          const ctx = state.context
+          const x =
+            coordinates[0] -
+            (isHigh ? highOffsetX * state.pixelRatio : offsetX * state.pixelRatio)
+          const y =
+            coordinates[1] + (isHigh ? 24 * state.pixelRatio : 17 * state.pixelRatio)
+          const width = isHigh
+            ? (length + 10) * state.pixelRatio
+            : length * state.pixelRatio
+          const height = isHigh ? 10 * state.pixelRatio : 6 * state.pixelRatio
+          const cornerRadius = isHigh ? 5 * state.pixelRatio : 3 * state.pixelRatio
+          const gradient = ctx.createLinearGradient(x - width / 2, y, x + width / 2, y)
+          gradient.addColorStop(0, startColor)
+          gradient.addColorStop(1, endColor)
+          ctx.beginPath()
+          ctx.moveTo(x - width / 2 + cornerRadius, y - height / 2)
+          ctx.lineTo(x + width / 2 - cornerRadius, y - height / 2)
+          ctx.arc(
+            x + width / 2 - cornerRadius,
+            y - height / 2 + cornerRadius,
+            cornerRadius,
+            -Math.PI / 2,
+            0,
+          )
+          ctx.lineTo(x + width / 2, y + height / 2 - cornerRadius)
+          ctx.arc(
+            x + width / 2 - cornerRadius,
+            y + height / 2 - cornerRadius,
+            cornerRadius,
+            0,
+            Math.PI / 2,
+          )
+          ctx.lineTo(x - width / 2 + cornerRadius, y + height / 2)
+          ctx.arc(
+            x - width / 2 + cornerRadius,
+            y + height / 2 - cornerRadius,
+            cornerRadius,
+            Math.PI / 2,
+            Math.PI,
+          )
+          ctx.lineTo(x - width / 2, y - height / 2 + cornerRadius)
+          ctx.arc(
+            x - width / 2 + cornerRadius,
+            y - height / 2 + cornerRadius,
+            cornerRadius,
+            Math.PI,
+            -Math.PI / 2,
+          )
+          ctx.closePath()
+          ctx.fillStyle = gradient
+          ctx.fill()
+        },
+        zIndex: isHigh ? 12 : 10,
+      })
+      this.textBgStyleCache[key] = style
+    }
+    return style
+  }
+
+  statusTitleStyle(statusName, offsetX, offsetY, color, strokeColor, fontSize, isHigh) {
+    const key = statusName + '-' + offsetY
+    let style = this.statusTitleStyleCache[key]
+    if (!style) {
+      style = new Style({
+        text: new Text({
+          text: statusName,
+          offsetX: offsetX,
+          offsetY: offsetY,
+          font: isHigh ? 'bold 14px sans-serif' : '12px sans-serif',
+          fill: new Fill({ color }),
+          stroke: new Stroke({ color: strokeColor }),
+        }),
+        zIndex: 13,
+      })
+      this.statusTitleStyleCache[key] = style
+    }
+    return style
+  }
+
+  pointStyle(img, isHigh) {
+    const key = img + isHigh
+    let bgStyle = this.bgStyleCache[key]
+    if (!bgStyle) {
+      bgStyle = new Style({
+        image: new Photo({
+          src: img,
+          kind: 'circle',
+          radius: isHigh ? 23 : 17,
+          shadow: 0,
+          crop: false,
+          displacement: [0, 0],
+          stroke: new Stroke({
+            width: isHigh ? 0 : 1,
+            color: isHigh ? '#fff' : '#C7C7C7',
+          }),
+        }),
+        zIndex: isHigh ? 11 : 0,
+      })
+      this.bgStyleCache[key] = bgStyle
+    }
+    return bgStyle
+  }
+
+  activePointBgStyle() {
+    return new Style({
+      image: new Icon({
+        src: activeBgImg,
+        scale: 0.55,
+      }),
+      zIndex: 10,
+    })
+  }
+
+  activeTextStyle() {
+    return new Style({
+      image: new Icon({
+        src: hereImg,
+        scale: 0.5,
+        displacement: [0, 108],
+      }),
+      zIndex: 15,
+    })
+  }
+
+  progressStyle(count, isHigh) {
+    const key = count + isHigh
+    let progressBgStyle = this.progressBgStyleCache[key]
+    if (!progressBgStyle) {
+      progressBgStyle = new Style({
+        image: new Icon({
+          src: isHigh ? progressActiveImg : progressImg,
+          scale: 0.5,
+          displacement: isHigh ? [0, -48] : [0, -34],
+        }),
+        zIndex: isHigh ? 12 : 0,
+      })
+      this.progressBgStyleCache[key] = progressBgStyle
+    }
+    return progressBgStyle
+  }
+
+  selectFeature(feature) {
+    const fs = feature.get('features')
+    if (!fs || fs.length === 0) {
+      return feature
+    }
+    if (fs.length === 1) {
+      return fs[0]
+    }
+    for (const item of fs) {
+      if (item.get('my')) {
+        return item
+      }
+    }
+    for (const item of fs) {
+      if (item.get('miniUserId')) {
+        return item
+      }
+    }
+    return fs[0]
+  }
+}
+
+export default ClusterPointsLayer

+ 23 - 0
src/views/adopt_map/map/index.js

@@ -0,0 +1,23 @@
+import * as KMap from '@/utils/ol-map/KMap.js'
+import * as util from '@/common/ol_common.js'
+import config from '@/api/config.js'
+/**
+ * @description 地图层对象
+ */
+class IndexMap {
+  constructor() {
+    let that = this;
+    let vectorStyle = new KMap.VectorStyle();
+    this.vectorStyle = vectorStyle;
+  }
+
+  initMap(location, target) {
+    let level = 18;
+    let coordinate = util.wktCastGeom(location).getFirstCoordinate();
+    this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 12, 22);
+    let xyz = config.base_img_url + "map/lby/{z}/{x}/{y}.png";
+    this.mapLayer = this.kmap.addXYZLayer(xyz, { minZoom: 14.6, maxZoom: 22, name: "ol-map-clearness" }, 2);
+  }
+}
+
+export default IndexMap;

+ 102 - 0
src/views/adopt_map/map/regionInfoCard.css

@@ -0,0 +1,102 @@
+.region-info-overlay-root {
+  pointer-events: none;
+}
+
+.region-info-card {
+  position: relative;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  min-width: 62px;
+  max-width: 220px;
+  padding: 4px 10px 4px 4px;
+  background: #fff;
+  border: 1px solid #fff;
+  border-radius: 6px;
+  box-sizing: border-box;
+  pointer-events: auto;
+  cursor: default;
+}
+
+.region-info-card.is-selectable {
+  cursor: pointer;
+}
+
+.region-info-card.is-selected {
+  border: 1px solid #FFA617;
+}
+
+.region-info-card__check {
+  display: none;
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 18px;
+  height: 12px;
+}
+
+.region-info-card.is-selected .region-info-card__check {
+  display: flex;
+  background: url('@/assets/img/map/checked.png') no-repeat center center;
+  background-size: 100% 100%;
+}
+
+/* .region-info-card.is-selected .region-info-card__check::after {
+  content: '';
+  width: 8px;
+  height: 5px;
+  border-left: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  transform: rotate(-45deg) translateY(-1px);
+} */
+
+.region-info-card__img {
+  flex-shrink: 0;
+  width: 57px;
+  height: 57px;
+  border-radius: 2px;
+  object-fit: cover;
+  background: #fff;
+}
+
+.region-info-card__body {
+  flex: 1;
+  min-width: 0;
+}
+
+.region-info-card__name {
+  font-size: 14px;
+  color: #000000;
+  line-height: 20px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.region-info-card__row {
+  font-size: 12px;
+  line-height: 18px;
+  color: #000;
+}
+
+.region-info-card__label {
+  color: rgba(0, 0, 0, 0.3);
+}
+
+.region-info-card__arrow {
+  position: absolute;
+  left: 50%;
+  bottom: -4px;
+  width: 8px;
+  height: 8px;
+  background: #fff;
+  border-right: 1px solid #fff;
+  border-bottom: 1px solid #fff;
+  transform: translateX(-50%) rotate(45deg);
+}
+
+.region-info-card.is-selected .region-info-card__arrow {
+  border-color: #f3a81d;
+  bottom: -4px;
+}

+ 94 - 0
src/views/adopt_map/map/regionInfoCard.js

@@ -0,0 +1,94 @@
+import './regionInfoCard.css'
+import defaultRegionImg from '@/assets/img/map/lichi.png'
+import config from '@/api/config.js'
+
+function pickField(item, keys, fallback = '') {
+  for (const key of keys) {
+    const val = item[key]
+    if (val !== undefined && val !== null && val !== '') {
+      return val
+    }
+  }
+  return fallback
+}
+
+function resolveImageUrl(item) {
+  const raw = pickField(item, ['icon', 'image', 'img', 'speciesIcon', 'varietyIcon'])
+  if (!raw) return defaultRegionImg
+  if (/^https?:\/\//i.test(raw)) return raw
+  if (raw.startsWith('/')) return raw
+  return config.base_img_url2 + raw
+}
+
+/**
+ * @param {Record<string, unknown>} item 分区数据
+ * @param {{ enterSelectTree?: boolean, selected?: boolean, onClick?: () => void }} options
+ */
+export function createRegionInfoCard(item, options = {}) {
+  const { enterSelectTree = false, selected = false, onClick } = options
+
+  const el = document.createElement('div')
+  el.className = 'region-info-card'
+  if (enterSelectTree) el.classList.add('is-selectable')
+  if (selected) el.classList.add('is-selected')
+
+  const check = document.createElement('div')
+  check.className = 'region-info-card__check'
+
+  const img = document.createElement('img')
+  img.className = 'region-info-card__img'
+  img.alt = ''
+  img.src = resolveImageUrl(item)
+  img.onerror = () => {
+    img.onerror = null
+    img.src = defaultRegionImg
+  }
+
+  const body = document.createElement('div')
+  body.className = 'region-info-card__body'
+
+  const name = document.createElement('div')
+  name.className = 'region-info-card__name'
+  name.textContent = pickField(item, ['name', 'regionName'], '分区')
+
+  const varietyRow = document.createElement('div')
+  varietyRow.className = 'region-info-card__row'
+  const varietyLabel = document.createElement('span')
+  varietyLabel.className = 'region-info-card__label'
+  varietyLabel.textContent = '品种:'
+  const varietyVal = document.createElement('span')
+  varietyVal.textContent = pickField(item, ['pz', 'speciesName', 'varietyName', 'variety'], '--')
+  varietyRow.append(varietyLabel, varietyVal)
+
+  const ageRow = document.createElement('div')
+  ageRow.className = 'region-info-card__row'
+  const ageLabel = document.createElement('span')
+  ageLabel.className = 'region-info-card__label'
+  ageLabel.textContent = '树龄:'
+  const ageVal = document.createElement('span')
+  const age = pickField(item, ['age', 'treeAge'])
+  ageVal.textContent = age !== '' && age != null ? `${age}年` : '树龄'
+  ageRow.append(ageLabel, ageVal)
+
+  body.append(name, varietyRow, ageRow)
+
+  const arrow = document.createElement('div')
+  arrow.className = 'region-info-card__arrow'
+
+  el.append(check, img, body, arrow)
+
+  if (enterSelectTree && onClick) {
+    el.addEventListener('click', (e) => {
+      e.stopPropagation()
+      onClick()
+    })
+  }
+
+  return el
+}
+
+export function updateRegionInfoCard(el, { enterSelectTree, selected }) {
+  if (!el) return
+  el.classList.toggle('is-selectable', !!enterSelectTree)
+  el.classList.toggle('is-selected', !!selected)
+}

+ 281 - 0
src/views/adopt_map/map/regionLayer.js

@@ -0,0 +1,281 @@
+import * as KMap from '@/utils/ol-map/KMap.js'
+import * as util from '@/common/ol_common.js'
+import VectorLayer from 'ol/layer/Vector.js'
+import { Vector as VectorSource } from 'ol/source'
+import { newRegionFeature } from '@/utils/map.js'
+import Style from 'ol/style/Style'
+import Fill from 'ol/style/Fill'
+import Stroke from 'ol/style/Stroke'
+import Overlay from 'ol/Overlay'
+import { getCenter } from 'ol/extent'
+import { createRegionInfoCard, updateRegionInfoCard } from './regionInfoCard.js'
+
+const FILL_DEFAULT = 'rgba(0, 0, 0, 0.06)'
+const FILL_SELECTED = 'rgba(255, 166, 23, 0.25)'
+const STROKE_DEFAULT = '#ffffff'
+const STROKE_SELECTED = '#FFA617'
+
+/**
+ * 地图分区图层 + 分区信息框
+ */
+class RegionLayer {
+  constructor(kmap, options = {}) {
+    const that = this
+    this.kmap = kmap
+    this.enterSelectTree = !!options.enterSelectTree
+    this.onRegionSelect = options.onRegionSelect
+    this.selectedRegionId = null
+    this.regionOverlays = []
+    this._regionClickHandler = null
+    this._selectedEntry = null
+    this._styleCache = {
+      normal: new Style({
+        fill: new Fill({ color: FILL_DEFAULT }),
+        stroke: new Stroke({ color: STROKE_DEFAULT, width: 1 }),
+      }),
+      selected: new Style({
+        fill: new Fill({ color: FILL_SELECTED }),
+        stroke: new Stroke({ color: STROKE_SELECTED, width: 2 }),
+      }),
+    }
+
+    this.regionLayer = new KMap.VectorLayer('regionLayer', 99, {
+      minZoom: 14.6,
+      renderMode: 'image',
+      updateWhileAnimating: false,
+      updateWhileInteracting: false,
+      style: (f) => that.getRegionStyle(f),
+    })
+
+    kmap.addLayer(this.regionLayer.layer)
+  }
+
+  get map() {
+    return this.kmap.map
+  }
+
+  getRegionStyle(feature) {
+    return feature.get('selected')
+      ? [this._styleCache.selected]
+      : [this._styleCache.normal]
+  }
+
+  getFeatureCenter(feature, item) {
+    if (item?.pointWkt) {
+      try {
+        return util.wktCastGeom(item.pointWkt).getFirstCoordinate()
+      } catch {
+        /* ignore */
+      }
+    }
+    const geom = feature.getGeometry()
+    if (!geom) return null
+    const type = geom.getType()
+    if (type === 'Polygon' && typeof geom.getInteriorPoint === 'function') {
+      return geom.getInteriorPoint().getCoordinates()
+    }
+    if (type === 'MultiPolygon' && typeof geom.getInteriorPoints === 'function') {
+      const points = geom.getInteriorPoints()
+      return points.getPoint(0).getCoordinates()
+    }
+    return getCenter(geom.getExtent())
+  }
+
+  clearRegionInfoOverlays() {
+    this.regionOverlays.forEach(({ overlay }) => {
+      this.map.removeOverlay(overlay)
+    })
+    this.regionOverlays = []
+  }
+
+  clearRegionClick() {
+    if (this._regionClickHandler) {
+      this.kmap.off('singleclick', this._regionClickHandler)
+      this._regionClickHandler = null
+    }
+  }
+
+  bindRegionClick() {
+    this.clearRegionClick()
+    const that = this
+    this._regionClickHandler = (evt) => {
+      if (!that.enterSelectTree) return
+      let hit = null
+      that.map.forEachFeatureAtPixel(
+        evt.pixel,
+        (feature, layer) => {
+          if (layer !== that.regionLayer.layer) return
+          const entry = that.regionOverlays.find((r) => r.feature === feature)
+          if (entry) hit = entry
+        },
+        {
+          layerFilter: (layer) => layer === that.regionLayer.layer,
+          hitTolerance: 5,
+        },
+      )
+      if (hit) that.selectRegion(hit)
+    }
+    this.kmap.on('singleclick', this._regionClickHandler)
+  }
+
+  selectRegion(entry) {
+    if (!this.enterSelectTree || !entry) return
+    if (this._selectedEntry === entry) return
+
+    const prev = this._selectedEntry
+    if (prev) {
+      prev.feature.set('selected', false)
+      updateRegionInfoCard(prev.el, {
+        enterSelectTree: true,
+        selected: false,
+      })
+    }
+
+    entry.feature.set('selected', true)
+    this._selectedEntry = entry
+    this.selectedRegionId = entry.item.id ?? entry.item.regionId ?? entry.feature.getId()
+    updateRegionInfoCard(entry.el, {
+      enterSelectTree: true,
+      selected: true,
+    })
+
+    // 合并为一次图层刷新,减轻 Canvas 矢量层整层重绘闪烁
+    const source = this.regionLayer.layer.getSource()
+    if (source) {
+      source.changed()
+    }
+
+    if (typeof this.onRegionSelect === 'function') {
+      this.onRegionSelect(entry.item, entry.feature)
+    }
+  }
+
+  addRegionInfoOverlay(item, feature) {
+    const coord = this.getFeatureCenter(feature, item)
+    if (!coord) return null
+
+    const entry = { item, feature, el: null, overlay: null }
+    const el = createRegionInfoCard(item, {
+      enterSelectTree: this.enterSelectTree,
+      selected: false,
+      onClick: () => this.selectRegion(entry),
+    })
+    entry.el = el
+
+    const overlay = new Overlay({
+      element: el,
+      position: coord,
+      positioning: 'bottom-center',
+      stopEvent: this.enterSelectTree,
+      offset: [0, -4],
+    })
+    entry.overlay = overlay
+    this.map.addOverlay(overlay)
+    if (this.enterSelectTree) this.bindCardClick(entry)
+    return entry
+  }
+
+  bindCardClick(entry) {
+    if (!entry.el || entry.el._regionSelectBound) return
+    entry.el.addEventListener('click', (e) => {
+      e.stopPropagation()
+      if (this.enterSelectTree) this.selectRegion(entry)
+    })
+    entry.el._regionSelectBound = true
+  }
+
+  setEnterSelectTree(val) {
+    this.enterSelectTree = !!val
+    this.regionOverlays.forEach((r) => {
+      updateRegionInfoCard(r.el, {
+        enterSelectTree: this.enterSelectTree,
+        selected: !!r.feature.get('selected'),
+      })
+      if (r.el) {
+        r.el.style.pointerEvents = this.enterSelectTree ? 'auto' : 'none'
+        if (this.enterSelectTree) this.bindCardClick(r)
+      }
+      if (r.overlay?.setStopEvent) {
+        r.overlay.setStopEvent(this.enterSelectTree)
+      }
+    })
+    if (this.enterSelectTree) {
+      this.bindRegionClick()
+    } else {
+      this.clearRegionClick()
+      this.regionOverlays.forEach((r) => {
+        r.feature.set('selected', false)
+        updateRegionInfoCard(r.el, { enterSelectTree: false, selected: false })
+      })
+      this.selectedRegionId = null
+      this._selectedEntry = null
+      this.regionLayer.layer.getSource()?.changed()
+    }
+  }
+
+  initData(data) {
+    this.clearLayer()
+    if (!data || !data.length) return
+
+    const features = []
+    for (const item of data) {
+      item.regionWkt = item.regionWkt || item.wkt
+      if (!item.regionWkt) {
+        console.warn('[regionLayer] 分区缺少 wkt', item)
+        continue
+      }
+      try {
+        const f = newRegionFeature({
+          ...item,
+          bgName: 'defalut',
+          bgColor: 'defalut',
+          selected: false,
+        })
+        features.push(f)
+
+        const entry = this.addRegionInfoOverlay(item, f)
+        if (entry) {
+          this.regionOverlays.push(entry)
+        }
+      } catch (err) {
+        console.warn('[regionLayer] 解析分区失败', item, err)
+      }
+    }
+
+    this.area = features
+    const source = new VectorSource({ features })
+    this.regionLayer.layer.setSource(source)
+
+    if (features.length) {
+      const extent = source.getExtent()
+      if (extent.every((n) => Number.isFinite(n))) {
+        this.kmap.getView().fit(extent, {
+          padding: [60, 40, 100, 40],
+          duration: 600,
+          maxZoom: 19,
+        })
+      }
+    }
+
+    if (this.enterSelectTree) {
+      this.bindRegionClick()
+    }
+  }
+
+  clearLayer() {
+    this.clearRegionClick()
+    this.clearRegionInfoOverlays()
+    this.selectedRegionId = null
+    this._selectedEntry = null
+    if (this.regionLayer?.layer?.getSource()) {
+      this.regionLayer.layer.getSource().clear()
+    }
+  }
+
+  reset(farm) {
+    this.clearLayer()
+    this.initData(farm?.id ? [] : [])
+  }
+}
+
+export default RegionLayer

+ 5 - 6
vite.config.ts

@@ -21,13 +21,12 @@ export default defineConfig(({ mode }) => {
         '@': path.resolve(__dirname, 'src'),
       },
     },
+    // 各字段单独 define,避免对象内 JSON.stringify 被 Vite 二次序列化导致 URL 带引号
     define: {
-      VE_ENV: {
-        MODE: JSON.stringify(isProd ? 'production' : 'development'),
-        SERVER: JSON.stringify(env.VITE_SERVER ?? ''),
-        PYSERVER: JSON.stringify(env.VITE_PYSERVER ?? ''),
-        MOCK: JSON.stringify(env.VITE_MOCK ?? 'False'),
-      },
+      'VE_ENV.MODE': JSON.stringify(isProd ? 'production' : 'development'),
+      'VE_ENV.SERVER': JSON.stringify(env.VITE_SERVER ?? ''),
+      'VE_ENV.PYSERVER': JSON.stringify(env.VITE_PYSERVER ?? ''),
+      'VE_ENV.MOCK': JSON.stringify(env.VITE_MOCK ?? 'False'),
     },
   }
 })