vue3 + openlayers實現(xiàn)在固定限制區(qū)域內(nèi)繪制和編輯多邊形

實現(xiàn)類似于電子圍欄的功能僵闯,在限制區(qū)域內(nèi)繪制和編輯多邊形存璃,不允許多邊形任何一個邊與限制區(qū)域邊界相交超過一個點憎兽。

先將限制邊界轉(zhuǎn)為turf下的linestring和polygon要素勿决。簡稱TL要素和TP要素愿棋。

主要實現(xiàn)思路是通過openlayers的Draw方法控制繪制點不超出限制邊界,再通過地圖單擊事件拿到鼠標(biāo)點下后的點诈嘿,將該點轉(zhuǎn)為turf的point點要素堪旧,通過turf的booleanPointInPolygon方法判斷該點是否在TP要素內(nèi),如果在TP要素內(nèi)并且該點與上一次點擊的點不是同一個點就記錄下該點奖亚,存儲到數(shù)組中 淳梦。通過當(dāng)前點擊的點與上一個點生成連線,通過turf將兩點生成的連線轉(zhuǎn)為truf的linestring要素昔字,再將限制邊界也轉(zhuǎn)為truf下的linestring要素爆袍,通過truf的lineIntersect判斷兩條linestring要素是否相交,如果相交點超過兩個作郭,通過removeLastPoint方法刪除最后繪制的點并且將其移出數(shù)組陨囊,以此達到多邊形不超出限制區(qū)域的目的。同理夹攒,地圖雙擊事件即是將鼠標(biāo)點擊下的點與上一個點和繪制的第一個點生成連線蜘醋,判斷相交點不超過兩個,達到目的

代碼部分
<template>
  <div id="map" class="_map" v-loading="loading" element-loading-text="加載中..."></div>
</template>
<script setup>
import 'ol/ol.css';
import { Map as olMap, View } from 'ol';
import { Tile as TileLayer, Vector as VectorLayer} from 'ol/layer';
import { Vector as VectorSource, WMTS } from 'ol/source';
import WMTSTileGrid from 'ol/tilegrid/WMTS';
import { getTopLeft } from 'ol/extent';
import { defaults as defaultControls } from 'ol/control';
import Modify from 'ol/interaction/Modify';
import Draw from 'ol/interaction/Draw'
import {Fill, Stroke, Style, Text, Circle, Icon} from 'ol/style';
import * as turf from '@turf/turf'
const { proxy } = getCurrentInstance() as ComponentInternalInstance;

const map = ref(); // 地圖
const limitationLayer = ref();   // 固定限制區(qū)域圖層
const drawLayer = ref();  // 圖形繪制后的顯示圖層
const linestring = ref(); // turf的linestring圖層
const polygon = ref();  // turf的polygon圖層
const draws = ref();  // 繪制圖形變量
const modifys = ref();  // 編輯圖形變量
const drawArr = ref([]); //
const controlBool = ref(true) // 控制僅允許繪制一個圖形

