OpenLayers 开启节点捕捉(Snap)

90 阅读6分钟

前言

在GIS开发中,节点捕捉是一种基础且常见的功能。节点捕捉是指在地图绘制或者编辑过程中,将点、线、面几何对象对象自动对齐到其他要素的节点、端点或边界的操作。节点捕捉在图形绘制、编辑以及拓扑检查中具有广泛应用,是GIS数据处理的基础工具。

在本例子中以加载云南省GoeJSON数据为例,对如何使用OpenLayers实现节点捕捉功能进行讲解。

1. 创建标注样式

首先对云南省行政区数据设置半透明填充和描边样式,然后对标注文本设置填充和描边样式。defaultFillColor为默认填充色,在要素不需要高亮显示后恢复为初始填充色时使用。

// 文字样式
const labelStyle new ol.style.Style({
    textnew ol.style.Text({
        font'12px Calibri,sans-serif',
        overflowtrue,
        // 填充色
        fillnew ol.style.Fill({
            color"#FAFAD2"
        }),
        // 描边色
        strokenew ol.style.Stroke({
            color"#2F4F4F",
            width3,
        })
    })
})
// 要素样式
const defaultFillColor = [2302302500.25]
const regionStyle new ol.style.Style({
    fillnew ol.style.Fill({
        color: defaultFillColor,
    }),
    strokenew ol.style.Stroke({
        color"#00FFFF",
        width1.25,
    }),
})

在以上代码中分别创建了图层渲染样式以及文本标注样式,在OpenLayers中使用Style对象管理样式,Fill对象设置要素填充颜色,Stroke对象设置描边(边线)样式,Text对象用于设置文本样式。

在以下代码中创建的是绘制样式,与前面的图层样式一致,可以为绘制图层和绘制对象添加渲染样式。

// 绘制样式
const drawStyle new ol.style.Style({
    fillnew ol.style.Fill({
        color: [2552331500.15]
    }),
    strokenew ol.style.Stroke({
        color: [255233150],
        width1.25
    }),
    // 绘制鼠标跟随样式
    imagenew ol.style.Circle({
        radius5,
        fillnew ol.style.Fill({
            color: [2552331500.65]
        }),
        strokenew ol.style.Stroke({
            color: [2491988],
            width1.25
        }),
    })
})

其中样式属性image用于在进行图形绘制时,设置鼠标指针的圆形样式,该样式使用Circle类实现。若在绘制时不想显示该圆形要素,可以不设置image属性。

2. 创建交互控件

创建绘制图层对象,使用绘制样式进行渲染。使用set方法为绘制图层对象添加"layerName"属性,设置其之为"tempLayer",用于根据图层名移除目标图层。

// 添加绘制图层
drawLayer = new ol.layer.Vector({
    sourcenew ol.source.Vector(),
    style: drawStyle
})
drawLayer.set("layerName""tempLayer")
map.addLayer(drawLayer)

创建节点捕捉控件,只需要在Snap交互控件中设置捕捉数据源。

// 节点捕捉控件
snapInteraction = new ol.interaction.Snap({
    source: regionLayer.getSource()
})

Snap对象属性不多,捕捉要素集features和捕捉数据源source两者必须要提供其中一个。

创建绘制控件,在Draw交互对象中需要设置绘制数据源对象、绘制对象类型,并设置绘制样式。trace属性用于设置是否开启节点捕捉;traceSource属性用于设置在绘制过程中进行捕捉的数据源对象。


// 绘制交互控件
drawInteraction = new ol.interaction.Draw({
    source: drawLayer.getSource(),
    style: drawStyle,
    type: drawType,
    trace: true,  // 开启节点捕捉
    traceSource: regionLayer.getSource() // 节点捕捉数据源
})
map.addInteraction(drawInteraction)
map.addInteraction(snapInteraction)

将以上对象抽取汇集到addInteraction方法中如下。

