2 OpenLayers学习笔记-加载GeoJSON

906 阅读7分钟
需求

用户点击行政区划,加载对应区域的GeoJSON文件在地图上显示。鼠标悬浮到GeoJSON内部区域的时候,高亮显示鼠标悬浮的区域。

实现过程
  • 默认显示地图,点击行政区划使用new ol.source.Vector加载GeoJSON文件,然后通过new ol.layer.Vector创建图层,最后使用map.addLayerGeoJSON的图层添加到地图上。其中,加载GeoJSON文件可使用openlayers内置的函数,也可以自己通过fetchxhr请求GeoJSON数据,再作为参数传递给ol.format.GeoJSON().readFeatures()

  • 鼠标悬浮高亮的效果有两种实现方式:

    • 通过map.on('pointermove', () => {}给地图添加鼠标移动事件,在事件处理函数中,根据鼠标的经纬度信息,通过map.forEachFeatureAtPixel(e.pixel, () => {})获取feature,调用feature.setStyle()feature进行样式修改。在修改feature的同时,用变量存储feature作为上一个修改的feature。调用feature.setStyle()feature时,如果上一个feaure存在,将其恢复到默认的样式,进而达到鼠标进去下一个区域时,上一个区域恢复原貌的效果。

    • 通过ol.interaction.Select实现,可以通过condition设置触发方式(点击、鼠标悬浮、Alt+点击)。通过select.on('select', (event) => {})处理鼠标悬浮的逻辑,使用map.addInteraction(select)添加到地图上。这种方式的好处是不需要自己处理鼠标离开区域的逻辑,可通过event.selected获取到鼠标悬浮的feature,通过event.deselected获取到鼠标离开的feature

知识点
  • ol.events.condition.pointerMove鼠标移动。

  • ol.events.condition.altclick鼠标左键+Alt

  • ol.events.condition.click鼠标点击

  • ol.events.condition.singleclick也是点击,区别是click鼠标单击地图触发的事件(鼠标单击多少次地图,该事件就触发多少次)。singleclick鼠标单击地图触发的事件(鼠标若在250ms内连续单击地图,则不会触发该事件)。

  • ol.style.Style样式,有geometry、fill、image、renderer、stroke配置等。

  • ol.source.Vector主要用于为矢量图层ol.layer.Vector类提供具体的数据来源。它可以用于直接组织或读取矢量数据(如点、线、面等常用的地图元素,即Feature)。用此方法加载GeoJSON有两种方式,(1)指定url和格式,ol.source.Vector自己去加载。(2)xhrfetch请求到GeoJSON数据,使用ol.format.GeoJSON().readFeatures读取GeoJSON,再将数据传给ol.layer.Vector

    • const vectorLayerGeo = new ol.layer.Vector({
          source: new ol.source.Vector({
              url: `./420000.json`,  
              format: new ol.format.GeoJSON()
          }),
          style: defaultStyle
      });
      
    • const sourceJsonData = new ol.source.Vector({
          features: new ol.format.GeoJSON().readFeatures(geojsonData, { featureProjection: 'EPSG:3857' })
      });
      const vectorLayerGeo = new ol.layer.Vector({
          source: sourceJsonData,
          style: defaultStyle
      });
      
  • source.on('featuresloadend', ()=> {},加载GeoJSON时,可通过featuresloadend事件监测加载的状态。加载完成后,能够获取GeoJSON中的center字段,根据center的经纬度,调整地图中心点到GeoJSON的中心。

  • map.getView().animate({center: ol.proj.fromLonLat(center)})可以调整地图的中心点到某个经纬度。可以通过duration设置animate的时长,设置为0表示没有动画效果。

  • ol.proj.fromLonLat用于将经纬度坐标转换为当前地图投影的坐标。它将经度和纬度值作为参数,并返回转换后的坐标数组。

  • map.un('click', func)解绑通过map.on()绑定的事件。

  • map.removeInteraction()删除地图上添加的交互。

  • GeoJSON文件可通过高德的开放平台获取 高德开放平台获取GeoJSON

代码HTML+CSS+JS
  • 由于要使用xhr或者fetch加载geojson文件,所以需要以服务的方式访问html文件,电脑安装了node的情况下,推荐安装anywhere(npm i anywhere -g),在任意文件夹内命令行执行anywhere 端口即可以服务的形式访问这个文件夹。或者使用vscode等编译器自带的服务。

    • 特别注意:监听geojson内部的鼠标事件时,一定要给Style设置fill属性设置,否则监听的不是整体的geojson平面区域,仅仅边线。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>openlayers加载GeoJson</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/ol.min.css" integrity="sha512-bc9nJM5uKHN+wK7rtqMnzlGicwJBWR11SIDFJlYBe5fVOwjHGtXX8KMyYZ4sMgSL0CoUjo4GYgIBucOtqX/RUQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <style type="text/css">
    * {
        padding: 0;
        margin: 0;
        box-sizing: border-box;
    }

    body,
    html {
        width: 100%;
        height: 100%;
        margin: 0;
        font-family: "Microsoft YaHei"
    }

    #map {
        width: 100vw;
        height: 100vh;
    }
    .orgs {
        display: grid;
        grid-template-columns: repeat(2, 50%);
        grid-template-rows: repeat(2, 50%);
    }
    #info {
        position: fixed;
        right: 10px;
        top: 10px;
        background-color: #fff;
        box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
        width: 350px;
        padding: 10px;
        text-align: center;
        border-radius: 5px;
        padding-bottom: 20px;
    }
    .orgs button {
        font-size: 18px;
        border: none;
        padding: 12px 20px;
        border-radius: 4px;
        color: #fff;
        background-color: #409eff;
        border-color: #409eff;
        cursor: pointer;
        border: 1px solid #dcdfe6;
    }
    .orgs button:hover {
        background-color: rgba(7, 193, 96, 0.8);
    }

    .orgs button.active {
        background-color: #07c160;
    }
    .hoverMethod,
    .loadMethod {
        margin-bottom: 10px;
        display: flex;
    }
    .hoverMethod div,
    .loadMethod div {
        text-align: center;
        width: 150px;
        cursor: pointer;
        height: 45px;
        line-height: 45px;
        border-radius: 5px;
    }
    .hoverMethod .active,
    .loadMethod .active {
        background-color: rgba(0, 255, 168, 0.7);
    }
    [v-cloak] {
        display: none;
    }
    </style>