/** 初始化地圖 */
const initMap = () => {
    map.value = new olMap({
        target: 'map',
        layers: [
            addOpenMapTiandituLayer('img', '5f1d316698***********', 1, false),  // 換成自己的天地圖秘鑰
            addOpenMapTiandituLayer('ibo', '5f1d316698***********', 2, false),
            addOpenMapTiandituLayer('cia', '5f1d316698***********', 3, false),
        ],
        view: new View({                       // 地圖視圖
            projection: gridName,             // 坐標(biāo)系芹助,有EPSG:4326和EPSG:3857
            center: props.center,          // 中心點坐標(biāo)
            minZoom: props.zoomMin || 4,       // 地圖縮放最小級別
            extent:projectionExtent,        // 區(qū)域限制
            zoom: props.defaultZoom     // 地圖縮放級別(打開頁面時默認級別)
        }),
        controls: defaultControls({
            zoom: false,
            rotate: false,
            attribution: false
        })
    })
    /** 添加固定限制區(qū)域 */
    limitationLayer.value = addJsonVectorLayer('' ,9)
    /** 固定限制區(qū)域樣式 */
    limitationLayer.value.setStyle(createStyle({stroke: {width: 1, color: 'red'}, fill: {color: 'rgba(0,0,0,0)'}}))
    map.value.getView().fit(limitationLayer.value.getSource().getExtent() ,{ padding: [150, 300, 150, 300] })
    /** 轉(zhuǎn)換為turf的lineString圖層 */
    linestring.value = turf.lineString(limitationLayer.value.getSource().getFeatures()[0].getGeometry().getCoordinates()[0][0])
    /** 轉(zhuǎn)換為turf的polygon圖層 */
    polygon.value = turf.polygon(limitationLayer.value.getSource().getFeatures()[0].getGeometry().getCoordinates()[0])
    /** 添加繪制圖形圖層 */
    drawLayer.value = addEmptyVectorLayer(10)
    /** 繪制完成后的圖形樣式 */
    drawLayer.value.setStyle(createStyle({stroke: {color: '#FF8139', width: 3}, fill: {color: 'rgba(0,237,45,0)'}}))
    /** 添加圖形繪制交互 */
    draws.value = addDrawFeature(drawLayer.value, '', 'Polygon')
    /** 添加編輯交互 */
    modifys.value = modifyFeature(drawLayer.value)
    /** 地圖單擊事件堂湖,用于在限制區(qū)域內(nèi)打點,判斷當(dāng)前點和上一個點與當(dāng)前點的連線是否超出限制 */
    map.value.on('click', mapClick());
    /** 地圖雙擊事件状土,雙擊結(jié)束繪制 */
    map.value.on('dblclick', mapDoubleClick())
    /** 監(jiān)聽開始繪制事件 */
    draws.value.addEventListener('drawstart',(evt) => {
        evtList.value = evt
        drawArr.value = []
        modifys.value.setActive(false)
    })
    /** 監(jiān)聽結(jié)束繪制事件 */
    draws.value.addEventListener('drawend',(evt) => {
        draws.value.setActive(false)
        modifys.value.setActive(true)
        drawArr.value = []
        controlBool.value = false
    })
    let prevControl = null    // 記錄上一步編輯成功的圖層狀態(tài)
    /** 監(jiān)聽開始編輯事件 */
    modifys.value.on('modifystart', (e) => {
        prevControl = drawLayer.value.getSource().getFeatures()[0].clone()
    })
    /** 監(jiān)聽結(jié)束編輯事件 */
    modifys.value.on('modifyend', (e) => {
        let arrs = drawLayer.value.getSource().getFeatures()[0].getGeometry().getCoordinates()[0]
        let indexArr = []
        arrs.forEach((item, index) => {
            if(JSON.stringify(item) == JSON.stringify(e.mapBrowserEvent.coordinate)) {
                indexArr.push(index)
            }
        })
        if(indexArr.length == 0) {
            return false
        }
        let point = turf.point(arrs[indexArr[0]])
        let isPointInPolygon = turf.booleanPointInPolygon(point, polygon.value);

        if(!isPointInPolygon) {
            proxy?.$modal.msgWarning('當(dāng)前編輯的圖形超出限制區(qū)域无蜂,請重新編輯!')
            drawLayer.value.getSource().getFeatures()[0].setGeometry(prevControl.getGeometry())
        } else if(indexArr.length > 1) {
            let line1 = turf.lineString([arrs[0], arrs[1]])
            let line2 = turf.lineString([arrs[0], arrs[arrs.length - 2]])
            let intersects1 = turf.lineIntersect(line1, linestring.value);
            let intersects2 = turf.lineIntersect(line2, linestring.value);
            if(intersects1.features.length > 1 || intersects2.features.length > 1) {
                proxy?.$modal.msgWarning('當(dāng)前編輯的圖形超出限制區(qū)域蒙谓,請重新編輯斥季!')
                drawLayer.value.getSource().getFeatures()[0].setGeometry(prevControl.getGeometry())
            }
        } else if(indexArr.length == 1) {
            let line1 = turf.lineString([arrs[indexArr[0]], arrs[indexArr[0] - 1]])
            let line2 = turf.lineString([arrs[indexArr[0]], arrs[indexArr[0] + 1]])
            let intersects1 = turf.lineIntersect(line1, linestring.value);
            let intersects2 = turf.lineIntersect(line2, linestring.value);
            if(intersects1.features.length > 1 || intersects2.features.length > 1) {
                proxy?.$modal.msgWarning('當(dāng)前編輯的圖形超出限制區(qū)域,請重新編輯累驮!')
                drawLayer.value.getSource().getFeatures()[0].setGeometry(prevControl.getGeometry())
            }
        }
    })
    // draws.value.setActive(false)  //TODO 關(guān)閉繪制功能酣倾,可在初始化時關(guān)閉,自己控制是否繪制
    // modifys.value.setActive(false)  //TODO 關(guān)閉編輯功能谤专,可在初始化時關(guān)閉躁锡,自己控制是否編輯
}

/** 地圖單擊事件 */
function mapClick() {
    return function(event) {
        // 獲取點擊的坐標(biāo)
        if(controlBool.value) {
            let coordinates = event.coordinate;
            let point = turf.point(coordinates)
            let isPointInPolygon = turf.booleanPointInPolygon(point, polygon.value);
            if(drawArr.value) {
                if(isPointInPolygon && JSON.stringify(drawArr.value[drawArr.value.length - 1]) != JSON.stringify(coordinates)) {
                    drawArr.value.push(coordinates)
                }
                if(drawArr.value.length > 2) {
                    drawArr.value.splice(0, 1)
                }
                if(drawArr.value.length == 2) {
                    let line = turf.lineString(drawArr.value)
                    let intersects = turf.lineIntersect(line, linestring.value);
                    if(intersects.features.length > 1) {
                        proxy?.$modal.msgWarning('繪制圖形超出限制區(qū)域,請重新繪制置侍!')
                        drawArr.value.splice(drawArr.value.length - 1, 1)
                        draws.value.removeLastPoint()
                    }
                }
            }
        }
    }
}

