|
|
@@ -0,0 +1,384 @@ |
|
|
|
<template> |
|
|
|
<div class="map-toolbar"> |
|
|
|
<img class="box" src="./distance.png" @click="measure('LineString')"> |
|
|
|
<img class="box" src="./area.png" @click="measure('Polygon')"> |
|
|
|
<img class="box" src="./clear.png" @click="clearMeasure"> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
<script> |
|
|
|
import { reactive, toRefs, watch } from 'vue' |
|
|
|
import { Feature } from 'ol' |
|
|
|
import VectorSource from 'ol/source/Vector' |
|
|
|
import VectorLayer from 'ol/layer/Vector' |
|
|
|
import { Stroke, Style, Fill } from 'ol/style' |
|
|
|
import CircleStyle from 'ol/style/Circle' |
|
|
|
import { Draw } from 'ol/interaction' |
|
|
|
import { getArea, getLength } from 'ol/sphere' |
|
|
|
import { unByKey } from 'ol/Observable' |
|
|
|
import { Overlay } from 'ol' |
|
|
|
import { LineString, Polygon } from 'ol/geom' |
|
|
|
import { transform, fromLonLat } from 'ol/proj' |
|
|
|
import { styleList } from '@/utils/style.js' |
|
|
|
|
|
|
|
// turf 用于简单的空间计算 |
|
|
|
import * as turf from '@turf/turf' |
|
|
|
export default { |
|
|
|
props: { |
|
|
|
map: { |
|
|
|
type: Object, |
|
|
|
default: () => {} |
|
|
|
} |
|
|
|
}, |
|
|
|
setup(props, { emit }) { |
|
|
|
const data = reactive({ |
|
|
|
mapData: null, |
|
|
|
mapMouseMove: null, |
|
|
|
helpTooltipElement: null, |
|
|
|
feature: null, |
|
|
|
draw: null, |
|
|
|
listener: null, |
|
|
|
measureTooltipElement: null, |
|
|
|
measureTooltip: null, |
|
|
|
drawLayers: [], |
|
|
|
drawElements: [] |
|
|
|
}) |
|
|
|
|
|
|
|
watch(() => props.map, (map) => { |
|
|
|
data.mapData = map |
|
|
|
cpRPA.setDistanceFn(distance) |
|
|
|
cpRPA.setLatlng2PxFn(latlng2Px) |
|
|
|
cpRPA.setPx2LatlngFn(px2Latlng) |
|
|
|
}) |
|
|
|
|
|
|
|
function distance(p1, p2) { |
|
|
|
const point1 = turf.point([parseFloat(p1.lng), parseFloat(p1.lat)]) |
|
|
|
const point2 = turf.point([parseFloat(p2.lng), parseFloat(p2.lat)]) |
|
|
|
return turf.distance(point1, point2, { units: 'meters' }) |
|
|
|
} |
|
|
|
|
|
|
|
function latlng2Px(latlng) { |
|
|
|
const pixel = data.mapData.getPixelFromCoordinate(fromLonLat([latlng.lng, latlng.lat])) |
|
|
|
return { |
|
|
|
x: pixel[0], |
|
|
|
y: pixel[1] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
function px2Latlng(px) { |
|
|
|
const coord = transform( |
|
|
|
data.mapData.getCoordinateFromPixel(px), |
|
|
|
'EPSG:3857', |
|
|
|
'EPSG:4326' |
|
|
|
) |
|
|
|
return { |
|
|
|
lat: coord[1], |
|
|
|
lng: coord[0] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 清除测量 |
|
|
|
*/ |
|
|
|
const clearMeasure = () => { |
|
|
|
for (let i = 0; i < data.drawLayers.length; i++) { |
|
|
|
data.mapData.removeLayer(data.drawLayers[i]) |
|
|
|
} |
|
|
|
for (let i = 0; i < data.drawElements.length; i++) { |
|
|
|
data.mapData.removeOverlay(data.drawElements[i]) |
|
|
|
} |
|
|
|
data.drawLayers = [] |
|
|
|
data.drawElements = [] |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 测距 |
|
|
|
*/ |
|
|
|
const measure = (measureType) => { |
|
|
|
// 创建一个数据源,用于放置绘制过程中和绘制结束后的线段 |
|
|
|
const source = new VectorSource() |
|
|
|
// 添加图层,用来放置数据源 |
|
|
|
const layer = new VectorLayer({ |
|
|
|
source: source, |
|
|
|
style: new Style({ |
|
|
|
fill: new Fill({ |
|
|
|
color: 'rgba(255, 255, 255, 0.2)' |
|
|
|
}), |
|
|
|
stroke: new Stroke({ |
|
|
|
color: '#ffcc33', |
|
|
|
width: 4 |
|
|
|
}), |
|
|
|
image: new CircleStyle({ |
|
|
|
radius: 7, |
|
|
|
fill: new Fill({ |
|
|
|
color: '#ffcc33' |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
data.mapMouseMove = data.mapData.on('pointermove', (evt) => { |
|
|
|
if (evt.dragging) { |
|
|
|
return |
|
|
|
} |
|
|
|
let helpMsg = '单击开始测量' |
|
|
|
if (data.feature) { |
|
|
|
helpMsg = '双击结束测量' |
|
|
|
} |
|
|
|
data.helpTooltipElement.innerHTML = helpMsg |
|
|
|
data.helpTooltip.setPosition(evt.coordinate) |
|
|
|
data.helpTooltipElement.classList.remove('hidden') |
|
|
|
}) |
|
|
|
|
|
|
|
data.mapData.getViewport().addEventListener('mouseout', () => { |
|
|
|
data.helpTooltipElement.classList.add('hidden') |
|
|
|
}) |
|
|
|
|
|
|
|
data.draw = new Draw({ |
|
|
|
source, |
|
|
|
// 测量方式 polygon 或者 lineString |
|
|
|
type: measureType, |
|
|
|
style: new Style({ |
|
|
|
fill: new Fill({ |
|
|
|
color: 'rgba(255, 255, 255, 0.2)' |
|
|
|
}), |
|
|
|
stroke: new Stroke({ |
|
|
|
color: 'rgba(0, 0, 0, 0.5)', |
|
|
|
lineDash: [10, 10], |
|
|
|
width: 4 |
|
|
|
}), |
|
|
|
image: new CircleStyle({ |
|
|
|
radius: 5, |
|
|
|
stroke: new Stroke({ |
|
|
|
color: 'rgba(0, 0, 0, 0.7)' |
|
|
|
}), |
|
|
|
fill: new Fill({ |
|
|
|
color: 'rgba(255, 255, 255, 0.2)' |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
// 开始进行绘制 |
|
|
|
data.draw.on('drawstart', (evt) => { |
|
|
|
data.feature = evt.feature |
|
|
|
let tooltipCoord = evt.coordinate |
|
|
|
data.listener = data.feature.getGeometry().on('change', (evt) => { |
|
|
|
const geom = evt.target |
|
|
|
// let output |
|
|
|
if (geom instanceof Polygon) { |
|
|
|
// output = formatArea(geom) |
|
|
|
tooltipCoord = geom.getInteriorPoint().getCoordinates() |
|
|
|
} else if (geom instanceof LineString) { |
|
|
|
// output = formatLength(geom) |
|
|
|
tooltipCoord = geom.getLastCoordinate() |
|
|
|
} |
|
|
|
|
|
|
|
// data.measureTooltipElement.innerHTML = output |
|
|
|
data.measureTooltip.setPosition(tooltipCoord) |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
// 绘制完成 |
|
|
|
data.draw.on('drawend', (evt) => { |
|
|
|
data.measureTooltipElement.className = 'ol-tooltip ol-tooltip-static' |
|
|
|
data.measureTooltip.setOffset([0, -7]) |
|
|
|
data.feature = null |
|
|
|
data.measureTooltipElement = null |
|
|
|
// createMeasureTooltip() |
|
|
|
data.mapData.removeInteraction(data.draw) |
|
|
|
unByKey(data.listener) |
|
|
|
unByKey(data.mapMouseMove) |
|
|
|
|
|
|
|
var feature = evt.feature |
|
|
|
var geometry = feature.getGeometry() |
|
|
|
var coordinate = geometry.getCoordinates() |
|
|
|
if (geometry instanceof LineString) { |
|
|
|
console.log(coordinate) |
|
|
|
} else if (geometry instanceof Polygon) { |
|
|
|
const lngLats = [] |
|
|
|
const len = coordinate[0].length |
|
|
|
for (let i = 0; i < len - 1; i++) { |
|
|
|
const coord = transform( |
|
|
|
coordinate[0][i], |
|
|
|
'EPSG:3857', |
|
|
|
'EPSG:4326' |
|
|
|
) |
|
|
|
lngLats.push( |
|
|
|
{ |
|
|
|
lat: coord[1], |
|
|
|
lng: coord[0] |
|
|
|
} |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
var polyline = cpRPA.setOptions({ |
|
|
|
polygon: lngLats, |
|
|
|
rotate: 0, |
|
|
|
space: 20 |
|
|
|
}) |
|
|
|
|
|
|
|
drawPolyline(formatPolyline(polyline)) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
createHelpTooltip() |
|
|
|
createMeasureTooltip() |
|
|
|
data.mapData.addLayer(layer) |
|
|
|
data.drawLayers.push(layer) |
|
|
|
data.mapData.addInteraction(data.draw) |
|
|
|
} |
|
|
|
|
|
|
|
const drawPolyline = (lngLats) => { |
|
|
|
const route = new LineString(lngLats) |
|
|
|
const routeFeature = new Feature({ |
|
|
|
type: 'route', |
|
|
|
geometry: route |
|
|
|
}) |
|
|
|
const vectorLayer = new VectorLayer({ |
|
|
|
source: new VectorSource({ |
|
|
|
features: [routeFeature] |
|
|
|
}), |
|
|
|
style: function(feature) { |
|
|
|
return styleList[feature.get('type')] |
|
|
|
} |
|
|
|
}) |
|
|
|
data.mapData.addLayer(vectorLayer) |
|
|
|
} |
|
|
|
|
|
|
|
const formatPolyline = (coordinate) => { |
|
|
|
const lngLats = [] |
|
|
|
coordinate.map((item) => { |
|
|
|
lngLats.push(fromLonLat([parseFloat(item.lng), parseFloat(item.lat)])) |
|
|
|
}) |
|
|
|
return lngLats |
|
|
|
} |
|
|
|
|
|
|
|
// 计算长度 |
|
|
|
const formatLength = (line) => { |
|
|
|
// ol自带的计算线段长度方法 |
|
|
|
const length = getLength(line) |
|
|
|
let output |
|
|
|
// 长度大于100米单位为km, 否则为m |
|
|
|
if (length > 100) { |
|
|
|
output = Math.round((length / 1000) * 100) / 100 + ' ' + 'km' |
|
|
|
} else { |
|
|
|
output = Math.round(length * 100) / 100 + ' ' + 'm' |
|
|
|
} |
|
|
|
return output |
|
|
|
} |
|
|
|
|
|
|
|
// 计算面积 |
|
|
|
const formatArea = (polygon) => { |
|
|
|
const proj = data.mapData.getView().getProjection() |
|
|
|
const area = getArea(polygon, { projection: proj }) |
|
|
|
let output |
|
|
|
if (area > 10000) { |
|
|
|
output = Math.round((area / 1000000 * 100) / 100) + ' ' + 'km<sup>2</sup>' |
|
|
|
} else { |
|
|
|
output = Math.round(area * 100) / 100 + ' ' + 'm<sup>2</sup>' |
|
|
|
} |
|
|
|
return output |
|
|
|
} |
|
|
|
|
|
|
|
const createMeasureTooltip = () => { |
|
|
|
if (data.measureTooltipElement) { |
|
|
|
data.measureTooltipElement.parentNode.removeChild(data.measureTooltipElement) |
|
|
|
} |
|
|
|
data.measureTooltipElement = document.createElement('div') |
|
|
|
data.measureTooltipElement.className = 'ol-tooltip ol-tooltip-measure' |
|
|
|
data.measureTooltip = new Overlay({ |
|
|
|
element: data.measureTooltipElement, |
|
|
|
offset: [0, -15], |
|
|
|
positioning: 'bottom-center', |
|
|
|
stopEvent: false, |
|
|
|
insertFirst: false |
|
|
|
}) |
|
|
|
data.drawElements.push(data.measureTooltip) |
|
|
|
data.mapData.addOverlay(data.measureTooltip) |
|
|
|
} |
|
|
|
|
|
|
|
const createHelpTooltip = () => { |
|
|
|
if (data.helpTooltipElement) { |
|
|
|
data.helpTooltipElement.parentNode.removeChild(data.helpTooltipElement) |
|
|
|
} |
|
|
|
data.helpTooltipElement = document.createElement('div') |
|
|
|
data.helpTooltipElement.className = 'ol-tooltip hidden' |
|
|
|
data.helpTooltip = new Overlay({ |
|
|
|
element: data.helpTooltipElement, |
|
|
|
offset: [15, 0], |
|
|
|
positioning: 'center-left' |
|
|
|
}) |
|
|
|
data.mapData.addOverlay(data.helpTooltip) |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...toRefs(data), |
|
|
|
measure, |
|
|
|
clearMeasure |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped lang="scss"> |
|
|
|
.map-toolbar { |
|
|
|
width: 32px; |
|
|
|
height: 100px; |
|
|
|
position: absolute; |
|
|
|
right: 10px; |
|
|
|
bottom: 10px; |
|
|
|
background-color: #fff; |
|
|
|
.box { |
|
|
|
width: 32px; |
|
|
|
height: 32px; |
|
|
|
background-size: 100% 100%; |
|
|
|
display: inline-block; |
|
|
|
cursor: pointer; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
::deep(.hidden) { |
|
|
|
display: none; |
|
|
|
} |
|
|
|
|
|
|
|
::deep(.ol-tooltip) { |
|
|
|
position: relative; |
|
|
|
background: rgba(0, 0, 0, 0.5); |
|
|
|
border-radius: 4px; |
|
|
|
color: white; |
|
|
|
padding: 4px 8px; |
|
|
|
opacity: 0.7; |
|
|
|
white-space: nowrap; |
|
|
|
font-size: 12px; |
|
|
|
cursor: default; |
|
|
|
user-select: none; |
|
|
|
} |
|
|
|
|
|
|
|
::deep(.ol-tooltip-measure) { |
|
|
|
opacity: 1; |
|
|
|
font-weight: bold; |
|
|
|
} |
|
|
|
|
|
|
|
::deep(.ol-tooltip-static) { |
|
|
|
background-color: #ffcc33; |
|
|
|
color: black; |
|
|
|
border: 1px solid white; |
|
|
|
} |
|
|
|
|
|
|
|
::deep(.ol-tooltip-measure:before), |
|
|
|
::deep(.ol-tooltip-static:before) { |
|
|
|
border-top: 6px solid rgba(0, 0, 0, 0.5); |
|
|
|
border-right: 6px solid transparent; |
|
|
|
border-left: 6px solid transparent; |
|
|
|
content: ""; |
|
|
|
position: absolute; |
|
|
|
bottom: -6px; |
|
|
|
margin-left: -7px; |
|
|
|
left: 50%; |
|
|
|
} |
|
|
|
|
|
|
|
::deep(.ol-tooltip-static:before) { |
|
|
|
border-top-color: #ffcc33; |
|
|
|
} |
|
|
|
|
|
|
|
</style> |