openlayer 综合应用和实际开发需求(区域搜索、编辑新增、Overlay、marker、layer联动功能、图标等比例缩放等实际开发功能)

320 阅读2分钟

效果展示

gis.gif

HTML结构和样式

<template>
  <div class="page_show">
    <div id="showPage" class="showPage-demo">
      <div id="popup" class="map-popup" ref="popup">
        <div class="popup-closed" @click="closed">×</div>
        <div class="map-popup-content" ref="popupContent">
          <div>设备编号:{{ popupData.code }}</div>
          <div>设备地址:{{ popupData.addr }}</div>
          <div>设备类型:{{ popupData.name }}</div>
        </div>
      </div>
    </div>
    <filtrate
      class="page-filtrate"
      :filterData="filterData"
      @type="lengenChange"
      @equipment="changEquipment"
    ></filtrate>
    <el-button class="page_show_Polygon" type="primary" @click="areaSreening">
      区域筛选
    </el-button>
    <el-button
      class="page_show_Polygon_cancel"
      type="danger"
      @click="cancelArea"
    >
      取消区域
    </el-button>
    <map-legend :list="exampleList" class="page_map_Legend"></map-legend>
    <div class="page_show_slect">
      <el-select
        v-model="searchFu"
        placeholder="请选择"
        filterable
        style="width: 400px"
      >
        <el-option
          v-for="item in adrAAray"
          :key="item.feature.id_"
          :label="item.addr"
          :value="item.feature.id_"
        >
        </el-option>
      </el-select>
    </div>
  </div>
</template>
<style>
.page-filtrate {
  position: absolute;
  top: 10px;
  right: 0;
}
.page_show_Polygon {
  position: absolute;
  top: 50px;
  right: 0;
}
.page_show_Polygon_cancel {
  position: absolute;
  top: 90px;
  right: 0;
}
.page_show_slect {
  position: absolute;
  top: 20px;
  left: 20px;
}
.page_map_Legend {
  position: absolute;
  bottom: 20px;
  left: 0;
}
.page_show,
.showPage-demo {
  width: 100%;
  height: 100%;
  position: relative;
  top: 0;
  left: 0;
}
/* popup */
.map-popup {
  position: absolute;
  background-color: #fff;
  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
  padding: 10px;
  border-radius: 10px;
  border: 1px solid #ccc;
  left: 0px;
  bottom: 0px;
}
.map-popup ::after {
  top: 100%;
  border: solid transparent;
  content: "";
  width: 0;
  height: 0;
  position: absolute;
  pointer-events: none;
}
.map-popup ::after {
  border-top-color: #fff;
  border-width: 10px;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 0);
}

.popup-closed {
  position: absolute;
  top: 0px;
  right: 0px;
  width: 20px;
  height: 20px;
  background: #000;
  color: #fff;
  text-align: center;
  line-height: 20px;
  font-size: 16px;
  border-radius: 20px 0 20px 20px;
  cursor: pointer;
}
.map-popup-content {
  line-height: 24px;
  font-size: 12px;
  color: #2b2b2b;
  width: 300px;
}
</style>

公共方法和全局图层

数据来源自己模拟数据

