| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821 |
- <template>
- <div id="map" ref="map">
- <div class="toolbox">
- <button @click="loadMarker">加载标记</button>
- <button
- :style="{ backgroundColor: isMarking ? '#ff4d4f' : '', color: isMarking ? '#fff' : '' }"
- @click="markPlace"
- >
- {{ isMarking ? '结束标记' : '标记地点' }}
- </button>
- <button
- :style="{ backgroundColor: isDrawingCircle ? '#409EFF' : '', color: isDrawingCircle ? '#fff' : '' }"
- @click="drawCircle"
- >
- {{ isDrawingCircle ? '结束画圆' : '绘制圆形' }}
- </button>
- <button @click="measure('distance')">测距离</button>
- <button @click="measure('area')">测面积</button>
- <button @click="clear">清除</button>
- </div>
- <div ref="radiusOverlayLabel" class="ol-radius-marker" style="display: none;">
- {{ liveRadiusText }}
- </div>
- </div>
- </template>
- <script>
- import 'ol/ol.css'
- import Map from 'ol/Map'
- import View from 'ol/View'
- import { Vector as VectorSource, OSM, XYZ } from 'ol/source'
- import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
- // Draw 绘制功能几何的交互
- import { Draw, Modify } from 'ol/interaction'
- import Overlay from 'ol/Overlay'
- // 线条几何形状
- import { LineString } from 'ol/geom'
- // 具有几何和其他属性属性属性的地理特征的矢量对象,类似于像 GeoJSON 这样的矢量文件格式中的特征
- import Feature from 'ol/Feature'
- import { unByKey } from 'ol/Observable'
- // 获取几何形状的球形长度和面积
- import { getLength, getArea } from 'ol/sphere'
- import Style from 'ol/style/Style'
- import Stroke from 'ol/style/Stroke'
- import Fill from 'ol/style/Fill'
- import Circle from 'ol/style/Circle'
- import { fromLonLat, toLonLat } from 'ol/proj'
- import Icon from 'ol/style/Icon'
- import Point from 'ol/geom/Point'
- import Text from 'ol/style/Text'
- import { getMapMarks } from '@/api/map'
- export default {
- name: 'OpenLayersMap',
- data() {
- return {
- center: [104.068071, 30.576432],
- zoom: 12, // 比例尺 10 公里
- map: null,
- vectorLayer: null,
- vectorSource: null,
- // 标记地点
- isMarking: false,
- clickListenerKey: null,
- // --- 新增:绘制圆形相关变量 ---
- isDrawingCircle: false, // 是否处于画圆状态
- liveRadiusText: '', // 实时半径文字
- radiusOverlay: null, // 存放半径文本的 Overlay 实例
- draw: null,
- select: null,
- modify: null,
- measureType: 'distance',
- tipDiv: null,
- pointermoveEvent: null, // 地图pointermove事件
- sketchFeature: null, // 绘制的要素
- geometryListener: null, // 要素几何change事件
- measureResult: '0', // 测量结果
- markerSource: null // 新增:专门存放后台加载的标记点
- }
- },
- mounted() {
- this.markerSource = new VectorSource({ wrapX: false })
- const markerLayer = new VectorLayer({
- source: this.markerSource,
- name: '后台标记图层'
- })
- // 底图使用 OpenStreetMap, 对应 view
- var baseLayer = new TileLayer({
- source: new OSM()
- })
- // 矢量图层源
- this.vectorSource = new VectorSource({
- wrapX: false
- })
- // 矢量图层
- this.vectorLayer = new VectorLayer({
- source: this.vectorSource
- })
- var url = 'https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}'
- // 切换为高德地图的电子地图底图
- var amapLayer = new TileLayer({
- source: new XYZ({
- // 高德官方切片地址(t1-t4可选,lang=zh_cn代表中文,style=7代表标准地图)
- url: url,
- crossOrigin: 'anonymous',
- maxZoom: 18
- }),
- name: '高德底图'
- })
- /* this.map = new Map({
- layers: [baseLayer, this.vectorLayer, markerLayer],
- target: 'map',
- view: new View({
- projection: 'EPSG:4326',
- center: [104.06531800244139, 30.65852484539117],
- zoom: 10
- })
- })*/
- this.map = new Map({
- layers: [amapLayer, this.vectorLayer, markerLayer], // 替换掉 baseLayer
- target: 'map',
- view: new View({
- projection: 'EPSG:3857', // ⚠️注意:高德切片是墨卡托(3857)投影,建议View也改为3857
- center: fromLonLat(this.center), // 用 fromLonLat 转换中心点
- zoom: 12,
- maxZoom: 18
- })
- })
- },
- created() {
- document.title = 'OpenLayersMap'
- },
- methods: {
- drawCircle() {
- this.isDrawingCircle = !this.isDrawingCircle
- // 清理上一次的组件
- if (this.draw) this.map.removeInteraction(this.draw)
- if (this.modify) this.map.removeInteraction(this.modify)
- if (this.radiusOverlay) this.map.removeOverlay(this.radiusOverlay)
- if (this.isDrawingCircle) {
- this.map.getTargetElement().style.cursor = 'crosshair'
- // 1. 创建半径气泡标签 DOM
- const labelEl = document.createElement('div')
- labelEl.className = 'ol-radius-marker'
- this.radiusOverlay = new Overlay({
- element: labelEl,
- offset: [15, -15],
- positioning: 'bottom-left',
- stopEvent: false
- })
- this.map.addOverlay(this.radiusOverlay)
- // 2. 初始化绘制器
- this.draw = new Draw({
- source: this.vectorSource,
- type: 'Circle'
- })
- let centerFeature = null
- // 3. 监听开始绘制
- this.draw.on('drawstart', (evt) => {
- const geometry = evt.feature.getGeometry()
- // 绘制圆心红点
- const initialCenter = geometry.getCenter()
- centerFeature = new Feature({ geometry: new Point(initialCenter) })
- centerFeature.setStyle(new Style({
- image: new Circle({
- radius: 5,
- fill: new Fill({ color: '#ff4d4f' }),
- stroke: new Stroke({ color: '#ffffff', width: 2 })
- })
- }))
- this.vectorSource.addFeature(centerFeature)
- // 绑定高频拖拽计算半径事件
- const bindGeometryChange = (geom) => {
- geom.on('change', () => {
- const radiusInMeters = geom.getRadius()
- const text = radiusInMeters < 1000 ? `${radiusInMeters.toFixed(1)}m` : `${(radiusInMeters / 1000).toFixed(2)}km`
- labelEl.innerText = text
- // 标签死死跟着圆周上的鼠标/控制点边缘
- const lastCoordinate = geom.getLastCoordinate()
- if (this.radiusOverlay) {
- this.radiusOverlay.setPosition(lastCoordinate)
- }
- // 同步纠正圆心物理位置
- if (centerFeature) centerFeature.getGeometry().setCoordinates(geom.getCenter())
- })
- }
- bindGeometryChange(geometry)
- // 将这个绑定函数挂载到 feature 上,方便后期二次编辑修改时复用
- evt.feature.set('changeBinder', bindGeometryChange)
- })
- // 4. 核心新增:监听绘制结束,立刻无缝移交控制权给 Modify
- this.draw.on('drawend', (evt) => {
- // 移除画圆交互(不让继续画新圆了),但保留图形和标签
- this.map.removeInteraction(this.draw)
- this.map.getTargetElement().style.cursor = 'default'
- const finishedFeature = evt.feature
- // 5. 激活修改交互器(传入指定的图层源)
- this.modify = new Modify({
- source: this.vectorSource
- })
- this.map.addInteraction(this.modify)
- // 6. 当用户再次用鼠标按住圆周边缘控制点拖拽调整时
- this.modify.on('modifystart', (mEvt) => {
- // 重新绑定几何变化监听,让半径气泡文字在拖拽修改时再度跟着动起来!
- mEvt.features.forEach((f) => {
- const geom = f.getGeometry()
- const binder = f.get('changeBinder')
- if (binder) binder(geom)
- })
- })
- this.modify.on('modifyend', () => {
- console.log('圆半径二次调整结束')
- })
- })
- this.map.addInteraction(this.draw)
- } else {
- // 退出状态
- this.map.getTargetElement().style.cursor = ''
- }
- },
- initFeatureClick() {
- this.map.on('singleclick', (evt) => {
- // 核心:如果是地点标记状态,优先允许用户打点,不触发图标点击事件
- if (this.isMarking) return
- // 1. 获取点击处的屏幕像素坐标
- const pixel = this.map.getEventPixel(evt.originalEvent)
- // 2. 探测当前像素点下是否存在 Feature 要素
- const feature = this.map.forEachFeatureAtPixel(pixel, (clickedFeature) => {
- return clickedFeature // 如果有,直接返回这个要素
- })
- // 3. 如果抓到了要素,说明用户点中了某个图标
- if (feature) {
- // 4. 健壮性检查:通过我们在创建要素时注入的 customType 属性,判定它是不是我们要的标记点
- const customType = feature.get('customType')
- if (customType === 'api-marker') {
- const name = feature.get('title')
- const id = feature.get('id')
- this.$message.info(`你点击了后台标记点:${name} (ID: ${id})`)
- } else if (feature.getGeometry() instanceof Point && !customType) {
- // === 处理手动通过 markPlace 打的橙色小圆点点击 ===
- // 因为在 addMarker 里你没有加 customType,但它的几何类型是 Point
- this.$message.info('你点击了手动标记的圆点位置')
- // 示例:点击手动标记点时,可以把它从数据源中删掉
- // this.vectorSource.removeFeature(feature)
- }
- }
- })
- },
- // 加载标记点主方法
- async loadMarker() {
- this.initFeatureClick()
- // 1. 先清除之前加载过的历史标记
- if (this.markerSource) {
- this.markerSource.clear()
- }
- getMapMarks().then(resp => {
- if (resp.code === 0) {
- for (const item of resp.data) {
- const numLng1 = item.lng
- const numLat1 = item.lat
- // 创建要素几何(使用转换后的坐标,如果是高德底图,记得在外面套一层 fromLonLat)
- const finalCoord = fromLonLat([numLng1, numLat1]) // 如果底图是高德请解开这行,下面传入 finalCoord
- const feature = new Feature({
- geometry: new Point(finalCoord),
- customType: 'api-marker',
- ...item
- })
- var text = item.title + '\n[,' + numLng1.toFixed(4) + ' ' + numLat1.toFixed(4) + ']'
- // 设置样式
- feature.setStyle(
- new Style({
- // 1. 图标样式
- image: new Icon({
- src: 'https://cdn-icons-png.flaticon.com/512/684/684908.png',
- crossOrigin: 'anonymous',
- anchor: [0.5, 1], // 锚点在底部尖角,图标整体向上伸展约 36 像素
- scale: 0.07
- }),
- // 2. 文字样式
- text: new Text({
- text: text,
- font: '12px sans-serif',
- fill: new Fill({ color: '#3a55c2' }),
- stroke: new Stroke({ color: '#ffffff', width: 3 }),
- offsetY: -42, // 调大负数!直接把文字推到图标头顶上方(36px 图标高度 + 6px 呼吸间距)
- textBaseline: 'bottom', // 保持底部对齐,确保多行文本也能整齐向上排列
- placement: 'point'
- })
- })
- )
- // 添加到数据源
- this.markerSource.addFeature(feature)
- if (this.markerSource && this.markerSource.getFeatures().length > 0) {
- const extent = this.markerSource.getExtent()
- // 安全校验
- if (extent && isFinite(extent[0]) && isFinite(extent[1])) {
- this.map.getView().fit(extent, {
- padding: [50, 50, 50, 50],
- maxZoom: 14,
- duration: 800
- })
- }
- }
- }
- }
- }).catch(error => {
- console.error('加载标记点失败:', error.message)
- })
- },
- // 在地图上标记经纬度
- addMarker(coordinate) {
- // 经度(保留6位小数)
- const lng = coordinate[0].toFixed(6)
- // 纬度(保留6位小数)
- const lat = coordinate[1].toFixed(6)
- /* const lng = Number(lngStr)
- const lat = Number(latStr)*/
- // 1. 创建一个点几何
- const pointGeom = new Point([lng, lat])
- // 2. 创建一个要素(Feature),并将几何图形赋予它
- const markerFeature = new Feature({
- geometry: pointGeom
- })
- const lonLat = toLonLat(coordinate)
- const lng1 = lonLat[0].toFixed(6)
- const lat1 = lonLat[1].toFixed(6)
- // 格式化需要显示的文字(保留6位小数)
- const textLabel = `[${lng1}, ${lat1}]`
- // 3. 设置标记的样式(这里画一个实心圆,你也可以用 Icon 贴图)
- markerFeature.setStyle(
- new Style({
- text: new Text({
- text: textLabel, // 要显示的文字内容
- font: '12px sans-serif', // 字体大小和家族
- fill: new Fill({ color: '#333333' }), // 文字颜色
- stroke: new Stroke({ color: '#ffffff', width: 3 }), // 文字白边背景(防瞎,更清晰)
- offsetY: -12, // 改为负数,将文字向上推 12 像素
- textBaseline: 'bottom', // 文本基线设为底部,确保文字的屁股对齐偏移位置,不会压到圆点
- placement: 'point' // 渲染类型
- }),
- image: new Circle({
- radius: 4, // 圆半径
- fill: new Fill({ color: '#ff7300' }), // 填充红色
- stroke: new Stroke({ color: '#ffffff', width: 2 }) // 白色外边框
- })
- })
- )
- // 4. 将这个要素添加到你现有的矢量图层源中
- // 注意:这里建议先清除之前的标记,或者你可以专门建一个标记图层
- this.vectorSource.addFeature(markerFeature)
- // 5. 动画平移让地图中心对准这个标记点
- /* this.map.getView().animate({
- center: [lng, lat],
- duration: 800, // 动画持续时间(毫秒)
- zoom: 14 // 顺便放大地图级别
- })*/
- },
- creatDraw(type) {
- let maxPoints = null
- if (this.measureType === 'angle') maxPoints = 3
- else maxPoints = null
- // 矢量图层源
- const vectorSource = new VectorSource({
- wrapX: false
- })
- // 矢量图层
- this.vectorLayer = new VectorLayer({
- source: vectorSource,
- style: new Style({
- fill: new Fill({
- color: 'rgba(252, 86, 49, 0.1)'
- }),
- stroke: new Stroke({
- color: '#fc5531',
- width: 3
- }),
- image: new Circle({
- radius: 0,
- fill: new Fill({
- color: '#fc5531'
- })
- })
- }),
- name: '测量图层'
- })
- this.map.addLayer(this.vectorLayer)
- this.draw = new Draw({
- source: vectorSource,
- type: type,
- maxPoints: maxPoints,
- style: new Style({
- fill: new Fill({
- color: 'rgba(252, 86, 49, 0.1)'
- }),
- stroke: new Stroke({
- color: '#fc5531',
- lineDash: [10, 10],
- width: 3
- }),
- image: new Circle({
- radius: 0,
- fill: new Fill({
- color: '#fc5531'
- })
- })
- }),
- // 绘制时点击处理事件
- condition: (evt) => {
- // 测距时添加点标注
- if (this.measureResult !== '0' && !this.map.getOverlayById(this.measureResult) && this.measureType === 'distance') {
- this.creatMark(
- null,
- this.measureResult,
- this.measureResult
- ).setPosition(evt.coordinate)
- }
- return true
- }
- })
- this.map.addInteraction(this.draw)
- /**
- * 绘制开始事件
- */
- this.draw.on('drawstart', (e) => {
- this.sketchFeature = e.feature
- const proj = this.map.getView().getProjection()
- //* *****距离测量开始时*****//
- if (this.measureType === 'distance') {
- this.creatMark(null, '起点', 'start').setPosition(
- this.map.getCoordinateFromPixel(e.target.downPx_)
- )
- this.tipDiv.innerHTML = '总长:0 m</br>单击确定地点,双击结束'
- this.geometryListener = this.sketchFeature
- .getGeometry()
- .on('change', (evt) => {
- this.measureResult = this.distanceFormat(
- getLength(evt.target, { projection: proj, radius: 6378137 })
- )
- this.tipDiv.innerHTML =
- '总长:' + this.measureResult + '</br>单击确定地点,双击结束'
- })
- } else if (this.measureType === 'area') {
- //* *****面积测量开始时*****//
- this.tipDiv.innerHTML = '面积:0 m<sup>2</sup></br>继续单击确定地点'
- this.geometryListener = this.sketchFeature
- .getGeometry()
- .on('change', (evt) => {
- if (evt.target.getCoordinates()[0].length < 4) {
- this.tipDiv.innerHTML =
- '面积:0m<sup>2</sup></br>继续单击确定地点'
- } else {
- this.measureResult = this.formatArea(
- getArea(evt.target, { projection: proj, radius: 6378137 })
- )
- this.tipDiv.innerHTML =
- '面积:' + this.measureResult + '</br>单击确定地点,双击结束'
- }
- })
- } else if (this.measureType === 'angle') {
- //* *****角度测量开始时*****//
- this.tipDiv.innerHTML = '继续单击确定顶点'
- this.geometryListener = this.sketchFeature
- .getGeometry()
- .on('change', (evt) => {
- if (evt.target.getCoordinates().length < 3) { this.tipDiv.innerHTML = '继续单击确定顶点' } else {
- this.measureResult = this.formatAngle(evt.target)
- this.tipDiv.innerHTML =
- '角度:' + this.measureResult + '</br>继续单击结束'
- }
- })
- }
- })
- /**
- * 绘制开始事件
- */
- this.draw.on('drawend', (e) => {
- const closeBtn = document.createElement('span')
- closeBtn.innerHTML = '×'
- closeBtn.title = '清除测量'
- closeBtn.style =
- 'width: 10px;height:10px;line-height: 12px;text-align: center;border-radius: 5px;display: inline-block;padding: 2px;color: rgb(255, 68, 0);border: 2px solid rgb(255, 68, 0);background-color: rgb(255, 255, 255);font-weight: 600;position: absolute;top: -25px;right: -2px;cursor: pointer;'
- closeBtn.addEventListener('click', () => {
- this.clearMeasure()
- })
- //* *****距离测量结束时*****//
- if (this.measureType === 'distance') {
- this.creatMark(closeBtn, null, 'close1').setPosition(
- e.feature.getGeometry().getLastCoordinate()
- )
- this.creatMark(
- null,
- '总长:' + this.measureResult + '',
- 'length'
- ).setPosition(e.feature.getGeometry().getLastCoordinate())
- this.map.removeOverlay(this.map.getOverlayById(this.measureResult))
- } else if (this.measureType === 'area') {
- //* *****面积测量结束时*****//
- this.creatMark(closeBtn, null, 'close2').setPosition(
- e.feature.getGeometry().getInteriorPoint().getCoordinates()
- )
- this.creatMark(
- null,
- '总面积:' + this.measureResult + '',
- 'area'
- ).setPosition(
- e.feature.getGeometry().getInteriorPoint().getCoordinates()
- )
- } else if (this.measureType === 'angle') {
- //* *****角度测量结束时*****//
- this.creatMark(closeBtn, null, 'close3').setPosition(
- e.feature.getGeometry().getCoordinates()[1]
- )
- this.creatMark(
- null,
- '角度:' + this.measureResult + '',
- 'angle'
- ).setPosition(e.feature.getGeometry().getCoordinates()[1])
- }
- // 停止测量
- this.stopMeasure()
- })
- },
- markPlace() {
- // 切换状态
- this.isMarking = !this.isMarking
- if (this.isMarking) {
- // 1. 改变鼠标指针为十字准星
- this.map.getTargetElement().style.cursor = 'crosshair'
- // 2. 定义点击时的执行函数
- const handleMapClick = (evt) => {
- // 获取点击位置的原始墨卡托坐标(高德地图) [米, 米]
- // evt.coordinate 是一个数组:[lng, lat]
- const coordinate = evt.coordinate
- this.addMarker(coordinate)
- }
- // 3. 绑定持久点击事件,并把返回的 key 存起来以便后续解绑
- this.clickListenerKey = this.map.on('singleclick', handleMapClick)
- } else {
- // ================= 退出标记地点状态 =================
- // 1. 恢复默认鼠标指针
- this.map.getTargetElement().style.cursor = ''
- // 2. 核心:解除绑定的点击事件,让地图恢复正常
- if (this.clickListenerKey) {
- // OpenLayers 6+ 中解绑事件的方式:使用 ol/Observable 的 unByKey 或者直接传入 key
- // 如果你的版本支持, 可以直接使用下面的标准方法:
- this.map.un('singleclick', this.clickListenerKey.listener)
- this.clickListenerKey = null
- }
- }
- },
- /**
- * 测量
- */
- measure(type) {
- if (this.draw !== null) return false // 防止在绘制过程再创建测量
- this.measureType = type
- if (this.vectorLayer !== null) this.clearMeasure()
- this.tipDiv = document.createElement('div')
- this.tipDiv.innerHTML = '单击确定起点'
- this.tipDiv.className = 'tipDiv'
- this.tipDiv.style = 'width:auto;height:auto;padding:4px;border:1px solid #fc5531;font-size:12px;background-color:#fff;position:relative;top:60%;left:60%;font-weight:600;'
- const overlay = new Overlay({
- element: this.tipDiv,
- autoPan: false,
- positioning: 'bottom-center',
- id: 'tipLay',
- stopEvent: false // 停止事件传播到地图
- })
- this.map.addOverlay(overlay)
- this.pointermoveEvent = this.map.on('pointermove', (evt) => {
- overlay.setPosition(evt.coordinate)
- })
- if (this.measureType === 'distance' || this.measureType === 'angle') {
- this.creatDraw('LineString')
- } else if (this.measureType === 'area') {
- this.creatDraw('Polygon')
- }
- },
- /**
- * 创建标记
- */
- creatMark(markDom, txt, idstr) {
- if (markDom === null) {
- markDom = document.createElement('div')
- markDom.innerHTML = txt
- markDom.style =
- 'width:auto;height:auto;padding:4px;border:1px solid #fc5531;font-size:12px;background-color:#fff;position:relative;top:60%;left:60%;font-weight:600;'
- }
- const overlay = new Overlay({
- element: markDom,
- autoPan: false,
- positioning: 'bottom-center',
- id: idstr,
- stopEvent: false
- })
- this.map.addOverlay(overlay)
- return overlay
- },
- /**
- * 格式化距离结果输出
- */
- distanceFormat(length) {
- let output
- if (length > 100) {
- output = Math.round((length / 1000) * 100) / 100 + ' ' + 'km' // 换算成km单位
- } else {
- output = Math.round(length * 100) / 100 + ' ' + 'm' // m为单位
- }
- return output // 返回线的长度
- },
- /**
- * 格式化面积输出
- */
- formatArea(area) {
- let output
- if (area > 10000) {
- output =
- Math.round((area / 1000000) * 100) / 100 + ' ' + 'km<sup>2</sup>' // 换算成km单位
- } else {
- output = Math.round(area * 100) / 100 + ' ' + 'm<sup>2</sup>' // m为单位
- }
- return output // 返回多边形的面积
- },
- /**
- * 计算角度输出
- */
- formatAngle(line) {
- var coordinates = line.getCoordinates()
- var angle = '0°'
- if (coordinates.length === 3) {
- const disa = getLength(
- new Feature({
- geometry: new LineString([coordinates[0], coordinates[1]])
- }).getGeometry(),
- {
- radius: 6378137,
- projection: this.map.getView().getProjection()
- }
- )
- const disb = getLength(
- new Feature({
- geometry: new LineString([coordinates[1], coordinates[2]])
- }).getGeometry(),
- {
- radius: 6378137,
- projection: this.map.getView().getProjection()
- }
- )
- const disc = getLength(
- new Feature({
- geometry: new LineString([coordinates[0], coordinates[2]])
- }).getGeometry(),
- {
- radius: 6378137,
- projection: this.map.getView().getProjection()
- }
- )
- var cos = (disa * disa + disb * disb - disc * disc) / (2 * disa * disb) // 计算cos值
- angle = (Math.acos(cos) * 180) / Math.PI // 角度值
- angle = angle.toFixed(2) // 结果保留两位小数
- }
- if (isNaN(angle)) return '0°'
- else return angle + '°' // 返回角度
- },
- /**
- * 停止测量
- */
- stopMeasure() {
- this.tipDiv = null
- this.map.removeInteraction(this.draw) // 移除绘制组件
- this.draw = null
- this.map.removeOverlay(this.map.getOverlayById('tipLay')) // 移除动态提示框
- },
- /**
- * 清除测量
- */
- clearMeasure() {
- this.vectorLayer.getSource().clear()
- this.map.getOverlays().clear()
- // 移除监听事件
- unByKey(this.pointermoveEvent) // 清除鼠标在地图的pointermove事件
- unByKey(this.geometryListener) // 清除绘制图像change事件
- this.pointermoveEvent = null
- this.geometryListener = null
- this.measureResult = '0'
- },
- // 坐标
- single() {
- this.map.on('singleclick', function(e) {
- console.log(e.coordinate)
- })
- },
- // 清除
- clear() {
- // 清空测量矢量图层
- if (this.vectorLayer) this.vectorLayer.getSource().clear()
- // 清空后台加载的标记图层
- if (this.markerSource) this.markerSource.clear()
- // 清空所有弹窗 Overlay
- this.map.getOverlays().clear()
- }
- }
- }
- </script>
- <style scoped>
- #map {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- /* 右上角工具箱容器 */
- .toolbox {
- position: absolute;
- z-index: 999;
- top: 20px; /* 距离顶部 20px */
- right: 20px; /* 距离右侧 20px */
- background: rgba(255, 255, 255, 0.9); /* 半透明白底背景 */
- padding: 8px;
- border-radius: 6px; /* 圆角 */
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15); /* 地图控件常用的阴影 */
- display: grid;
- grid-template-columns: repeat(2, 75px); /* 固定为两列,每列宽 75px */
- gap: 6px; /* 按钮之间的间距 */
- }
- /* 工具箱内的按钮通用样式 */
- .toolbox button {
- height: 28px;
- border: 1px solid #dcdfe6;
- background-color: #fff;
- color: #606266;
- font-size: 12px;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.2s;
- padding: 0 4px;
- }
- /* 鼠标悬浮和点击效果 */
- .toolbox button:hover {
- color: #fc5531;
- border-color: #fc5531;
- background-color: rgba(252, 86, 49, 0.05);
- }
- /* 实时跟随鼠标的半径标签气泡样式 */
- .ol-radius-marker {
- background: rgba(64, 158, 255, 0.95); /* 漂亮的科技蓝 */
- color: #ffffff;
- padding: 4px 8px;
- border-radius: 4px;
- font-size: 11px;
- font-weight: bold;
- white-space: nowrap;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
- border: 1px solid #ffffff;
- position: relative;
- pointer-events: none; /* 极其重要:确保 DOM 穿透,否则会挡住鼠标导致无法继续绘制 */
- }
- /* 气泡下方的微型小尖角 */
- .ol-radius-marker::after {
- content: "";
- position: absolute;
- bottom: -4px;
- left: 10px;
- border-width: 4px 4px 0;
- border-style: solid;
- border-color: rgba(64, 158, 255, 0.95) transparent;
- }
- </style>
|