</head>

<body>
    <div id="app" v-cloak>
        <div id="map"></div>
        <div id="info">
            <div class="loadMethod">
                <div :class="{active: loadType === 1}" @click='loadType = 1' title="使用openlayers提供的ol.source.Vector加载GeoJSON文件">
                    加载JSON方式一
                </div>
                <div :class="{active: loadType === 2}" @click='loadType = 2' title="fetch加载geojson文件">
                    加载JSON方式二
                </div>
            </div>
            <div class="hoverMethod">
                <div :class="{active: hoverType === 1}" @click='changeColorBySelect' title="使用 ol.interaction.Select,实现鼠标悬浮区域变色">
                    鼠标悬浮方式一
                </div>
                <div :class="{active: hoverType === 2}" @click='changeColorByPointerMove' title="通过鼠标移动事件,实现鼠标悬浮区域变色">
                    鼠标悬浮方式二
                </div>
            </div>
            <div class="orgs">
                <button v-for='area in areaData' :key='area.code' type='button' @click='handleClickProvience(area.code)' :class='{active: currentCode == area.code}'>{{area.name}}</button>
            </div>
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/openlayers/8.2.0/dist/ol.min.js" integrity="sha512-+nvfloZUX7awRy1yslYBsicmHKh/qFW5w79+AiGiNcbewg0nBy7AS4G3+aK/Rm+eGPOKlO3tLuVphMxFXeKeOQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.14/vue.global.prod.min.js" integrity="sha512-huEQFMCpBzGkSDSPVAeQFMfvWuQJWs09DslYxQ1xHeaCGQlBiky9KKZuXX7zfb0ytmgvfpTIKKAmlCZT94TAlQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script>
    const { createApp } = Vue;
    // 悬浮样式
    const hoverStyle = new ol.style.Style({
        fill: new ol.style.Fill({
            color: 'rgba(7, 193, 96, 0.5)'
        }),
        stroke: new ol.style.Stroke({
            color: '#07c160',
            width: 2
        })
    });
    // 默认样式
    const defaultStyle = new ol.style.Style({
        fill: new ol.style.Fill({
            color: 'rgba(255, 255, 255, 0.8)'
        }),
        stroke: new ol.style.Stroke({
            color: '#3399CC',
            width: 2
        })
    });
    // 错误的默认样式,使用这个默认样式鼠标事件只在geojson的边线上触发
    const errorDefaultStyle =  new ol.style.Style({
        // fill: new ol.style.Fill({
        //     color: 'rgba(255, 255, 255, 0.4)'
        // }),
        stroke: new ol.style.Stroke({
            color: '#3399CC',
            width: 2
        })
    });
    const vm = createApp({
        data() {
            return {
                map: {}, // 地图实例
                areaData: [{
                    code: '420000_full',
                    name: '湖北省'
                }, {
                    code: '430000_full',
                    name: '湖南省'
                }, {
                    code: '130000_full',
                    name: '河北省'
                } ,{
                    code: '410000_full',
                    name: '河南省'
                }],
                currentCode: '', // 当前选中的省份
                loadType: 1, // 加载JSON数据的方式
                hoverType: 2, // 鼠标悬浮处理方式
                prevLayer: null, // 上一个加载的省份图层
                select: null // select交互实例
            }
        },
        methods: {
            // 初始化地图
            initMap() {
                // 高德地图瓦片地址
                const amapVectorLayer = new ol.layer.Tile({
                    source: new ol.source.XYZ({
                        url: 'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
                    })
                });
                this.map = new ol.Map({
                    target: 'map',
                    layers: [amapVectorLayer],
                    view: new ol.View({
                        center: ol.proj.fromLonLat([105.481992, 36.777646]),
                        zoom: 5
                    })
                });
                // 绑定地图点击事件,打印经纬度
                this.map.on('click', (e) => {
                    const point = ol.proj.transform(e.coordinate, 'EPSG:3857', 'EPSG:4326')
                    console.log(`经度:${point[0].toFixed(6)} 纬度:${point[1].toFixed(6)}`);
                });
            },
            // 点击省份加载json文件
            handleClickProvience (code) {
                this.currentCode = code;
                // 如果上一个json图层存在,先将其删除
                this.prevLayer && this.map.removeLayer(this.prevLayer);
                if (this.loadType ===  1) {
                    // 指定json的url和格式,ol自己去加载
                    console.log('指定json的url和格式,ol自己去加载');
                    this.olLoadJson();
                } else {
                    // 请求json数据,ol读取
                    console.log('请求json数据,ol读取');
                    this.featchLoadJson();
                }
            },
            // ol自己去加载json文件
            olLoadJson () {
                // 加载资源
                const source =  new ol.source.Vector({
                    url: `./json/${this.currentCode}.json`,
                    format: new ol.format.GeoJSON()
                });
                // 监听加载geojson文件进度
                source.on('featuresloadend', (event) => {
                    // 获取第一个featrue的中心并跳转
                    this.goToCenterByLonLat(this.prevLayer);
                });
                // 创建图层,获取geojson文件,根目录下放一个json文件夹存放geojson文件
                this.prevLayer = new ol.layer.Vector({
                    source: source,
                    style: defaultStyle
                });
                // 将图层添加到地图中  
                this.map.addLayer(this.prevLayer);
            },
            // featch加载json
            async featchLoadJson ()  {
                // 请求数据 根目录下放一个json文件夹存放geojson文件
                const response = await fetch(`./json/${this.currentCode}.json`);
                const json = await response.json();
                // 读取json
                const sourceJsonData = new ol.source.Vector({
                    features: new ol.format.GeoJSON().readFeatures(json, { featureProjection: 'EPSG:3857' })
                });
                // 创建图层
                this.prevLayer = new ol.layer.Vector({
                    source: sourceJsonData,
                    style: defaultStyle
                });
                // 获取第一个featrue的中心并跳转
                this.goToCenterByLonLat(this.prevLayer);
                // 将图层添加到地图中  
                this.map.addLayer(this.prevLayer);
            },
            // 使用 ol.interaction.Select,实现鼠标悬浮区域变色
            changeColorBySelect () {
                this.hoverType = 1;
                // 使用 ol.interaction.Select时,将鼠标移动事件解绑
                this.map.un('pointermove', this.pointermoveFunc);
                // 添加交互
                this.select = new ol.interaction.Select({
                    condition: ol.events.condition.pointerMove
                });
                // 处理悬浮更改区域颜色
                this.select.on('select', function(event) {
                    console.log('使用 ol.interaction.Select,实现鼠标悬浮区域变色');
                    if (event.selected.length > 0) {
                        // 鼠标悬浮时应用悬浮样式
                        event.selected[0].setStyle(hoverStyle);
                    }
                    if (event.deselected.length > 0) {
                        // 鼠标离开时恢复原始样式
                        event.deselected[0].setStyle(null);
                    }
                });
                // 添加到地图
                this.map.addInteraction(this.select);
            },
            // 通过鼠标移动事件,实现鼠标悬浮区域变色
            changeColorByPointerMove () {
                this.hoverType = 2;
                // 使用鼠标移动事件的时候,将select交互删除
                this.select && this.map.removeInteraction(this.select);
                this.map.on('pointermove', this.pointermoveFunc);
            },
            // 单独把鼠标移动的事件提出来,便于解绑使用
            pointermoveFunc (e) {
                console.log('通过鼠标移动事件,实现鼠标悬浮区域变色');
                 // 根据经纬度获取feature
                const feature = this.map.forEachFeatureAtPixel(e.pixel, function(feature) {
                    return feature;
                });
                // 获取不到不处理
                if (!feature) {
                    // 如果此时prevFeature存在,说明鼠标移出了geojson区域,将prevFeature样式恢复
                    this.prevFeature && this.prevFeature.setStyle(defaultStyle);
                    this.prevFeature = null;
                    return;
                }
                // 如果上一个feature和获取到的feature是同一个feature,不处理,防止移动鼠标频繁设置样式
                if (this.prevFeature && this.prevFeature.ol_uid === feature.ol_uid) {
                    return;
                }
                // 上一个feature存在,将其样式恢复默认
                this.prevFeature && this.prevFeature.setStyle(defaultStyle);
                // 改变feature填充颜色
                feature.setStyle(hoverStyle);
                // 存储当前高亮显示的feature
                this.prevFeature = feature;
            },
            // 获取图层的第一个包含center的feature,根据这个center坐标进行跳转
            goToCenterByLonLat (vectorLayerGeo) {
                const features = vectorLayerGeo.getSource().getFeatures();
                for (let i = 0; i < features.length; i++) {
                    const feature = features[i];
                    const center = feature.get('center'); // 如果存在center字段的话  
                    if (center) {
                        this.map.getView().animate({  
                            center: ol.proj.fromLonLat(center),
                            duration: 500
                        });  
                        break; // 如果找到一个center字段,就退出循环  
                    }
                }
            }
        },
        mounted() {
            // 初始化地图
            this.initMap();
            // 默认是使用 ol.interaction.Select,实现鼠标悬浮区域变色
            this.changeColorByPointerMove();
        }
    }).mount('#app');
    </script>
</body>

</html>

参看文章

OpenLayers v8.2.0 API - Class: VectorSource文档

OpenLayers v8.2.0 API - Class: Style文档

ol.interaction.Select官方栗子

高德开放平台获取GeoJSON

anywhere介绍