import mark from "@/assets/mark.png";
import Feature from "ol/Feature";
import { Point } from "ol/geom";
import { Vector as VectorSource } from "ol/source";
import { Vector as VectorLayer } from "ol/layer";
import {
  Fill,
  Stroke,
  Style,
  Icon,
  Text,
  Circle as CircleStyle,
} from "ol/style";
import { GeoJSON } from "ol/format";
import { initMap, devList } from "./district"; //或者区域
import config from "./globalConfig.json";
import { Select, Modify, Draw } from "ol/interaction";
import { altKeyOnly, doubleClick } from "ol/events/condition";
import { getCenter } from "ol/extent";
//公共方法
const GAS_POINT_TYPE = () => {
  const datas = config.GAS_POINT_TYPE.children;
  datas.map((item) => (item.type = item.val.typeId.value));
  return datas;
};
const LINE_MATERIAL_ARRAY = () => {
  let array = [];
  config.GAS_LINE_MATERIAL.children.map((item) => {
    array.push({
      code: item.code,
      name: item.name,
      strokeColor: item.val.color.value,
      strokeWidth: item.val.width.value,
      material: item.val.material.value,
    });
  });
  return array;
};
const LINE_PRESSURE_ARRAY = () => {
  let array = [];
  config.GAS_LINE_PRESSURE_LEVEL.children.map((item) => {
    array.push({
      code: item.code,
      name: item.name,
      strokeColor: item.val.color.value,
      strokeWidth: item.val.width.value,
      pressureType: item.val.pressureType.value,
    });
  });
  return array;
};
const LINE_BELONGING_ARRAY = () => {
  let array = [];
  config.ENGINEERING_OF_BELONGING.children.map((item) => {
    array.push({
      code: item.code,
      name: item.name,
      strokeColor: item.val.color.value,
      strokeWidth: item.val.width.value,
      belong: item.val.belong.value,
    });
  });
  return array;
};
const toResolution = (resolution) => {
  return 1 / (((resolution * 50) / 2.54) * 100) > 1
    ? 1
    : 1 / (((resolution * 50) / 2.54) * 100);
};
const analysisLayer = function (layer, callBack) {
  layer
    .getSource()
    .getFeatures()
    .forEach((featrue) => {
      callBack && callBack(featrue);
    });
};
function gasPointType() {
  let customPoint = config.CUSTOM_POINT.val,
    GAS_POINT_TYPE = config.GAS_POINT_TYPE.children;
  GAS_POINT_TYPE.forEach((item) => {
    item.type = item.val.typeId.value;
  });
  return [
    ...GAS_POINT_TYPE,
    { type: customPoint.typeId.value, val: customPoint },
  ];
}
//中心标志点
function styleFunction(featrue, resolution) {
  return new Style({
    image: new Icon({
      src: mark,
      scale: 1,
    }),
    text: new Text({
      textAlign: "center",
      textBaseline: "middle",
      font: "normal 14px 微软雅黑",
      text: initMap.district.properties.name,
      fill: new Fill({ color: "#aa3300" }),
      stroke: new Stroke({ color: "#ffcc33", width: 2 }),
      offsetY: 0,
      scale: 1,
      offsetY: -20,
    }),
  });
}
const centerPoint = initMap.centerPoint.geometry.coordinates.slice(0, 2);
const centerMark = new VectorLayer({
  style: styleFunction,
  source: new VectorSource({
    features: [
      new Feature({
        geometryName: "centerMark",
        geometry: new Point(centerPoint),
      }),
    ],
  }),
});
//地区
const areaGeom = new GeoJSON({
  geometryName: "area",
}).readFeatures(initMap.district.geometry);
const areaLayer = new VectorLayer({
  source: new VectorSource({
    features: areaGeom,
  }),
  style: new Style({
    stroke: new Stroke({
      color: "rgba(255, 0,0, 1)",
      lineDash: [4],
      width: 2,
    }),
    fill: null,
    text: new Text({
      text: initMap.district.properties.name,
      textAlign: "center",
      font: "14px PingFangSC",
      padding: [3, 5, 3, 5],
      overflow: true,
      fill: new Fill({
        color: "yellow",
      }),
      backgroundFill: new Fill({
        color: "#48B5FA",
      }),
    }),
  }),
});
//设备
let equipmentFeature = new GeoJSON({
  geometryName: "equipment",
}).readFeatures({
  type: devList.type,
  features: devList.features,
});
const equipmentLayer = new VectorLayer({
  source: new VectorSource({
    features: equipmentFeature,
  }),
  //管道默认为压力展示
  style: (featrue, resolution) => styleFu(featrue, resolution, "pressureType"),
});
const lineEqimentStyles = {
  material: (properties, resolution) => {
    let confiLine = LINE_MATERIAL_ARRAY().find(
      (item) => item.material === properties.material || "1"
    );
    return new Style({
      stroke: new Stroke({
        color: confiLine.strokeColor,
        width: confiLine.strokeWidth * toResolution(resolution),
      }),
      text: new Text({
        scale: toResolution(resolution),
        text: confiLine.name,
        placement: "line",
        textAlign: "center",
        font: `${toResolution(resolution) * 14}px PingFangSC bloder`,
        padding: [2, 2, 2, 2],
        overflow: true,
        fill: new Fill({
          color: "#000",
        }),
      }),
    });
  }, //材料
  pressureType: (properties, resolution) => {
    let confiLine = LINE_PRESSURE_ARRAY().find(
      (item) => item.pressureType === properties.pressureType || "1"
    );
    //  console.log(confiLine, "result");
    return new Style({
      stroke: new Stroke({
        color: confiLine.strokeColor,
        width: confiLine.strokeWidth * toResolution(resolution),
      }),
      text: new Text({
        scale: toResolution(resolution),
        text: confiLine.name,
        placement: "line",
        textAlign: "center",
        font: `${toResolution(resolution) * 14}px PingFangSC bloder`,
        padding: [2, 2, 2, 2],
        overflow: true,
        fill: new Fill({
          color: "#000",
        }),
      }),
    });
  }, //压力
  belong: (properties, resolution) => {
    let confiLine = LINE_BELONGING_ARRAY().find(
      (item) => item.belong === properties.belong || "1"
    );
    return new Style({
      stroke: new Stroke({
        color: confiLine.strokeColor,
        width: confiLine.strokeWidth * toResolution(resolution),
      }),
      text: new Text({
        scale: toResolution(resolution),
        text: confiLine.name,
        placement: "line",
        textAlign: "center",
        font: `${toResolution(resolution) * 14}px PingFangSC bloder`,
        padding: [2, 2, 2, 2],
        overflow: true,
        fill: new Fill({
          color: "#000",
        }),
      }),
    });
  }, //工程
};
function getICon(properties) {
  let typeId = properties.typeId || "1",
    allPoint = gasPointType();
  return allPoint.find((i) => i.type == typeId).val;
}
function styleFu(featrue, resolution, lineStyleType) {
  let type = featrue.getGeometry().getType(),
    properties = featrue.getProperties(),
    scale = toResolution(resolution);
  if (type === "Point") {
    //各点展示
    let item = getICon(properties);
    return new Style({
      image: new Icon({
        rotation: 0,
        src: item.iconUrl.value,
        anchorXUnits: "fraction",
        // anchorYUnits: 'pixels',
        imgSize: [item.width.value, item.height.value],
        scale,
      }),
    });
  }
  if (type === "LineString") {
    return lineEqimentStyles[lineStyleType](properties, resolution);
  }
}
function bounce(t) {
  const s = 7.5625;
  const p = 2.75;
  let l;
  if (t < 1 / p) {
    l = s * t * t;
  } else {
    if (t < 2 / p) {
      t -= 1.5 / p;
      l = s * t * t + 0.75;
    } else {
      if (t < 2.5 / p) {
        t -= 2.25 / p;
        l = s * t * t + 0.9375;
      } else {
        t -= 2.625 / p;
        l = s * t * t + 0.984375;
      }
    }
  }
  return l;
}
function searchfeatrueId(newVal, vm) {
  let souce = equipmentLayer.getSource(),
    featrue = souce.getFeatureById(newVal),
    geom = featrue.getGeometry(),
    coordinates = geom.getCoordinates(),
    type = featrue.getGeometry().getType(),
    center;
  vm.popupData = featrue.getProperties();
  switch (type) {
    case "Point":
      center = coordinates.slice(0, 2);
      break;
    case "LineString":
      center = getCenter(geom.getExtent());
      break;
  }
  console.log(vm.map.getView());
  vm.map.getView().setCenter(center);
  //   vm.map.getView().animate({
  //     center,
  //     duration: 2000,
  //     zoom: 12,
  //   });
  vm.popupOverlay.setPosition(center);
}
//编辑功能
const selected = new Style({
  fill: new Fill({
    color: "#444",
  }),
  stroke: new Stroke({
    color: "rgba(255, 0, 255, 0.9)",
    width: 2,
  }),
});
const select = new Select({
  condition: function (mapBrowserEvent) {
    return doubleClick(mapBrowserEvent) && altKeyOnly(mapBrowserEvent);
  },
  style: selected,
  layers: [equipmentLayer],
  //   filter: (feature) => {  //过滤那些不额能编辑
  //     let properties = feature.getProperties();
  //     if (properties.highlight == 1) {
  //       return true;
  //     } else {
  //       Message({
  //         message: "不能编辑哦",
  //         center: true,
  //       });
  //     }

  //   },
});
const modify = new Modify({
  features: select.getFeatures(),
});
modify.on("modifyend", () => {
  select.getFeatures().clear();
});
//绘制多边形
const polygonSource = new VectorSource({
  wrapX: true,
});
const vector = new VectorLayer({
  source: polygonSource,
  style: new Style({
    fill: new Fill({ color: "rgba(0,0,0, 0.2)" }),
    stroke: new Stroke({ color: "#0ff", lineDash: [4], width: 2 }),
    //image: new CircleStyle({ radius: 7, fill: new Fill({ color: "#f00" }) }),
  }),
});
const draw = new Draw({
  source: polygonSource,
  type: "Polygon",
});
draw.setActive(false);
export {
  centerMark,
  centerPoint,
  areaLayer,
  equipmentLayer,
  LINE_PRESSURE_ARRAY,
  LINE_MATERIAL_ARRAY,
  LINE_BELONGING_ARRAY,
  GAS_POINT_TYPE,
  styleFu,
  lineEqimentStyles,
  modify,
  select,
  searchfeatrueId,
  bounce,
  draw,
  vector,
  polygonSource,
  analysisLayer,
  areaGeom,
};