/** 地圖雙擊事件 */
function mapDoubleClick() {
    return function(event) {
        let coordinates = event.coordinate;
        if(evtList.value) {
            let line = turf.lineString([coordinates, evtList.value.feature.getGeometry().getCoordinates()[0][0]])
            let intersects = turf.lineIntersect(line, linestring.value);
            let arrs = evtList.value.feature.getGeometry().getCoordinates()[0]
            arrs.forEach((item, index, arr) => {
                arr[index] = item.join(',')
            })
            if(intersects.features.length > 1) {
                proxy?.$modal.msgWarning('繪制圖形超出限制區(qū)域映之,請重新繪制拦焚!')
                drawArr.value.splice(drawArr.value.length - 1, 1)
                draws.value.removeLastPoint()
            } else if(Array.from(new Set(arrs)).length > 2){
                draws.value.finishDrawing()
                drawArr.value = []
            }
        }
    }
}

/** 底圖加載 */
function addOpenMapTiandituLayer(serviceName, tk, zIndex, addBool = true){
    /**
     * 添加天地圖
     * serviceName 選項如下,更多圖層查閱天地圖官方網(wǎng)站
     *      img: 影像底圖
     *      vec: 矢量底圖
     *      cva : 矢量注記
     *      ter: 地形暈渲
     * tk:秘鑰
     */
    const layerObj = new TileLayer({
        'zIndex':zIndex,
        'source': new WMTS({
            'url': "https://t{0-7}.tianditu.gov.cn/" + serviceName +"_c/wmts?tk="+tk,
            'crossOrigin':'anonymous',
            'tileGrid': new WMTSTileGrid({
                'origin': getTopLeft(projectionExtent),
                'resolutions':resolutions,
                'matrixIds':ids
            }),
            'format': 'tiles',
            'layer': serviceName,
            'matrixSet': 'c',
            'style': ''
        })
    });
    if(addBool){
        map.value.addLayer(layerObj)
    }
    return layerObj
}

/** 創(chuàng)建空矢量圖層 */
function addEmptyVectorLayer(zIndex, bool=true){
    const layerObj = new VectorLayer({
        'zIndex':zIndex || 1,
        'source': new VectorSource({
            'format': jsonFormat
        })
    });
    if(bool) {
        map.value.addLayer(layerObj);
    }
    return layerObj;
}

/** 添加本地json圖層 */
function addJsonVectorLayer (jsonData,zIndex, bool=true) {
    const layerObj = new VectorLayer({
        'zIndex':zIndex || 1,
        'source': new VectorSource({
            'features': jsonFormat.readFeatures(jsonData)
        })
    });
    if(bool) {
        map.value.addLayer(layerObj);
    }
    return layerObj;
}

/***********
 * 繪制點、線杠输、面
 * @param layer  繪制圖形圖層,必須為矢量圖層
 * @param styles 繪制過程中的圖形顏色
 * @param drawType 繪制類型 'Point' 'LineString' 'Polygon' 'Circle'
 */
function addDrawFeature(layer, styles, drawType= 'Point'){
    let drawStyle = styles ? styles : createStyle({ stroke:{color: '#FF8139', width: 3}, fill:{color: 'rgba(255, 255, 255, 0.3)'}, image: {radius: 5, stroke: {width: 1, color: '#FFFFFF'}, fill: {color: '#FF8139'}} })
    let draw = new Draw({
        source: layer.getSource(),
        type: drawType,
        style: drawStyle,
        condition: function (e) {
            let features = map.value.getFeaturesAtPixel(e.pixel, { layerFilter: function (layer) { return layer === limitationLayer.value; } });
            if (features != null && features.length > 0) {
                return true;
            } else {
                proxy?.$modal.msgWarning('繪制圖形超出限制區(qū)域赎败,請重新繪制!')
                return false;
            }
        },
        finishCondition:(e)=>{return false}
    })
    map.value.addInteraction(draw)
    return draw
}
/********
 * 選中圖形并編輯
 */
function modifyFeature(layer){
    const modify = new Modify({
        source: layer.getSource()
    });
    map.value.addInteraction(modify)
    return modify;
}

