xjsaas-ui/src/components/map/fssm-map.vue

934 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
* @Author: SunTao 328867980@qq.com
* @Date: 2024-10-14 10:46:23
* @LastEditors: SunTao 328867980@qq.com
* @LastEditTime: 2024-12-23 17:37:59
* @FilePath: \znxjxt-ui\src\components\map\fssm-map.vue
* @Description: 公共地图
-->
<template>
<div class="map-container">
<div ref="container" :id="`map-${mapId}`"></div>
<!-- 地图弹窗 -->
<div ref="mapPopup" class="ol-popup">
<div ref="popupContent"></div>
</div>
<div ref="mapController" :style="controlStyle" class="control-container">
<!-- 绘制线段 -->
<div class="draw-line" v-if="showLine">
<i class="el-icon-edit" @click="drawLineMap()"></i>
<i class="el-icon-delete" @click="deleteLinedraw()"></i>
</div>
<!-- 绘制图形 -->
<div class="draw-map" v-if="showDraw">
<i class="el-icon-edit-outline" @click="drawMap()"></i>
<i class="el-icon-delete" @click="deletedraw()"></i>
</div>
<!-- 切换底图控件 -->
<div class="change-map" v-if="showChange">
<i
class="el-icon-picture-outline"
:class="mapType === 'cva_c' ? 'change-map-click' : ''"
@click="changeImg('cva_c')"
></i>
<i
class="el-icon-picture"
:class="mapType === 'img_c' ? 'change-map-click' : ''"
@click="changeImg('img_c')"
></i>
</div>
<!-- 放大缩小控件 -->
<div class="bigButton map-btn" v-if="showZoom">
<i class="el-icon-plus" @click="changeZoom(1, 0.5)"></i>
</div>
<div class="smallButton map-btn" v-if="showZoom">
<i class="el-icon-minus" @click="changeZoom(-1, 0.5)"></i>
</div>
</div>
</div>
</template>
<script>
import { Feature, Map, View } from "ol";
import XYZ from "ol/source/XYZ";
import { Tile as TileLayer } from "ol/layer";
import { defaults as defaultControls } from "ol/control";
import VectorLayer from "ol/layer/Vector";
import { Vector as VectorSource } from "ol/source";
import { Draw, Modify, Select, Snap } from "ol/interaction";
import * as styleExports from "ol/style";
import { Polygon, LineString } from "ol/geom";
import Overlay from "ol/Overlay";
export default {
name: "FssmMap",
props: {
// 接收传过来得中心点
centerPoint: {
type: Array,
default: () => {
return [123.30297096718999, 41.87942945541742];
},
},
// 接受传过来得地图层级
zoom: {
type: String,
default: "10",
},
// 是否显示绘制多边形功能
showDraw: {
type: Boolean,
default: false,
},
// 是否显示绘制线段功能
showLine: {
type: Boolean,
default: false,
},
// 是否显示地图放大缩小
showZoom: {
type: Boolean,
default: false,
},
// 是否显示切换地图底层按钮
showChange: {
type: Boolean,
default: false,
},
// 地图控件位置
controlStyle: {
type: Object,
default: () => {
return {
top: 0,
};
},
},
// 用于区分同一个页面多个地图的id
mapId: {
type: String,
default: "0",
},
// 接受传过来的绘制点位数组
editCoordinates: {
type: Array,
default: () => [],
},
// 接收传过来的绘制线段数组
editCoordinatesLine: {
type: Array,
default: () => [],
},
// 接收传过来的底图类型
baseMap: {
type: String,
default: "cva_c",
},
},
data() {
return {
// 地图实例
instance: new Map(),
// 点击地图类型
mapType: "cva_c",
// 地图图层
mapLayers: [],
// 绘制多边形图层
draw: null,
// 绘制线段图层
drawLine: null,
// 绘制图层源
source: new VectorSource(),
// 绘制线段图层源
sourceLine: new VectorSource(),
// 当前地图层级
sendZoom: "",
// 当前选中得图层
selectSingClick: null,
// 绘制图形保存的点位
drawMarkers: [],
// 绘制多边形图层layer
drawLayer: null,
// 绘制线段保存的点位
drawLineMarkers: [],
// 绘制线段图层layer
drawLineLayer: null,
// 绘制弹窗图层
overlayDialog: null,
};
},
watch: {
/* 监听传过来的坐标区域 */
editCoordinates: {
handler(val) {
this.$nextTick(() => {
const aa = val.map((item) => {
return [item[0] * 1, item[1] * 1];
});
this.drawMarkers = aa;
const map = this.instance.get("map");
// 增加snap吸附功能 还有就是snap交互可以在编辑和画features时候保持拓扑结构
const snap = new Snap({
source: this.source,
});
map.addInteraction(snap);
// 添加修改元素使得绘制的多边形可以编辑
const modify = new Modify({ source: this.source });
map.addInteraction(modify);
// 增加绘图层
this.drawLayer = new VectorLayer({
source: this.source,
id: "draw",
style: new styleExports.Style({
stroke: new styleExports.Stroke({
color: "blue",
size: 2,
}),
}),
});
const feature = new Feature({
geometry: new Polygon([aa]),
});
this.drawLayer.getSource().addFeature(feature);
map.addLayer(this.drawLayer);
modify.on("modifyend", (e) => {
e.features.forEach((feature) => {
this.drawMarkers = feature.getGeometry().getCoordinates().flat();
this.$emit("endEoordinate", this.drawMarkers);
});
});
});
},
deep: true,
immediate: true,
},
/* 监听传过来的绘制线段坐标 */
editCoordinatesLine: {
handler(val) {
this.$nextTick(() => {
const aa = val.map((item) => {
return [item[0] * 1, item[1] * 1];
});
this.drawLineMarkers = aa;
const map = this.instance.get("map");
// 增加snap吸附功能 还有就是snap交互可以在编辑和画features时候保持拓扑结构
const snap = new Snap({
source: this.sourceLine,
});
map.addInteraction(snap);
// 添加修改元素使得绘制的多边形可以编辑
const modify = new Modify({ source: this.sourceLine });
map.addInteraction(modify);
// 增加绘图层
this.drawLineLayer = new VectorLayer({
source: this.sourceLine,
id: "drawLine",
style: new styleExports.Style({
stroke: new styleExports.Stroke({
color: "red",
size: 4,
}),
}),
});
const feature = new Feature({
geometry: new LineString(aa),
});
this.drawLineLayer.getSource().addFeature(feature);
map.addLayer(this.drawLineLayer);
modify.on("modifyend", (e) => {
e.features.forEach((feature) => {
this.drawLineMarkers = feature.getGeometry().getCoordinates();
this.$emit("endEoordinateLine", this.drawLineMarkers);
});
});
});
},
immediate: true,
deep: true,
},
/* 监听传过来的底图类型 */
baseMap: {
handler(val) {
this.changeImg(val);
},
immediate: true,
deep: true,
},
},
mounted() {
this.initMap();
},
created() {
// this.changeImg("cva_c");
},
methods: {
/**
* @description: 初始化openlayer地图
* @return {*}
*/
initMap() {
// 内网
// const tianditu_vec_c = new TileLayer({
// className: "baseLayerClass",
// title: "矢量底图",
// id: "vec_c",
// source: new XYZ({
// url: "http://10.60.5.242:25033/v3/tile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
// // projection: "EPSG:4326",
// }),
// visible: true,
// });
// const tianditu_cva_c = new TileLayer({
// className: "baseLayerClass",
// title: "矢量地图",
// id: "cva_c",
// source: new XYZ({
// url: "http://10.60.5.242:25003/v3/tile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
// // projection: "EPSG:4326",
// }),
// visible: true,
// });
// 外网
const tianditu_cva_c = new TileLayer({
className: "baseLayerClass",
title: "矢量地图",
id: "cva_c",
source: new XYZ({
url: "https://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
// projection: "EPSG:4326",
}),
visible: true,
});
const tianditu_img_c = new TileLayer({
title: "影像地图",
id: "img_c",
source: new XYZ({
url: "https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
// projection: "EPSG:4326",
}),
visible: false,
});
// 弹窗图层
const overlays = new Overlay({
element: this.$refs.mapPopup,
autoPan: {
animation: {
duration: 250,
},
},
});
const map = new Map({
target: `map-${this.mapId}`,
controls: defaultControls({
zoom: false,
attribution: false,
rotate: false,
}),
// 弹窗图层数组
overlays: [overlays],
view: new View({
center: this.centerPoint, //中心点经纬度
zoom: this.zoom, //图层缩放大小
projection: "EPSG:4326",
minZoom: 7,
maxZoom: 18,
}),
// 内网
// layers: [tianditu_cva_c, tianditu_vec_c, tianditu_img_c],
// 外网
layers: [tianditu_cva_c, tianditu_img_c],
});
// 图层点击事件
map.on("singleclick", (e) => {
const featureClick = map.forEachFeatureAtPixel(
map.getEventPixel(e.originalEvent),
(feature) => {
return feature;
}
);
// 如果有图层则返回图层点击
if (featureClick) {
this.$emit("feature-click", featureClick);
} else {
// 没有图层则返回地图点击事件
this.$emit("map-click", e);
}
});
// 图层双击事件
map.on("dblclick", (e) => {
const featureDblclick = map.forEachFeatureAtPixel(
map.getEventPixel(e.originalEvent),
(feature) => {
return feature;
}
);
if (featureDblclick) {
this.$emit("feature-dblclick", featureDblclick);
}
});
// 鼠标移入事件
map.on("pointermove", (e) => {
const feature = map.forEachFeatureAtPixel(
map.getEventPixel(e.originalEvent),
(mapFeature) => {
return mapFeature;
}
);
// 获取弹窗图层
const [dislogLay] = map.getOverlays().getArray();
// 打印或处理经纬度
// this.$emit("pointer-move", { feature, coordinate });
if (feature && feature.getGeometry().getType() === "LineString") {
// 获取鼠标当前位置的像素坐标
const pixel = e.pixel;
// 将像素坐标转换为地理坐标(经纬度)
const coordinate = map.getCoordinateFromPixel(pixel);
// 获取弹窗数据
const popupData = feature.get("data");
// 获取弹窗内元素并赋值
this.$refs.popupContent.innerHTML = `<span>路段名称:</span><span>${popupData.name}</span><br/>
<span>病害数:</span><span>${popupData.count}个</span><br/>
<span>起始桩号:</span><span>${popupData.stakeStart}</span><br/>
<span>终止桩号:</span><span>${popupData.stakeEnd}</span>`;
dislogLay.setPosition(coordinate);
} else {
dislogLay.setPosition(undefined);
}
// 线、面要素不做鼠标移入样式修改
// if (feature) {
// if (feature.getGeometry()?.getType() === "Point") {
// map.getTargetElement().style.cursor = "pointer";
// this.$emit("pointer-move", feature);
// } else {
// map.getTargetElement().style.cursor = "auto";
// }
// } else {
// map.getTargetElement().style.cursor = "auto";
// }
});
// 地图缩放级别事件
map.on("moveend", (e) => {
const zoom = map.getView().getZoom().toFixed(); //获取当前地图的缩放级别
this.$emit("map-moveend", zoom);
});
// 图层选择事件
this.selectSingClick = new Select();
map.addInteraction(this.selectSingClick);
this.selectSingClick.on("select", (e) => {
this.$emit("feature-select", e);
// let selectedFeatures = e.selected;
// if (selectedFeatures.length > 0) {
// let feature = selectedFeatures[0];
// let features = feature.get("features");
// console.log(feature.getProperties(),'fff');
// if(feature.getProperties().type!=="line"){
// }
// if (features.length === 1) {
// // 单个点位
// // 执行之前的业务逻辑
// // 获取点击的图层信息
// const selectFeature = feature.getProperties().features[0];
// } else {
// // 聚合点
// // 放大地图层级
// map.getView().animate({
// center: feature.getGeometry().getCoordinates(),
// zoom: map.getView().getZoom() + 1,
// });
// }
// }
});
// 图层缩放事件监听
map.getView().on("change:resolution", () => {
if (this.sendZoom !== map.getView().getZoom().toFixed()) {
this.sendZoom = map.getView().getZoom().toFixed();
this.$emit("map-zoom", this.sendZoom);
}
});
this.instance.set("map", map);
this.instance.set("overlay-list", []);
},
/**
* @description: 删除图层选择对象
* @return {*}
*/
removeSelectClick() {
this.selectSingClick.getFeatures().clear();
},
/**
* @description: 绘制多边形地图
* @return {*}
*/
drawMap() {
if (this.drawMarkers.length < 1) {
const map = this.instance.get("map");
// 增加绘图层
this.drawLayer = new VectorLayer({
source: this.source,
id: "draw",
});
map.addLayer(this.drawLayer);
// 增加snap吸附功能 还有就是snap交互可以在编辑和画features时候保持拓扑结构
const snap = new Snap({
source: this.source,
});
map.addInteraction(snap);
// 添加修改元素使得绘制的多边形可以编辑
const modify = new Modify({ source: this.source });
map.addInteraction(modify);
this.draw = new Draw({
source: this.source,
type: "Polygon",
style: new styleExports.Style({
image: new styleExports.Circle({
radius: 5,
fill: new styleExports.Fill({
color: "blue",
}),
}),
stroke: new styleExports.Stroke({
color: "blue",
width: 2,
}),
fill: new styleExports.Fill({
color: "rgba(0, 0, 255, 0.2)",
}),
}),
});
map.addInteraction(this.draw);
this.draw.on("drawend", (e) => {
this.drawend(e);
});
modify.on("modifyend", (e) => {
e.features.forEach((feature) => {
this.drawMarkers = feature.getGeometry().getCoordinates().flat();
this.$emit("endEoordinate", this.drawMarkers);
});
});
}
},
/**
* @description: 监听绘制多边形完成的事件
* @param {*} event
* @return {*}
*/
drawend(event) {
const feature = event.feature;
const geometry = feature.getGeometry();
// 获取区域点位
const coordinates = geometry.getCoordinates()[0];
this.drawMarkers = coordinates;
// 在 drawend 结束后检查是否有交叉的线段
if (coordinates.length > 3 && this.isSelfCrossing(coordinates)) {
this.$modal.msgWarning("线段交叉,请重新绘制");
this.$nextTick(() => {
this.deletedraw();
this.drawMap();
});
} else {
const map = this.instance.get("map");
map.removeInteraction(this.draw);
}
this.$emit("endEoordinate", this.drawMarkers);
},
/**
* @description: 绘制线段地图
* @return {*}
*/
drawLineMap() {
if (this.drawLineMarkers.length < 1) {
const map = this.instance.get("map");
// 增加绘图层
this.drawLineLayer = new VectorLayer({
source: this.sourceLine,
id: "drawLine",
});
map.addLayer(this.drawLineLayer);
// 增加snap吸附功能 还有就是snap交互可以在编辑和画features时候保持拓扑结构
const snap = new Snap({
source: this.sourceLine,
});
map.addInteraction(snap);
// 添加修改元素使得绘制的多边形可以编辑
const modify = new Modify({ source: this.sourceLine });
map.addInteraction(modify);
this.drawLine = new Draw({
source: this.sourceLine,
type: "LineString",
style: new styleExports.Style({
image: new styleExports.Circle({
radius: 5,
fill: new styleExports.Fill({
color: "blue",
}),
}),
stroke: new styleExports.Stroke({
color: "blue",
width: 2,
}),
fill: new styleExports.Fill({
color: "rgba(0, 0, 255, 0.2)",
}),
}),
});
map.addInteraction(this.drawLine);
this.drawLine.on("drawend", (e) => {
this.drawLineend(e);
});
modify.on("modifyend", (e) => {
e.features.forEach((feature) => {
this.drawLineMarkers = feature.getGeometry().getCoordinates();
this.$emit("endEoordinateLine", this.drawLineMarkers);
});
});
}
},
/**
* @description: 监听绘制线段完成的事件
* @param {*} e
* @return {*}
*/
drawLineend(e) {
const feature = e.feature;
const geometry = feature.getGeometry();
// 获取区域点位
const coordinates = geometry.getCoordinates();
this.drawLineMarkers = coordinates;
this.$emit("endEoordinateLine", this.drawLineMarkers);
const map = this.instance.get("map");
map.removeInteraction(this.drawLine);
},
/**
* @description: 检测交叉点位方法
* @param {*} coordinates
* @return {*}
*/
isSelfCrossing(coordinates) {
for (let i = 0; i < coordinates.length - 1; i++) {
const segment1 = [coordinates[i], coordinates[i + 1]];
for (let j = i + 2; j < coordinates.length - 1; j++) {
const segment2 = [coordinates[j], coordinates[j + 1]];
if (this.doSegmentsCross(segment1, segment2)) {
return true;
}
}
}
return false;
},
/**
* @description: 检测交叉线段
* @param {*} segment1
* @param {*} segment2
* @return {*}
*/
doSegmentsCross(segment1, segment2) {
const [p1, p2] = segment1;
const [q1, q2] = segment2;
function crossProduct(p, q) {
return p[0] * q[1] - p[1] * q[0];
}
function subtractPoints(p, q) {
return [p[0] - q[0], p[1] - q[1]];
}
const r = subtractPoints(p2, p1);
const s = subtractPoints(q2, q1);
const uNumerator = crossProduct(subtractPoints(q1, p1), r);
const denominator = crossProduct(r, s);
if (denominator === 0) {
return false; // 线段平行或共线
}
const u = uNumerator / denominator;
const t = crossProduct(subtractPoints(q1, p1), s) / denominator;
// 判断是否相交并且不是共线
return t > 0 && t < 1 && u > 0 && u < 1;
},
/**
* @description: 删除绘制多边形功能
* @return {*}
*/
deletedraw() {
const map = this.instance.get("map");
map.removeInteraction(this.draw);
this.drawLayer.getSource().clear();
map.removeLayer(this.drawLayer);
this.draw = null;
this.source = new VectorSource();
this.drawMarkers = [];
this.$emit("endEoordinate", []);
},
/**
* @description: 删除绘制线段功能
* @return {*}
*/
deleteLinedraw() {
const map = this.instance.get("map");
map.removeInteraction(this.drawLine);
this.drawLineLayer.getSource().clear();
map.removeLayer(this.drawLineLayer);
this.drawLine = null;
this.source = new VectorSource();
this.drawLineMarkers = [];
this.$emit("endEoordinateLine", []);
},
/**
* @description: 修改地图底图样式
* @param {*} item
* @return {*}
*/
changeImg(item) {
if (this.mapType !== item) {
this.mapType = item;
this.$nextTick(() => {
if (item === "img_c") {
const layer = this.instance
.get("map")
.getAllLayers()
.filter((itemInfo) => {
return (
itemInfo.get("id") === "cva_c" ||
itemInfo.get("id") === "vec_c"
);
});
layer.forEach((it) => {
it.setVisible(false);
});
const layerDisabled = this.instance
.get("map")
.getAllLayers()
.filter((itemInfo) => {
return itemInfo.get("id") === "img_c";
});
layerDisabled.forEach((it) => {
it.setVisible(true);
});
} else if (item === "cva_c") {
const layer = this.instance
.get("map")
.getAllLayers()
.filter((itemInfo) => {
return (
itemInfo.get("id") === "cva_c" ||
itemInfo.get("id") === "vec_c"
);
});
layer.forEach((it) => {
it.setVisible(true);
});
const layerDisabled = this.instance
.get("map")
.getAllLayers()
.filter((itemInfo) => {
return itemInfo.get("id") === "img_c";
});
layerDisabled.forEach((it) => {
it.setVisible(false);
});
}
});
}
},
/**
* @description: 切换当前位置
* @param {Array} position 中心点位置
* @param {number} zoom 地图缩放等级
* @param {number} rotation 地图旋转角度
* @param {number} duration 地图垂直深度
* @return
*/
changePosition(position, zoom, rotation, duration) {
const map = this.instance.get("map");
if (map) {
map.getView().animate({
center: position, // 地图中心点位
zoom: zoom || 12, // 地图缩放等级
rotation: rotation || undefined, // 旋转角度
duration: duration || 1000, // 动画加载时间
});
}
},
/**
* @description: 清空地图图层
* @return
*/
clearMapFeature() {
const map = this.instance.get("map");
const [layer] = map.getAllLayers().filter((item) => item.get("type"));
map.removeLayer(layer);
},
/**
* @description: 根据类型清除地图线段图层
* @return {*}
*/
clearMapLine() {
const map = this.instance.get("map");
const [layer] = map
.getAllLayers()
.filter((item) => item.get("type") === "line");
map.removeLayer(layer);
},
/**
* @description: 调整地图缩放等级
* @return
*/
changeZoom(type = 1, zoomStep) {
const map = this.instance.get("map");
if (map) {
const step = zoomStep || 0.5;
// 获取当前地图缩放等级
const currentZoom = map.getView().getZoom();
map.getView().animate({
zoom: currentZoom + step * type,
});
}
},
},
};
</script>
<style lang="scss" scoped>
.map-container {
width: 100%;
height: 100%;
position: relative;
#map-0 {
width: 100%;
height: 100%;
}
#map-1 {
width: 100%;
height: 100%;
}
// 地图弹窗样式
.ol-popup{
width:10rem;
position: absolute;
margin: 0.5rem;
> div {
padding: 0.5rem;
box-sizing: border-box;
background: rgba(79, 79, 79, 0.7);
border-radius: 0.2rem;
border: 1px solid #3976f1;
font-size: 0.7rem;
color: #ffffff;
}
}
// 放大缩小控件
.control-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: absolute;
// left: 1rem;
// top: 1rem;
.draw-line {
width: 1.5rem;
height: 3rem;
background-color: rgb(198, 216, 216);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
i {
cursor: pointer;
padding: 0.1rem 0;
font-size: 1.2rem;
}
.change-map-click {
background-color: rgb(240, 240, 240);
}
}
.draw-map {
width: 1.5rem;
height: 3rem;
background-color: rgb(198, 216, 216);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
i {
cursor: pointer;
padding: 0.1rem 0;
font-size: 1.2rem;
}
.change-map-click {
background-color: rgb(240, 240, 240);
}
}
.change-map {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 1.5rem;
height: 3rem;
background-color: rgb(198, 216, 216);
i {
cursor: pointer;
padding: 0.1rem 0;
font-size: 1.2rem;
}
.change-map-click {
background-color: rgb(240, 240, 240);
}
}
.map-btn {
width: 1.5rem;
background-color: rgb(198, 216, 216);
height: 1.5rem;
display: flex;
margin: 0.1rem 0;
align-items: center;
justify-content: center;
i {
cursor: pointer;
padding: 0.1rem 0;
font-size: 1.2rem;
}
}
}
}
</style>