filtrate 组件

<template>
  <el-popover placement="left" width="300" trigger="click">
    <el-form label-position="top" label-width="40px">
      <el-form-item label="颜色显示">
        <el-radio-group v-model="filterData.type" @change="typeChange">
          <el-radio label="pressureType">压力级别</el-radio>
          <el-radio label="material">材质</el-radio>
          <el-radio label="belong">工程归属</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item label="设备显示">
        <el-tree
          :data="typeData"
          show-checkbox
          default-expand-all
          :default-checked-keys="filterData.equipment"
          node-key="type"
          ref="tree"
          highlight-current
          :props="defaultProps"
          @check="handleChecke"
        ></el-tree>
      </el-form-item>
    </el-form>
    <template slot="reference">
      <el-button type="success">高级筛选</el-button>
    </template>
  </el-popover>
</template>

<script>
import { GAS_POINT_TYPE } from "@/utils/showPage.js";

export default {
  name: "filtrate",
  props: {
    filterData: {
      type: Object,
      required: true,
      default: () => {
        return {};
      },
    },
  },
  data() {
    return {
      typeData: [
        {
          name: "设备",
          type: "all",
          children: [
            {
              type: "0",
              name: "燃气管道",
            },
            ...GAS_POINT_TYPE(),
          ],
        },
      ],
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },
  methods: {
    handleChecke(
      node,
      { checkedKeys, checkedNodes, halfCheckedKeys, halfCheckedNodes }
    ) {

      this.$emit("equipment", checkedKeys);
    },
    typeChange(val) {
      this.$emit("type", val);
    },
  },
  mounted() {},
};
</script>

mapLegend 组件

<template>
    <div class="map-legend" functional>
        <div v-for="(item, index) in list" :key="`mapLegend${index}`" class="map-legend-item">
            <div class="legen_color" :style="{ background: item.strokeColor }"></div>
            <div class="legen_name">{{ item.name }}</div>
        </div>
    </div>
</template>

<script>
export default {
    name: 'mapLegend',
    props: {
        list: {
            required: true,
            type: Array,
            default: () => {}
        }
    }
};
</script>
<style scoped>
.map-legend {
    background: rgba(255, 255, 255, 0.8);
    padding: 5px;
}

.map-legend-item {
    display: flex;
    padding: 0 6px;
}

.map-legend-item .legen_color {
    width: 40px;
    height: 10px;
    margin: 5px 0;
}

.map-legend-item .legen_name {
    padding-left: 4px;
    line-height: 20px;
    font-size: 12px;
}
</style>

VUE data初始化值

  data() {
    return {
      searchFu: "",
      map: null,
      filterData: {
        type: "pressureType",
        equipment: ["all"],
      },
      exampleList: [],
      adrAAray: [],
      popupOverlay: null,
      popupData: {},
    };
  },

VUE created 生命周期/加载mapMapLegend数据、下拉筛选栏

  created() {
    let { type } = this.filterData;
    this.getMapLengebd(type);
    let Features = equipmentLayer.getSource().getFeatures();
    this.initSlect(Features, true);
  },
  methods:{
     getMapLengebd(type) {
      switch (type) {
        case "pressureType":
          this.exampleList = LINE_PRESSURE_ARRAY();
          break;
        case "material":
          this.exampleList = LINE_MATERIAL_ARRAY();
          break;
        case "belong":
          this.exampleList = LINE_BELONGING_ARRAY();
          break;
      }
    },
    initSlect(Features, initialRender) {
      let _this = this,
        adrAAray = [];
      Features.forEach((feature) => {
        if (initialRender) {
          feature.setProperties({
            isshowEquipment: true,
            isshowScope: true,
          });
        }
        adrAAray.push({
          addr:
            feature.getProperties().addr + `(${feature.getProperties().name})`,
          feature: feature,
        });
      });
      _this.adrAAray = adrAAray;
    },
  }

VUE mounted生命周期 初始地图和地图监听

  mounted() {
    this.initMap();
  },
  methods:{
     initMap() {
      let map = new Map({
        // 设置地图图层
        layers: [gaodeMapLayer, areaLayer, centerMark, vector, equipmentLayer],
        // 设置显示地图的视图
        view: new View({
          center: centerPoint,
          zoom: 11,
          minZoom: 2,
          maxZoom: 18,
          projection: "EPSG:4326",
          rotation: 0,
        }),
        // 让id为map的div作为地图的容器
        target: "showPage",
        interactions: defaultInteractions({
          doubleClickZoom: false, // 取消双击放大功能交互
          // mouseWheelZoom: false, // 取消滚动鼠标中间的滑轮交互
          // shiftDragZoom: false, // 取消shift+wheel左键拖动交互
        }).extend([select, modify, draw]),
      });
      this.map = map;
      this.addEventListenerMAP(map);//监听变化
      this.initPopup(map);//初始化popup
      this.addEventListenerDraw();//监听Draw
    },
    addEventListenerMAP(map) {
      let _this = this,
        { popupOverlay } = _this;
      map.on("pointermove", (e) => {
        let pixel = map.getEventPixel(e.originalEvent);
        let hit = map.hasFeatureAtPixel(pixel);
        map.getTargetElement().style.cursor = hit ? "pointer" : "";
      });
      map.on("singleclick", (evt) => {
        map.forEachFeatureAtPixel(evt.pixel, (feature, layer) => {
          let type = feature.getGeometry().getType(),
            properties = feature.getProperties();
          if (feature.getGeometryName() === "equipment") {
            this.popupData = properties;
            this.popupOverlay.setPosition(evt.coordinate);
          }
        });
      });
    },
    initPopup(map) {
      let { popup } = this.$refs,
        _this = this;
      let popupOverlay = new Overlay({
        element: popup,
        autoPan: true,
        positioning: "center-center",
        stopEvent: true,
        offset: [-160, -10],
        autoPanAnimation: {
          duration: 3000,
          easing: bounce,
        },
      });
      popupOverlay.setPosition(undefined);
      // console.log(popupOverlay)
      this.popupOverlay = popupOverlay;
      map.addOverlay(popupOverlay);
    },
    addEventListenerDraw() {
      let _this = this;
      draw.on("drawend", (evt) => {
        let polygon = evt.feature.getGeometry(),
          Features = [],
          { type, equipment } = _this.filterData;
        analysisLayer(equipmentLayer, (featrue) => {
          let coordinates = featrue.getGeometry().getCoordinates(),
            featrueType = featrue.getGeometry().getType(),
            sign = false,
            properties = featrue.getProperties(),
            { isshowEquipment, isshowScope } = properties;
          if (isshowEquipment) {
            switch (featrueType) {
              case "Point":
                sign = polygon.intersectsCoordinate(coordinates);
                break;
              case "LineString":
                sign =
                  polygon.intersectsCoordinate(coordinates[0]) &&
                  polygon.intersectsCoordinate(coordinates[1]);
                break;
            } //是否在这个区域
            if (sign) {
              featrue.setStyle((featrue, resolution) =>
                styleFu(featrue, resolution, type)
              );
              properties["isshowScope"] = true;
              Features.push(featrue);
            } else {
              properties["isshowScope"] = false;
              featrue.setStyle(() => null);
            }
            featrue.setProperties(properties);
          }
        });
        _this.initSlect(Features);
        draw.setActive(false);
      });
    },
  }

温馨提示

如有不明白的地方请留言! 可以一起探讨功能点 后续有更精彩的 开发中真实需求奉上