// 添加交互控件
function addInteraction(type{
    // 添加绘制图层
    drawLayer = new ol.layer.Vector({
        sourcenew ol.source.Vector(),
        style: drawStyle
    })
    drawLayer.set("layerName""tempLayer")
    map.addLayer(drawLayer)

    switch (type) {
        case "line":
            drawType = "LineString"
            break
        case "polygon":
            drawType = "Polygon"
            break
    }
    // 节点捕捉控件
    snapInteraction = new ol.interaction.Snap({
        source: regionLayer.getSource()
    })
    // 绘制交互控件
    drawInteraction = new ol.interaction.Draw({
        source: drawLayer.getSource(),
        style: drawStyle,
        type: drawType,
        tracetrue,  // 开启节点捕捉
        traceSource: regionLayer.getSource() // 节点捕捉数据源
    })
    map.addInteraction(drawInteraction)
    map.addInteraction(snapInteraction)
}

3. 节点捕捉效果

在开始绘制时,在捕捉要素边缘上点击一下开始进行捕捉绘制,再次点击地图或者移动地图将关闭捕捉绘制。

4. 完整代码

其中libs文件夹下的包需要更换为自己下载的本地包或者引用在线资源。

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>OpenLayers 节点捕捉</title>
    <meta charset="utf-8" />

    <link rel="stylesheet" href="../../libs/css/ol9.2.4.css">

    <script src="../../js/config.js"></script>
    <script src="../../js/util.js"></script>
    <script src="../../libs/js/ol9.2.4.js"></script>
    <style>
        * {
            padding0;
            margin0;
            font-size14px;
            font-family'微软雅黑';
        }

        html,
        body {
            width100%;
            height100%;
        }

        #map {
            position: absolute;
            top50px;
            bottom0;
            width100%;
        }

        #top-content {
            position: absolute;
            width100%;
            height50px;
            line-height50px;
            backgroundlinear-gradient(135deg#ff00cc#ffcc00#00ffcc#ff0066);
            color#fff;
            text-align: center;
            font-size32px;
        }

        #top-content span {
            font-size32px;
        }

        .main-container {
            position: absolute;
            padding5px;
            top60px;
            left100px;
            background-color#ffcc00ad;
            border-radius2.5px;
        }

        .main-ul {
            list-style-type: none;
        }

        .main-ul::after {
            display: block;
            clear: both;
            content"";
        }

        .main-li {
            float: left;
            padding5px;
            margin0 5px;
            min-width50px;
            background-color: azure;
            background#cae4cf82;
            transition: background-color 10s ease-in-out 10s;
            text-align: center;
            border-radius2.5px;
        }

        .main-li:hover {
            cursor: pointer;
            color#fff;
            filterbrightness(110%);
            backgroundlinear-gradient(135deg#c850c0#4158d0);
        }

        .active {
            backgroundlinear-gradient(135deg#c850c0#4158d0);
            color#fff;
            filterbrightness(120%);
        }
    </style>
</head>

<body>
    <div id="top-content">
        <span>OpenLayers 节点捕捉</span>
    </div>
    <div id="map" title=""></div>
    <div class="main-container">
        <ul class="main-ul">
            <li class="main-li" data-type="line">绘线</li>
            <li class="main-li" data-type="polygon">绘面</li>
            <li class="main-li" data-type="none">清除</li>
        </ul>
    </div>
</body>

</html>

<script>
    //==============================================================================//
    //============================天地图服务参数简单介绍==============================//
    //================================vec:矢量图层==================================//
    //================================img:影像图层==================================//
    //================================cva:注记图层==================================//
    //======================其中:_c表示经纬度投影,_w表示球面墨卡托投影================//
    //==============================================================================//
    const TDTImgLayer = new ol.layer.Tile({
        title"天地图影像图层",
        sourcenew ol.source.XYZ({
            url"http://t0.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=" + TDTTOKEN,
            attibutions"天地图影像描述",
            crossOrigin"anoymous",
            wrapXfalse
        })
    })
    const TDTImgCvaLayer = new ol.layer.Tile({
        title"天地图影像注记图层",
        sourcenew ol.source.XYZ({
            url"http://t0.tianditu.com/DataServer?T=cia_w&x={x}&y={y}&l={z}&tk=" + TDTTOKEN,
            attibutions"天地图注记描述",
            crossOrigin"anoymous",
            wrapXfalse
        })
    })
    const map = new ol.Map({
        target"map",
        loadTilesWhileInteractingtrue,
        viewnew ol.View({
            center: [102.84586425.421639],
            zoom8,
            worldsWrapfalse,
            minZoom1,
            maxZoom20,
            projection'EPSG:4326',
        }),
        layers: [TDTImgLayer],
        // 地图默认控件
        controls: ol.control.defaults.defaults({
            zoomfalse,
            attributionfalse,
            rotatefalse
        })
    })

    // 文本标注样式
    const labelStyle = new ol.style.Style({
        textnew ol.style.Text({
            font'12px Calibri,sans-serif',
            overflowtrue,
            // 填充色
            fillnew ol.style.Fill({
                color"#FAFAD2"
            }),
            // 描边色
            strokenew ol.style.Stroke({
                color"#2F4F4F",
                width3,
            }),
        })
    })
    // 要素样式
    const defaultFillColor = [2302302500.25]
    const regionStyle = new ol.style.Style({
        fillnew ol.style.Fill({
            color: defaultFillColor,
        }),
        strokenew ol.style.Stroke({
            color"#00FFFF",
            width1.25,
        }),
    })

    // 绘制样式
    const drawStyle = new ol.style.Style({
        fillnew ol.style.Fill({
            color: [2552331500.15]
        }),
        strokenew ol.style.Stroke({
            color: [255233150],
            width1.25
        }),
        // 绘制鼠标跟随样式
        imagenew ol.style.Circle({
            radius5,
            fillnew ol.style.Fill({
                color: [2552331500.65]
            }),
            strokenew ol.style.Stroke({
                color: [2491988],
                width1.25
            }),
        })
    })
    const style = [labelStyle, regionStyle]
    const JSON_URL = "../../data/geojson/yn_region.json"

    // 行政区GeoJSON图层
    const regionLayer = new ol.layer.Vector({
        sourcenew ol.source.Vector({
            urlJSON_URL,
            formatnew ol.format.GeoJSON()
        }),
        stylefunction (feature) {
            const label = feature.get("name").split(" ").join("n")
            labelStyle.getText().setText(label)
            return style
        },
        decluttertrue
    })
    map.addLayer(regionLayer)
    map.getView().setCenter([101.48510625.008643])
    map.getView().setZoom(6.5)

    let drawLayer = undefined           // 绘制图层
    let drawType = undefined            // 绘制类型
    let drawInteraction = undefined     // 绘制对象
    let snapInteraction = undefined     // 捕捉对象
    const parentEle = document.querySelector(".main-ul")
    parentEle.addEventListener("click"evt => {
        const type = evt.target.dataset.type
        toogleActiveClass(evt.target".main-ul")
        if (type === "none") {
            removeAllActiveClass(".main-ul"".active")
            drawType = undefined
            if (drawInteraction) {
                map.removeInteraction(drawInteraction)
                drawInteraction = undefined
            }
            if (snapInteraction) {
                map.removeInteraction(snapInteraction)
                snapInteraction = undefined
            }
            removeLayerByName("tempLayer", map)
            return
        } else {
            addInteraction(type)
        }
    })

    // 添加交互控件
    function addInteraction(type) {
        // 添加绘制图层
        drawLayer = new ol.layer.Vector({
            sourcenew ol.source.Vector(),
            style: drawStyle
        })
        drawLayer.set("layerName""tempLayer")
        map.addLayer(drawLayer)

        switch (type) {
            case "line":
                drawType = "LineString"
                break
            case "polygon":
                drawType = "Polygon"
                break
        }
        // 节点捕捉控件
        snapInteraction = new ol.interaction.Snap({
            source: regionLayer.getSource()
        })
        // 绘制交互控件
        drawInteraction = new ol.interaction.Draw({
            source: drawLayer.getSource(),
            style: drawStyle,
            type: drawType,
            tracetrue,  // 开启节点捕捉
            traceSource: regionLayer.getSource() // 节点捕捉数据源
        })
        map.addInteraction(drawInteraction)
        map.addInteraction(snapInteraction)
    }
</script>