import * as ol from 'ol'
import * as style from 'ol/style'
import * as layer from 'ol/layer'
import * as source from 'ol/source'
import * as interaction from 'ol/interaction'
import Modify from "ol/interaction/Modify"
import {createBox} from "ol/interaction/Draw"
import * as sphere from "ol/sphere";
import {unByKey} from "ol/Observable";
import {isValue} from "@/utils/util";
// @ts-ignore
import PointIcon from "@/assets/images/gis-layout/gis-layout-tools_tool-bz_icon.png"
import {v4} from "uuid";
const globalLineDash = [
[0, 0], //实线
[15, 15], //长虚线
[5, 5] //虚线
]
const layerFlag = ['layerName', 'toolDrawViewsLayer']
const drawFlag = ['interactionName', 'toolDrawInteraction']
const modifyFlag = ['interactionName', 'toolModifyInteraction']
const baseDrawConfig = {
// 样式字段
text: null, // 要素上显示的文字,默认无文字
pointIcon: PointIcon, // Point的图标
pointScale: 1, // Point的缩放,默认1
pointOffset: [0, 24], // Point的偏移量,默认[0, 0]
rectangle: false,
lineColor: '#2860F1', // LineString的线段颜色,默认蓝色
lineWidth: 2, // LineString的线段宽度,默认1
lineType: 0, // LineString的线段类型索引,默认0,实线,取globalLineDash数组索引
lineDash: null, // LineString的线段类型,默认null,优先级比lineType高
polyColor: 'rgba(20, 129, 241, 0.1)', // Polygon的填充色,默认蓝色
polyBorderColor: '#2860F1', // Polygon的边框颜色,默认蓝色
polyBorderWidth: 2, // Polygon的边框宽度,默认1
polyBorderType: 0, // Polygon的边框类型索引,默认0,实线,取globalLineDash数组索引
polyBorderDash: null, // Polygon的边框类型,默认null,优先级比polyBorderType高
// 业务字段
show: false, // 标绘样式选项是否显示
featureType: 'Point', // 标绘的要素类型
isPoint: false, // 是否可以标绘点
isLineString: false, // 是否可以标绘线
isPolygon: false, // 是否可以标绘面
showPosition: false, // 是否显示经纬度输入框
refreshStyleFunc: () => {}, // 刷新标绘样式方法
}
export const getBaseDrawConfig = () => {
return JSON.parse(JSON.stringify(baseDrawConfig))
}
/**
*
* @param map
* @param arr 要标绘的数组,每个对象的数据在要素中保存的属性为 'val'
* @param arr > wkt:wkt格式坐标
* @param arr > styles:要素的样式,优先级最高
* @param arr > text:styles为空的时候,要素上显示的文字,默认无文字
* @param arr > textOffsetY:styles为空的时候,要素上显示的文字Y轴偏移量,默认Point-30,其他0
* @param arr > pointIcon:styles为空的时候,Point的图标,默认圆形
* @param arr > pointScale:styles为空的时候,Point的缩放,默认1
* @param arr > pointOffset:styles为空的时候,Point的偏移量,默认[0, 0]
* @param arr > lineColor:styles为空的时候,LineString的线段颜色,默认蓝色
* @param arr > lineWidth:styles为空的时候,LineString的线段宽度,默认1
* @param arr > lineType:styles为空的时候,LineString的线段类型索引,默认0,实线,取globalLineDash数组索引
* @param arr > lineDash:styles为空的时候,LineString的线段类型,默认null,优先级比lineType高
* @param arr > polyColor:styles为空的时候,Polygon的填充色,默认蓝色
* @param arr > polyBorderColor:styles为空的时候,Polygon的边框颜色,默认蓝色
* @param arr > polyBorderWidth:styles为空的时候,Polygon的边框宽度,默认1
* @param arr > polyBorderType:styles为空的时候,Polygon的边框类型索引,默认0,实线,取globalLineDash数组索引
* @param arr > polyBorderDash:styles为空的时候,Polygon的边框类型,默认null,优先级比polyBorderType高
*/
let toolDrawTooltipElement;
let toolDrawHelpTooltipElement;
export const draw = (map, obj) => {
return new Promise((resolve => {
if (!isValue(obj.textOffsetY)) {
obj.textOffsetY = (obj.featureType === 'Point' ? -30 : 0)
}
let commonStyle = (showText, featureType = undefined) => {
if ((featureType ?? obj.featureType) === 'Point') {
return new style.Style({
image: obj.pointIcon ? new style.Icon({
src: obj.pointIcon,
scale: obj.pointScale,
displacement: obj.pointOffset
}) : new style.Circle({
radius: 10,
fill: new style.Fill({
color: '#e810dd',
}),
scale: obj.pointScale,
displacement: obj.pointOffset
}),
text: (showText && obj.text) ? new style.Text({
font: "16px bold 微软雅黑",
text: obj.text,
fill: new style.Fill({
color: '#ffffff'
}),
stroke: new style.Stroke({
color: '#D26CDB',
width: 2
}),
offsetY: obj.textOffsetY,
}) : undefined,
})
} else if ((featureType ?? obj.featureType) === 'LineString') {
return new style.Style({
stroke: new style.Stroke({
color: obj.lineColor,
width: obj.lineWidth,
lineDash: obj.lineDash ?? globalLineDash[Number(obj.lineType)]
}),
text: (showText && obj.text) ? new style.Text({
font: "16px bold 微软雅黑",
text: obj.text,
fill: new style.Fill({
color: '#ffffff'
}),
stroke: new style.Stroke({
color: '#D26CDB',
width: 2
}),
offsetY: obj.textOffsetY,
}) : undefined,
})
} else if ((featureType ?? obj.featureType) === 'Polygon' || (featureType ?? obj.featureType) === 'Circle') {
return new style.Style({
stroke: new style.Stroke({
color: obj.polyBorderColor,
width: obj.polyBorderWidth,
lineDash: obj.polyBorderDash ?? globalLineDash[Number(obj.polyBorderType)]
}),
fill: new style.Fill({
color: obj.polyColor,
}),
text: (showText && obj.text) ? new style.Text({
font: "16px bold 微软雅黑",
text: obj.text,
fill: new style.Fill({
color: '#ffffff'
}),
stroke: new style.Stroke({
color: '#D26CDB',
width: 2
}),
offsetY: obj.textOffsetY,
}) : undefined,
})
}
}
const reset = () => {
const oldLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
if (oldLayer) {
map.removeLayer(oldLayer[0])
}
const oldDraw = map.getInteractions().getArray().filter(v => v.get(drawFlag[0]) === drawFlag[1])
if (oldDraw) {
map.removeInteraction(oldDraw[0])
}
const oldModify = map.getInteractions().getArray().filter(v => v.get(modifyFlag[0]) === modifyFlag[1])
if (oldModify) {
map.removeInteraction(oldModify[0])
}
}
if (!toolDrawTooltipElement) {
// reset()
let _source
const realLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
if (realLayer[0]) {
_source = realLayer[0].getSource()
} else {
_source = new source.Vector(); //图层数据源
const _vector = new layer.Vector({
zIndex: 9999,
source: _source,
style: (feat: any) => {
feat.setStyle(commonStyle(true, feat.getGeometry().getType()))
},
});
_vector.set(layerFlag[0], layerFlag[1])
map.addLayer(_vector);
}
const modifyInteraction = new Modify({
source: _source,
});
modifyInteraction.set(modifyFlag[0], modifyFlag[1])
map.addInteraction(modifyInteraction)
modifyInteraction.on('modifyend', evt => {
try {
const feat = evt.features.item(0)
} catch {
}
})
if (!obj.wkt) {
let sketch;
let helpTooltip;
let measureTooltip;
let continueMsg = '双击结束标绘';
const geodesicCheckbox = true;//测地学方式对象
const createMeasureTooltip = () => {
const id = 'toolDrawTooltipElementId'
if (toolDrawTooltipElement) {
map.removeOverlay(map.getOverlayById(id))
toolDrawTooltipElement.parentNode.removeChild(toolDrawTooltipElement);
}
toolDrawTooltipElement = document.createElement('div');
toolDrawTooltipElement.className = 'tooltip tooltip-measure';
measureTooltip = new ol.Overlay({
id,
element: toolDrawTooltipElement,
offset: [0, -15],
positioning: 'bottom-center'
});
map.addOverlay(measureTooltip);
}
const createHelpTooltip = () => {
const id = 'toolDrawHelpTooltipElementId'
if (toolDrawHelpTooltipElement) {
map.removeOverlay(map.getOverlayById(id))
toolDrawHelpTooltipElement.parentNode.removeChild(toolDrawHelpTooltipElement);
}
toolDrawHelpTooltipElement = document.createElement('div');
toolDrawHelpTooltipElement.className = 'tooltip hidden';
helpTooltip = new ol.Overlay({
id,
element: toolDrawHelpTooltipElement,
offset: [15, 0],
positioning: 'center-left'
});
map.addOverlay(helpTooltip);
}
const formatLength = (line) => {
// 获取投影坐标系
const sourceProj = map.getView().getProjection();
// ol/sphere里有getLength()和getArea()用来测量距离和区域面积,默认的投影坐标系是EPSG:3857, 其中有个options的参数,可以设置投影坐标系
const length = sphere.getLength(line, {projection: sourceProj});
// const length = getLength(line);
let output;
if (length > 100) {
const km = Math.round((length / 1000) * 100) / 100;
output = `${km} 千米
${parseFloat(String(km * 0.53995)).toFixed(2)} 海里`;
} else {
output = `${Math.round(length * 100) / 100} m`;
}
return output;
};
//获取圆的面积
const getCircleArea = (circle, projection) => {
const P = 3.14
const radius = getCircleRadius(circle, projection)
return P * radius * radius
}
//获取圆的半径
const getCircleRadius = (circle, projection) => {
return circle.getRadius() * projection.getMetersPerUnit()
}
const formatArea = (polygon, type= 'polygon') => {
let area
const sourceProj = map.getView().getProjection();
// 获取投影坐标系
if (type === 'polygon') {
area = sphere.getArea(polygon, {
projection: sourceProj,
});
} else if (type === 'circle') {
area = getCircleArea(polygon, sourceProj)
}
let output;
if (area > 10000) {
const km = Math.round((area / 1000000) * 100) / 100;
output = `${km} 平方公里
${parseFloat(String(km * 0.38610)).toFixed(
2
)} 平方英里`;
} else {
output = `${Math.round(area * 100) / 100} ` + " m2";
}
return output;
};
const addInteraction = () => {
const id = 'baseDrawName'
const draw = new interaction.Draw({
source: _source,//测量绘制层数据源
type: obj.rectangle ? 'LineString' : obj.featureType, //几何图形类型
// @ts-ignore
// geometryFunction: createBox(),
geometryFunction: obj.rectangle ? createBox() : null,
// geometryFunction: createRegularPolygon(6),
style: commonStyle(obj.featureType === 'Point' ? true : false),
});
draw.set('showText', obj.featureType === 'Point' ? true : false)
draw.set(drawFlag[0], drawFlag[1])
draw.set(id, id)
createMeasureTooltip(); //创建测量工具提示框
createHelpTooltip(); //创建帮助提示框
map.addInteraction(draw);
let listener;
//绑定交互绘制工具开始绘制的事件
const drawstartHandle = (evt) => {
sketch = evt.feature; //绘制的要素
let tooltipCoord = evt.coordinate;// 绘制的坐标
//绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
listener = sketch.getGeometry().on('change', function (evt) {
const geom = evt.target
let output;
if (geom.getType() === 'LineString') {
output = formatLength(geom);//长度值
tooltipCoord = geom.getLastCoordinate();//坐标
} else if (geom.getType() === 'Polygon') {
output = formatArea(geom);//面积值
tooltipCoord = geom.getInteriorPoint().getCoordinates();//坐标
} else if (geom.getType() === 'Circle') {
output = formatArea(geom, 'circle');//面积值
tooltipCoord = geom.getCenter()
}
toolDrawTooltipElement.innerHTML = output;//将测量值设置到测量工具提示框中显示
measureTooltip.setPosition(tooltipCoord);//设置测量工具提示框的显示位置
});
}
draw.on('drawstart', drawstartHandle);
//绑定交互绘制工具结束绘制的事件
const copy = (value) => {
const str = document.createElement('input')
str.setAttribute('value', value)
document.body.appendChild(str)
str.select()
document.execCommand('copy')
document.body.removeChild(str)
}
const drawendHandle = (evt) => {
map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
// 标绘的时候不需要最终结果dom
map.removeOverlay(map.getOverlayById('baseDrawHelpTooltipElementId'))
toolDrawTooltipElement.parentNode.removeChild(toolDrawTooltipElement);
sketch = null; //置空当前绘制的要素对象
toolDrawTooltipElement = null; //置空测量工具提示框对象
toolDrawHelpTooltipElement.parentNode.removeChild(toolDrawHelpTooltipElement);
toolDrawHelpTooltipElement = null; //置空测量工具提示框对象
unByKey(listener);
draw.un('drawstart', drawstartHandle);
draw.un('drawend', drawendHandle);
map.removeInteraction(map.getInteractions().getArray().filter(v => v.get(id) === id)[0]);
map.un('pointermove', pointerMoveHandler)
const featEnd = evt.feature
featEnd.set('isInit', true)
featEnd.set(layerFlag[0], layerFlag[1])
featEnd.setId(v4())
}
draw.on('drawend', drawendHandle);
}
addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
const pointerMoveHandler = (evt) => {
if (evt.dragging) {
return;
}
let helpMsg = '单击开始标绘';//当前默认提示信息
//判断绘制几何类型设置相应的帮助提示信息
if (sketch) {
const geom = sketch.getGeometry()
helpMsg = continueMsg;
// if (geom.getType() === 'Polygon') {
// helpMsg = continueMsg; //绘制多边形时提示相应内容
// } else if (geom.getType() === 'LineString') {
// helpMsg = continueMsg; //绘制线时提示相应内容
// }
}
toolDrawHelpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
helpTooltip.setPosition(evt.coordinate);//设置帮助提示框的位置
toolDrawHelpTooltipElement.classList.remove('hidden');//移除帮助提示框的隐藏样式进行显示
};
map.on('pointermove', pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
//地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
try {
map.getViewport().on('mouseout', () => {
toolDrawHelpTooltipElement.addClass('hidden');
});
} catch (e) {
}
}
}
resolve(() => {
const oldLayer = map.getLayers().getArray().filter(v => v.get(layerFlag[0]) === layerFlag[1])
if (oldLayer) {
oldLayer[0].setStyle(commonStyle(true))
}
// const oldDraw = map.getInteractions().getArray().filter(v => v.get(drawFlag[0]) === drawFlag[1])
// if (oldDraw) {
// oldDraw[0].setStyle(commonStyle(oldDraw[0].get('showText')))
// }
})
}))
}
export const refreshModify = (map, _source) => {
const oldModify = map.getInteractions().getArray().filter(v => v.get(modifyFlag[0]) === modifyFlag[1])
if (oldModify) {
map.removeInteraction(oldModify[0])
}
const modifyInteraction = new Modify({
source: _source,
});
modifyInteraction.set(modifyFlag[0], modifyFlag[1])
map.addInteraction(modifyInteraction)
modifyInteraction.on('modifyend', evt => {
try {
const feat = evt.features.item(0)
} catch {
}
})
}