/** 創(chuàng)建圖層樣式 */
function createStyle(styleConfig){
    /**
     * 根據(jù) json 格式蠢甲,創(chuàng)建樣式僵刮,并返回 Style 對象
     */
    styleConfig = styleConfig || {};
    // 創(chuàng)建圖層樣式
    const styleObj = new Style();

    // 邊框
    const stroke = styleConfig['stroke'] || {};
    const strokeStyle = new Stroke({
        'color': stroke['color'] || 'rgba(0,0,255,1.0)',
        'width': stroke['width'] || 2,
        'lineDash': stroke['lineDash'] || ''
    });
    styleObj.setStroke(strokeStyle);

    // 填充
    const fill = styleConfig['fill'] || {};
    const fillStyle = new Fill({
        'color': fill['color'] || 'rgba(255,0,0,0.5)',
    });
    styleObj.setFill(fillStyle);

    // 文字
    const text = styleConfig['text'] || {};
    const textStyle = new Text({
        'text': text['text'] || '',
        'padding': text['padding'] || [0,0,0,0],
        'placement': text['placement'] || 'point',
        'overflow': text['overflow'] || false,
        'font': text['font'] || 'normal 14px 微軟雅黑',
        'offsetX': text['offsetX'] || 0,
        'offsetY': text['offsetY'] || 0
    });
    if (!!text['fill']){
        const fill = new Fill({
            'color':text.fill['color']
        });
        textStyle.setFill(fill);
    }
    if (!!text['backgroundFill']){
        const backgroundFill = new Fill({
            'color':text.backgroundFill['color']
        });
        textStyle.setBackgroundFill(backgroundFill);
    }
    if (!!text['stroke']){
        const stroke = new Stroke({
            'color':text.stroke['color']
        });
        textStyle.setStroke(stroke);
    }
    styleObj.setText(textStyle);

    // 圖片
    const icon = styleConfig['icon'];
    if (icon){
        let obj:any = {}
        if(icon['anchor']) {
            obj['anchor'] = icon['anchor']
        }
        if(icon['anchorOrigin']) {
            obj['anchorOrigin'] = icon['anchorOrigin']
        }
        if(icon['displacement']) {
            obj['displacement'] = icon['displacement']
        }
        if(icon['offset']) {
            obj['offset'] = icon['offset']
        }
        if(icon['src']) {
            obj['src'] = icon['src']
        }
        if(icon['img']) {
            obj['img'] = icon['img']
        }
        if(icon['imgSize']) {
            obj['imgSize'] = icon['imgSize']
        }
        if(icon['size']) {
            obj['size'] = icon['size']
        }
        const iconStyle = new Icon(obj);
        styleObj.setImage(iconStyle);
    }

    const image = styleConfig['image'];
    if(image){
        const imageStyle = new Circle();
        if(image['radius']){
            imageStyle.setRadius(image['radius'])
        }
        if(image['stroke']){
            const stroke = new Stroke({
                'color': image.stroke['color'],
                'width': image.stroke['width'] || 2
            });
            imageStyle.setStroke(stroke)
        }
        if(image['fill']){
            const fill = new Fill({
                'color': image.fill['color']
            });
            imageStyle.setFill(fill)
        }
        styleObj.setImage(imageStyle);
    }

    return styleObj;
}
onMounted(() => {
    initMap()
})
</script>
<style lang="scss" scoped>
._map {
    width: 100%;
    height: 100%;
    position: relative;
}
</style>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鹦牛,隨后出現(xiàn)的幾起案子搞糕,更是在濱河造成了極大的恐慌,老刑警劉巖能岩,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寞宫,死亡現(xiàn)場離奇詭異,居然都是意外死亡拉鹃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門鲫忍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膏燕,“玉大人,你說我怎么就攤上這事悟民“颖瑁” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵射亏,是天一觀的道長近忙。 經(jīng)常有香客問我,道長智润,這世上最難降的妖魔是什么及舍? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮窟绷,結(jié)果婚禮上锯玛,老公的妹妹穿的比我還像新娘。我一直安慰自己兼蜈,他們只是感情好攘残,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著为狸,像睡著了一般歼郭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辐棒,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天病曾,我揣著相機與錄音牍蜂,去河邊找鬼。 笑死知态,一個胖子當(dāng)著我的面吹牛捷兰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播负敏,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼贡茅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了其做?” 一聲冷哼從身側(cè)響起顶考,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎妖泄,沒想到半個月后驹沿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蹈胡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年渊季,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罚渐。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡却汉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出荷并,到底是詐尸還是另有隱情合砂,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布源织,位于F島的核電站翩伪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谈息。R本人自食惡果不足惜缘屹,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黎茎。 院中可真熱鬧囊颅,春花似錦、人聲如沸傅瞻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗅骄。三九已至胳挎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溺森,已是汗流浹背慕爬。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工窑眯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人医窿。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓磅甩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姥卢。 傳聞我的和親對象是個殘疾皇子卷要,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容