OpenLayers是一个开源的JavaScript库,用于在网页上展示地图和进行空间分析。使用OpenLayers进行测距,主要涉及到在地图上添加绘制线段的交互,并实时计算这些线段的长度。以下是一个基于OpenLayers进行测距的基本步骤和代码示例:
目录结构
OlMeasure/index.vue
<template>
<NButtonGroup vertical size="tiny" class="OlMeasure">
<slot></slot>
<n-popover trigger="hover" placement="left">
<template #trigger>
<NButton :disabled="!current" type="error" @click="onClose">
<RemixIcon icon="close-large-fill"></RemixIcon>
</NButton>
</template>
<span>关闭</span>
</n-popover>
</NButtonGroup>
</template>
<script setup>
import {Style, Stroke, Fill, Circle} from "ol/style"
import VectorLayer from "ol/layer/Vector.js"
import VectorSource from "ol/source/Vector.js"
defineOptions({
name: "OlMeasure"
})
let {map} = inject("openlayers")
// {'Point'}
// {'LineString'}
// {'LinearRing'}
// {'Polygon'}
// {'MultiPoint'}
// {'MultiLineString'}
// {'MultiPolygon'}
// {'GeometryCollection'}
// {'Circle'}
let style = new Style({
fill: new Fill({
color: "rgba(0, 0, 255, 0.2)"
}),
stroke: new Stroke({
color: "yellow",
lineDash: [10, 10],
width: 2
}),
image: new Circle({
radius: 5,
stroke: new Stroke({
color: "rgba(0, 0, 0, 0.7)"
}),
fill: new Fill({
color: "rgba(255, 255, 255, 0.2)"
})
})
})
let source = new VectorSource()
let layer = new VectorLayer({
source,
style,
zIndex: Infinity
})
map.addLayer(layer)
let current = ref(null)
watch(current, () => {
source.clear()
})
const onClose = () => {
current.value = null
}
provide("OlMeasure", {
source,
layer,
current
})
</script>
<style lang="scss" scoped>
.OlMeasure {
position: absolute;
right: 10px;
top: 20px;
z-index: 1;
}
</style>
OlMeasure/config.js
import {getDistance} from "ol/sphere"
import numeral from "numeral"
export const getDistanceTotal = coordinates => {
return coordinates.reduce((target, item, index) => {
let next = coordinates[index + 1]
if (!next) return target
target += getDistance(item, next)
return target
}, 0)
}
export const formatDistance = value => {
if (value > 10000) {
return numeral(value / 1000).format("0.00") + "km"
}
return numeral(value).format("0.00") + "m"
}
测距离
OlMeasure/OlDistance/index.vue
<template>
<n-popover trigger="hover" placement="left">
<template #trigger>
<NButton @click="onClick" :disabled="isShow" :type="isShow ? 'info' : 'primary'">
<RemixIcon icon="ruler-fill"></RemixIcon>
</NButton>
</template>
<span>测距</span>
</n-popover>
<div v-for="(item, index) in flagOverlayList" :key="index">
<LineOverlay v-for="option in item.distance.filter((a, b) => b > 0)" v-bind="option" :index="index"></LineOverlay>
</div>
<OlOverlay :position="mousePosition" :offset="[0, -90]" v-if="isShow">
<div class="mouse-overlay">
<div v-if="currentFlag.length > 0">
<p>总长: {{ formatDistance(totalLength) }}</p>
<p>点击设置顶点,右键删除上一个点,双击结束测量</p>
</div>
<div v-else>
<p>0米</p>
<p>点击开始测量</p>
</div>
</div>
</OlOverlay>
</template>
<script setup>
import {useEventListener} from "../../hooks"
import {last} from "lodash-es"
import {getDistanceTotal, formatDistance} from "../config"
import {getDistance} from "ol/sphere"
import LineOverlay from "./LineOverlay"
import {Draw} from "ol/interaction"
defineOptions({
name: "OlDistance"
})
let {map} = inject("openlayers")
let {current, source} = inject("OlMeasure")
let isShow = computed(() => current.value === "distance")
let prevLength = ref(0)
let totalLength = ref(0)
let mousePosition = ref([])
let flagOverlayList = ref([])
let currentFlag = reactive([])
let draw = new Draw({
source: source,
type: "LineString"
})
provide("olDistance", {
flagOverlayList
})
watch(current, value => {
if (isShow.value) {
map.addInteraction(draw)
} else {
map.removeInteraction(draw)
flagOverlayList.value = []
currentFlag = reactive([])
}
})
draw.on("drawstart", ev => {
flagOverlayList.value.push({
distance: currentFlag,
feature: ev.feature
})
})
draw.on("drawend", ev => {
let {feature} = ev
let geometry = feature.getGeometry()
let points = geometry.getCoordinates()
let metre = getDistance(last(points), last(currentFlag).position)
currentFlag.push({
key: Math.random(),
position: last(points),
metre,
total: currentFlag.reduce((a, b) => a + b.metre, metre),
isEnd: true
})
currentFlag = reactive([])
})
useEventListener(map, "contextmenu", ev => {
ev.preventDefault()
draw.removeLastPoint()
draw.removeLastPoint()
currentFlag.pop()
})
useEventListener(map, "singleclick", ev => {
if (!isShow.value) return
currentFlag.push({
key: Math.random(),
position: ev.coordinate,
metre: last(currentFlag) ? getDistance(ev.coordinate, last(currentFlag).position) : 0
})
})
useEventListener(map, "pointermove", ev => {
if (!isShow.value) return
mousePosition.value = ev.coordinate
if (currentFlag.length) {
prevLength.value = getDistance(ev.coordinate, last(currentFlag).position)
totalLength.value = getDistanceTotal([ev.coordinate, ...currentFlag.map(item => item.position)])
}
})
const onClick = async () => {
if (isShow.value) return
current.value = "distance"
}
</script>
<style lang="scss">
.mouse-overlay {
margin-left: -50%;
margin-right: 50%;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
color: lime;
padding: 8px 18px 8px 12px;
white-space: nowrap;
z-index: 0;
/* font-weight: bold; */
position: relative;
p {
line-height: 150%;
}
&::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%;
}
&-record {
margin-left: -50%;
margin-right: 50%;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
color: #fff;
padding: 8px 28px 8px 12px;
white-space: nowrap;
z-index: 0;
/* font-weight: bold; */
position: relative;
font-size: 14px;
&::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%;
}
}
&-close {
font-size: 20px;
position: absolute;
right: 0px;
top: 0;
}
}
</style>
OlMeasure/OlDistance/LineOverlay.vue
<template>
<OlOverlay :position="position" :offset="[0, -50]">
<div class="mouse-overlay-record" style="color: lime" v-if="isEnd">
<b>总长:</b>
<span>{{ formatDistance(total) }}</span>
<RemixIcon
@click="onClose(item, options, index)"
class="mouse-overlay-close"
icon="close-circle-fill"
></RemixIcon>
</div>
<div class="mouse-overlay-record" v-else>
<b>本段长:</b>
<span>{{ formatDistance(metre) }}</span>
</div>
</OlOverlay>
</template>
<script setup>
import {last} from "lodash-es"
import {formatDistance} from "../config"
let props = defineProps({
position: Array,
isEnd: Boolean,
total: Number,
metre: Number,
index: Number
})
let {flagOverlayList} = inject("olDistance")
let {source} = inject("OlMeasure")
const onClose = () => {
let remove = flagOverlayList.value.splice(props.index, 1)
source.removeFeature(last(remove).feature)
}
</script>
<style lang="scss" scoped>
.mouse-overlay-record {
margin-left: -50%;
margin-right: 50%;
background: rgba(0, 0, 0, 0.5);
border-radius: 4px;
color: #fff;
padding: 8px 20px 8px 12px;
white-space: nowrap;
z-index: 0;
position: relative;
font-size: 14px;
.mouse-overlay-close {
cursor: pointer;
}
}
</style>
测面积
OlMeasure/OlArea/index.vue
<template>
<n-popover trigger="hover" placement="left">
<template #trigger>
<NButton @click="onClick" :disabled="isShow" :type="isShow ? 'info' : 'primary'">
<RemixIcon icon="map-fill"></RemixIcon>
</NButton>
</template>
<span>测面积</span>
</n-popover>
<OlOverlay v-if="isShow" v-for="(item, index) in areaOverlay" :key="item.key" :position="item.center">
<div class="mouse-overlay">
<p>面积: {{ numeral(item.area).format("0.00") }}米²</p>
<RemixIcon @click="onClose(item, index)" class="mouse-overlay-close" icon="close-circle-fill"></RemixIcon>
</div>
</OlOverlay>
<OlOverlay :position="currentOverlay.position" :offset="[0, -90]" v-if="isShow">
<div class="mouse-overlay">
<div v-if="currentOverlay.area">
<p>面积: {{ currentOverlay.area }}米²</p>
<p>点击设置顶点,右键删除上一个点,双击结束测量</p>
</div>
<div v-else>
<p>0米²</p>
<p>点击开始测量</p>
</div>
</div>
</OlOverlay>
</template>
<script setup>
import {Draw} from "ol/interaction"
import numeral from "numeral"
import {getArea} from "ol/sphere"
import {getCenter} from "ol/extent"
import {Polygon} from "ol/geom"
defineOptions({
name: "OlArea"
})
let {map} = inject("openlayers")
let {current, source} = inject("OlMeasure")
let areaOverlay = ref([])
let currentOverlay = reactive({
position: [0, 0],
area: 0,
key: 0
})
let draw = new Draw({
source: source,
type: "Polygon",
minPoint: 3
})
let isShow = computed(() => current.value === "area")
watch(current, value => {
if (isShow.value) {
map.addInteraction(draw)
} else {
map.removeInteraction(draw)
areaOverlay.value = []
currentOverlay = reactive({
position: [0, 0],
area: 0,
key: 0
})
}
})
useEventListener(map, "contextmenu", ev => {
ev.preventDefault()
draw.removeLastPoint()
draw.removeLastPoint()
})
useEventListener(map, "pointermove", ev => {
if (!isShow.value) return
currentOverlay.position = ev.coordinate
if (get(draw, ["sketchLineCoords_", "length"], 0) < 3) return
let geom = new Polygon([draw.sketchLineCoords_])
currentOverlay.area = getArea(geom, {projection: "EPSG:4326"})
})
draw.on("drawend", ev => {
let {feature} = ev
let geometry = feature.getGeometry()
var extent = geometry.getExtent()
areaOverlay.value.push({
center: getCenter(extent),
area: currentOverlay.area,
key: Math.random(),
feature
})
currentOverlay = reactive({
position: [0, 0],
area: 0
})
})
const onClick = async () => {
if (isShow.value) return
current.value = "area"
}
const onClose = ({feature}, index) => {
source.removeFeature(feature)
areaOverlay.value.splice(index, 1)
}
</script>
<style lang="scss" scoped></style>