MapBox实现框选查询,多边形范围查询

news2024/11/15 5:10:19

还是老规矩先来看效果:

 mapbox官方没有为我们提供框选查询的案例,所以这个功能需要我们自己写。在openlayers框架中是有一个矩形范围查询的例子,但是在maobox没有。

那么我们就来说一下如何来做这个效果吧,首先这个效果可以分为两个部分,第一个部分是绘制图形,第二部分是利用绘制的图形去查询指定的图层数据。

绘制图形在mapbox中也没有固定的案例,因此也需要我们自己写。

绘制一个图形的思路其实是通过一个关键的函数setData()这个函数的作用是为图层源source设置数据。在mapbox中我们通常可以这样操作。先建立一个空的source和layer,然后后续再为这个图层指定数据。就像下面这样的写法:

map.addSource("selected-source", {
        type: "geojson",
        data: {
          type: "FeatureCollection",
          features: [],
        },
      });
      map.addLayer({
        id: "selected-layer",
        type: "circle",
        source: "selected-source",
        paint: {
          "circle-radius": 8,
          "circle-color": color,
        },
      });
      map.getSource("selected-source").setData(features);

因此顺着这个思路我们可以先建立图层,后更新数据。如何来更新数据?我们可以监听地图的“onmousemove”事件,初始状态我们可以鼠标点击确定多边形的起点,然后鼠标移动的时候,我们让鼠标当前的位置和第一次点下的位置进行连线,这样就画成了一条线,顺着这个思路我们就可以不断的使用点击和鼠标移动的方式来绘制多边形。当然如果你是矩形或者圆形 的话思路是一样的,只不过画线的数学计算不一样,例如圆形的第一次点击是确定圆心的位置,鼠标的移动是为了确定圆的半径,第二次鼠标落下圆就画好了。

绘制多边形的核心代码如下:

function boxSelect(targetLayer) {
      return new Promise((resolve, reject) => {
        var Selected = true;
        map.doubleClickZoom.disable();
        map.getCanvas().style.cursor = "default";
        clearSelect();
        var jsonPoint = {
          type: "FeatureCollection",
          features: [],
        };
        var jsonLine = {
          type: "FeatureCollection",
          features: [],
        };
        var points = [];
        var ele = document.createElement("div");
        ele.setAttribute("class", "measure-result");
        const option = {
          element: ele,
          anchor: "left",
          offset: [8, 0],
        };
        var markers = [];
        var tooltip = new mapboxgl.Marker(option).setLngLat([0, 0]).addTo(map);
        markers.push(tooltip);
        var source = map.getSource("points-area");
        if (source) {
          map.getSource("points-area").setData(jsonPoint);
          map.getSource("line-area").setData(jsonLine);
        } else {
          map.addSource("points-area", {
            type: "geojson",
            data: jsonPoint,
          });
          map.addSource("line-area", {
            type: "geojson",
            data: jsonLine,
          });
          map.addLayer({
            id: "line-area",
            type: "fill",
            source: "line-area",
            paint: {
              "fill-color": "#006aff",
              "fill-opacity": 0.1,
            },
          });
          map.addLayer({
            id: "line-area-stroke",
            type: "line",
            source: "line-area",
            paint: {
              "line-color": "#006aff",
              "line-width": 2,
              "line-opacity": 0.65,
            },
          });
          map.addLayer({
            id: "points-area",
            type: "circle",
            source: "points-area",
            paint: {
              "circle-color": "#006aff",
              "circle-radius": 6,
            },
          });
        }

        function addPoint(coords) {
          jsonPoint.features.push({
            type: "Feature",
            geometry: {
              type: "Point",
              coordinates: coords,
            },
          });
          map.getSource("points-area").setData(jsonPoint);
        }

        map.on("click", function (_e) {
          if (Selected) {
            var coords = [_e.lngLat.lng, _e.lngLat.lat];
            points.push(coords);
            addPoint(coords);
          }
        });
        var boxResult = {};
        map.on("dblclick", function (_e) {
          if (Selected) {
            var coords = [_e.lngLat.lng, _e.lngLat.lat];
            points.push(coords);
            Selected = false;
            markers.forEach((f) => {
              f.remove();
            });
            boxResult.boxGeometry = map.getSource("line-area")["_data"];
            boxResult.area = turf.area(boxResult.boxGeometry);
            boxResult.selectedFeatures = execSelect(
              boxResult.boxGeometry,
              targetLayer
            );
            hightLightFeature(boxResult.selectedFeatures);
            resolve(boxResult);
          }
        });

        map.on("mousemove", function (_e) {
          if (Selected) {
            var coords = [_e.lngLat.lng, _e.lngLat.lat];
            var len = jsonPoint.features.length;
            if (len === 0) {
              ele.innerHTML = "点击绘制多边形";
            } else if (len === 1) {
              ele.innerHTML = "点击继续绘制";
            } else {
              var pts = points.concat([coords]);
              pts = pts.concat([points[0]]);
              var json = {
                type: "Feature",
                geometry: {
                  type: "Polygon",
                  coordinates: [pts],
                },
              };
              map.getSource("line-area").setData(json);
            }
            tooltip.setLngLat(coords);
          }
        });
      });
    }


画完多边形之后,就要进入查询的阶段了,查询的方式有很多,第一种你可以把画完的多边形的经纬度边界范围提交到服务端(自己的数据服务或者geoserver)进行查询,然后把结果返回到前端,第二种方式你可以采用turf.js直接在前端做查询,其实mapbox官方虽然没有提供范围查询的示例但是却提供了范围查询的api:

 

 只需要传入一个边界范围就可以进行查询。不过这个api是有两个坑的地方。

第一是这个api你传入的边界范围坐标必须是canvas坐标。经纬度是不行的。因此在传递范围的时候要这样写:

  var extent = turf.bbox(polygon);
      const features = map.queryRenderedFeatures(
        [
          map.project([extent[0], extent[1]]),
          map.project([extent[2], extent[3]]),
        ],
        { layers: [targetLayer] }
      );

第二个比较坑的地方是按照这个接口查询出来的结果并不准确,因为他选择的是外部边界的范围。就像下图的情况是很容易发生的:

 可以看到五边形之外的点也能够被选中高亮,因为这个接口他是按照多变形的外接矩形(图中橙色虚线)来选择的。外接矩形范围内的点都能够被选中。所以为了准确的计算结果,我们最好是接入turf.js的一个函数,叫做:pointsWithinPolygon。这个函数能够准确的获取多边形内部的点。所以我们可以采用这个函数来查询,也可以采用mapbox 的接口第一次检索一批结果。然后再使用这个函数进行过滤。看个人设计。

最后为大家附上框选范围查询的源码,大家只需要配置自己的mapbox的token就可以直接使用:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>框选查询</title>
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.js"></script>
    <script src="./turf.min.js"></script>
    <script src="./token.js"></script>
    <script src="./poi.js"></script>
    <style>
      body {
        padding: 0;
        margin: 0;
      }
      .mapboxgl-ctrl-bottom-left {
        display: none !important;
      }
      #map {
        width: 100%;
        height: 100vh;
        top: 0;
        left: 0;
        position: absolute;
        z-index: 2;
      }
      #result {
        width: 500px;
        height: 450px;
        position: absolute;
        left: 20px;
        bottom: 30px;
        z-index: 10;
        background: rgba(255, 255, 255, 0.8);
        overflow: auto;
      }
      .btns {
        position: absolute;
        top: 50px;
        left: 50px;
        z-index: 10;
      }
      .measure-result {
        background-color: white;
        border-radius: 3px;
        height: 16px;
        line-height: 16px;
        padding: 0 3px;
        font-size: 12px;
        box-shadow: 0 0 0 1px #ccc;
        &.close {
          cursor: pointer;
          width: 20px;
          height: 20px;
          display: flex;
          align-items: center;
          justify-content: center;
        }
      }
    </style>
  </head>

  <body>
    <div id="map"></div>
    <div id="result"></div>
    <div class="btns">
      <button onclick="test()">开始框选</button
      ><button onclick="clearSelect()">清除结果</button>
    </div>
  </body>
  <script>
    mapboxgl.accessToken = token;
    const map = new mapboxgl.Map({
      center: [120.34233, 30.324342],
      attributionControl: false,
      style: "mapbox://styles/mapbox/satellite-v9",
      container: "map",
      zoom: 3,
    });
    map.on("load", (e) => {
      map.addSource("poi", { type: "geojson", data: poi });
      map.addLayer({
        id: "poi-layer",
        source: "poi",
        type: "circle",
        paint: { "circle-radius": 5, "circle-color": "yellow" },
      });
    });
    async function test() {
      let re = await boxSelect("poi-layer");
      document.getElementById("result").innerText = JSON.stringify(re, 2, null);
    }
    function boxSelect(targetLayer) {
      return new Promise((resolve, reject) => {
        var Selected = true;
        map.doubleClickZoom.disable();
        map.getCanvas().style.cursor = "default";
        clearSelect();
        var jsonPoint = {
          type: "FeatureCollection",
          features: [],
        };
        var jsonLine = {
          type: "FeatureCollection",
          features: [],
        };
        var points = [];
        var ele = document.createElement("div");
        ele.setAttribute("class", "measure-result");
        const option = {
          element: ele,
          anchor: "left",
          offset: [8, 0],
        };
        var markers = [];
        var tooltip = new mapboxgl.Marker(option).setLngLat([0, 0]).addTo(map);
        markers.push(tooltip);
        var source = map.getSource("points-area");
        if (source) {
          map.getSource("points-area").setData(jsonPoint);
          map.getSource("line-area").setData(jsonLine);
        } else {
          map.addSource("points-area", {
            type: "geojson",
            data: jsonPoint,
          });
          map.addSource("line-area", {
            type: "geojson",
            data: jsonLine,
          });
          map.addLayer({
            id: "line-area",
            type: "fill",
            source: "line-area",
            paint: {
              "fill-color": "#006aff",
              "fill-opacity": 0.1,
            },
          });
          map.addLayer({
            id: "line-area-stroke",
            type: "line",
            source: "line-area",
            paint: {
              "line-color": "#006aff",
              "line-width": 2,
              "line-opacity": 0.65,
            },
          });
          map.addLayer({
            id: "points-area",
            type: "circle",
            source: "points-area",
            paint: {
              "circle-color": "#006aff",
              "circle-radius": 6,
            },
          });
        }

        function addPoint(coords) {
          jsonPoint.features.push({
            type: "Feature",
            geometry: {
              type: "Point",
              coordinates: coords,
            },
          });
          map.getSource("points-area").setData(jsonPoint);
        }

        map.on("click", function (_e) {
          if (Selected) {
            var coords = [_e.lngLat.lng, _e.lngLat.lat];
            points.push(coords);
            addPoint(coords);
          }
        });
        var boxResult = {};
        map.on("dblclick", function (_e) {
          if (Selected) {
            var coords = [_e.lngLat.lng, _e.lngLat.lat];
            points.push(coords);
            Selected = false;
            markers.forEach((f) => {
              f.remove();
            });
            boxResult.boxGeometry = map.getSource("line-area")["_data"];
            boxResult.area = turf.area(boxResult.boxGeometry);
            boxResult.selectedFeatures = execSelect(
              boxResult.boxGeometry,
              targetLayer
            );
            hightLightFeature(boxResult.selectedFeatures);
            resolve(boxResult);
          }
        });

        map.on("mousemove", function (_e) {
          if (Selected) {
            var coords = [_e.lngLat.lng, _e.lngLat.lat];
            var len = jsonPoint.features.length;
            if (len === 0) {
              ele.innerHTML = "点击绘制多边形";
            } else if (len === 1) {
              ele.innerHTML = "点击继续绘制";
            } else {
              var pts = points.concat([coords]);
              pts = pts.concat([points[0]]);
              var json = {
                type: "Feature",
                geometry: {
                  type: "Polygon",
                  coordinates: [pts],
                },
              };
              map.getSource("line-area").setData(json);
            }
            tooltip.setLngLat(coords);
          }
        });
      });
    }
    function hightLightFeature(features, color = "#06ceff") {
      map.addSource("selected-source", {
        type: "geojson",
        data: {
          type: "FeatureCollection",
          features: [],
        },
      });
      map.addLayer({
        id: "selected-layer",
        type: "circle",
        source: "selected-source",
        paint: {
          "circle-radius": 8,
          "circle-color": color,
        },
      });
      map.getSource("selected-source").setData(features);
    }
    function execSelect(polygon, targetLayer) {
      var extent = turf.bbox(polygon);
      const features = map.queryRenderedFeatures(
        [
          map.project([extent[0], extent[1]]),
          map.project([extent[2], extent[3]]),
        ],
        { layers: [targetLayer] }
      );
      //优化一下,因为有的点不在多边形内,但是还是被选上了;
      const points = features.map((f) => f.geometry.coordinates);
      let withinFeature = turf.pointsWithinPolygon(
        turf.points(points),
        polygon
      );
      return withinFeature;
    }
    function clearSelect() {
      const dom = document.getElementsByClassName("measure-result");
      if (dom.length > 0) {
        for (let index = dom.length - 1; index > -1; index--) {
          dom[index].parentNode.removeChild(dom[index]);
        }
      }
      var source = map.getSource("points");
      var json = {
        type: "FeatureCollection",
        features: [],
      };
      if (source) {
        map.getSource("points").setData(json);
        map.getSource("line-move").setData(json);
        map.getSource("line").setData(json);
      }
      var sourceArea = map.getSource("points-area");
      if (sourceArea) {
        map.getSource("points-area").setData(json);
        map.getSource("line-area").setData(json);
      }
      if (map.getLayer("selected-layer")) {
        map.removeLayer("selected-layer");
        map.removeSource("selected-source");
      }
    }
  </script>
</html>

测试数据:

var poi = {
  type: "FeatureCollection",
  features: [
    {
      type: "Feature",
      properties: { type: "school", name: "susu学校" },
      geometry: {
        type: "Point",
        coordinates: [125.384449, 46.578032],
      },
    },
    {
      type: "Feature",
      properties: { type: "school", name: "混过你学校" },
      geometry: {
        type: "Point",
        coordinates: [122.58806, 50.438486],
      },
    },
    {
      type: "Feature",
      properties: { type: "hospital", name: "司机医院" },
      geometry: {
        type: "Point",
        coordinates: [117.583995, 44.877376],
      },
    },
    {
      type: "Feature",
      properties: { type: "school", name: "损害学校" },
      geometry: {
        type: "Point",
        coordinates: [125.384449, 46.578032],
      },
    },
    {
      type: "Feature",
      properties: { type: "hospital", name: "同样是医院" },
      geometry: {
        type: "Point",
        coordinates: [116.112212, 41.430537],
      },
    },
    {
      type: "Feature",
      properties: { type: "hospital", name: "输液医院" },
      geometry: {
        type: "Point",
        coordinates: [108.311758, 40.538283],
      },
    },
    {
      type: "Feature",
      properties: { type: "school", name: "USB学校" },
      geometry: {
        type: "Point",
        coordinates: [116.700925, 31.783546],
      },
    },
    {
      type: "Feature",
      properties: { type: "market", name: "哈哈超市" },
      geometry: {
        type: "Point",
        coordinates: [97.935682, 37.554957],
      },
    },
    {
      type: "Feature",
      properties: { type: "market", name: "低估超市" },
      geometry: {
        type: "Point",
        coordinates: [86.014234, 44.193101],
      },
    },
    {
      type: "Feature",
      properties: { type: "school", name: "嘎哈学校" },
      geometry: {
        type: "Point",
        coordinates: [85.351931, 32.783785],
      },
    },
    {
      type: "Feature",
      properties: { type: "hospital", name: "六医院" },
      geometry: {
        type: "Point",
        coordinates: [105.44178, 28.00127],
      },
    },
    {
      type: "Feature",
      properties: { type: "market", name: "超市不过" },
      geometry: {
        type: "Point",
        coordinates: [115.008374, 23.944745],
      },
    },
    {
      type: "Feature",
      properties: { type: "hospital", name: "玉兔医院" },
      geometry: {
        type: "Point",
        coordinates: [111.77045, 33.341497],
      },
    },
  ],
};


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/638329.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分享】2000-2021年全国1km分辨率的逐月PM2.5栅格数据(免费获取)

PM2.5作为最主要的空气质量指标&#xff0c;在我们日常研究中非常常用&#xff01;之前我们分享了2000-2021年全国范围1km分辨率的逐日的PM2.5栅格数据&#xff08;可查看之前的文章获悉详情&#xff09;&#xff0c;数据来自于Zendo平台&#xff01; 我们发现在Zendo平台上还…

RFID系统:解析无线识别技术的未来

RFID&#xff08;Radio Frequency Identification&#xff09;系统是一种基于无线识别技术的自动识别和数据采集系统。尽管对于新手来说&#xff0c;RFID可能是一个陌生的术语&#xff0c;但它正在快速地渗透和影响着我们的日常生活。 在RFID系统中&#xff0c;一个标签&…

与react的初定情素

前要&#xff1a; 其实吧&#xff01;之前的博客基本没有写过相关react的笔记&#xff0c;因为我根本没有写过react的相关项目&#xff0c;作为一个小水前端&#xff0c;没有写过react的项目会让别人笑大大牙的&#xff0c;所以趁着划水时间好好学习&#xff0c;天天向上&#…

Java程序员面试1000问,让你开挂的面试宝典,花点耐心看完offer拿到手软

前言: 本文收集整理了各大厂常见面试题N道&#xff0c;你想要的这里都有内容涵盖&#xff1a;Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spring Boot、Spring Cloud、RabbitMQ、Kafka、Linux 等技术栈&#xff0c;希望大家都能找到适…

[nlp] OPT与GPT-2的差异

Meta AI 开源1750亿参数大模型- OPT,FlagAI一键调用! - 知乎简介Meta AI 开源 OPT系列模型,其中最大模型1750 亿参数(需申请访问权限)媲美 GPT-3。OPT系列模型包括了多组不同参数规模的模型权重,如图: OPT开源了一系列大模型,但是实际调用这些模型有很高的技术门槛。为…

如何变成领域里大牛级的人物?

今天看到一段话&#xff0c;挺有道理的&#xff0c;于是摘录下来&#xff1a; 对一个领域不太了解&#xff0c;而该领域又重要&#xff0c;你可以这样做: 1.读领域中经典教材和入门书 2.实践&#xff0c;观察、反思&#xff0c;找到核心的几个关键问题 3.对关键问题深入探索&am…

Rust in Action笔记 第四章生命周期、所有权、借用

第四章用了一个行星通信的例子来阐述整个主题&#xff0c;主要角色有地面站&#xff08;Ground station&#xff09;、人造卫星&#xff08;CubeSat&#xff09;&#xff0c;两者有不同的状态并且能互相发消息通信&#xff1b; Rust有类型安全&#xff08;type safety&#xf…

位姿估计 | 空间目标位姿估计方法分类总结

目录 前言位姿估计方法分类一、传统位姿估计方法1. 基于特征的位姿估计2. 基于模型的位姿估计 二、深度学习位姿估计方法 总结 前言 本文接着分享空间目标位姿跟踪和滤波算法中用到的一些常用内容&#xff0c;希望为后来者减少一些基础性内容的工作时间。以往分享总结见文章&a…

KW 新闻 | KaiwuDB 受邀亮相 IOTE 2023 第十九届国际物联网展

5月17日&#xff0c;IOTE 2023 第十九届国际物联网展在上海拉开序幕&#xff0c;全球超过 350 家参展企业到场展示先进的物联网技术和产品&#xff0c;行业专家、领军企业代表等人物齐聚一堂&#xff0c;共话 IoT 未来趋势。KaiwuDB 受邀亮相参展并就《工业物联网产业数字化转型…

剑指 Offer 30: 包含min函数的栈

这里用到了java中的Stack,和别的方法一样&#xff08;LinkedList&#xff09;&#xff0c;也是先使用Stack带上类型&#xff08;这里是<Integer>&#xff09;声明&#xff0c;然后再new出来。 这里需要的返回值是int&#xff01;&#xff01;&#xff01; 这里千万要注意…

SQLServer2017安装教程

一、安装包下载地址&#xff1a; https://download.csdn.net/download/u011152234/87898714 二、软件安装 1、双击“SQLEXPR_x64_CHS.exe”将文件提取在指定文件中。 2、 右击“SETUP.EXE”以“管理员身份运行” 3、选择全新安装模式继续安装 4、在协议中&#xff0c;点击同…

入驻京东MCN机构详细流程

京东MCN机构可以为京东平台提供更专业的运营和管理服务&#xff0c;通过对UP主的培训和管理&#xff0c;提高UP主的创作水平和内容品质&#xff0c;从而提高平台的服务质量和用户体验&#xff0c;京东MCN机构可以通过与京东平台的合作&#xff0c;优化平台生态&#xff0c;提高…

Java RabbitMQ API 对接说明

1.背景 最近研发的物联网底层框架使用了RabbitMQ作为消息队列&#xff0c;如果监控消息队列对其通道是否出现阻塞能够及时获知与让管理员收到预警并及时处理&#xff0c;这里我们会采用RabbitMQ的rabbitmq_management插件。利用其提供的API进行获取信息&#xff0c;从而实现队…

【Python实战】Python采集王者最低战力信息

前言 王者新赛季马上就要开始了&#xff0c;大家都开始冲榜了&#xff0c;准备拿一个小省标&#xff0c;那么&#xff0c;本文&#xff0c;就来练习获取各地最低战力的爬虫采集实战。 环境使用 python 3.9pycharm 模块使用 requests 模块介绍 requests requests是一个很实用…

都说软件测试简单,我怎么没有感觉呢?

前言 软件测试到底难不难&#xff1f;试试就知道了。 经常听想入行的朋友说&#xff1a; 我想转软件测试&#xff0c;听说软件测试很简单。 这时我的心里就呵呵了&#xff0c;谁说软件测试简单了&#xff0c;你知道吗&#xff1f;测试工程师要会的&#xff0c;要了解的东西比…

电力vr智能巡检模拟实操教学灵活性高成本低

传统电力智能运检服务培训采用交接班期间开展智能带电检测仪器的操作培训&#xff0c;教学时间、场地及材料有限&#xff0c;有了VR技术&#xff0c;将推动电力智能运检服务培训走向高科技、高效率和智能化水平。 深圳华锐视点凭借着对VR实训系统的深入研发和升级&#xff0c;多…

asp.net 框架开发的LIMS实验室信息管理系统源码

LIMS实验室信息管理系统源码 通过互联网实验室的管理模式&#xff0c;结合实验室硬件设备&#xff0c;将实验室的项目、设备、耗材、人员等结合到软件上管理&#xff0c;利用计算机网络技术、数据存储技术 、快速数据处理等&#xff0c;对实验室进行全方位的管理&#xff0c;帮…

web接口测试之GET与POST请求

目录 前言&#xff1a; GET请求 POST请求 前言&#xff1a; Web接口测试中最常见的请求方法是GET和POST&#xff0c;它们构成了大部分的Web API请求。在接口测试开发过程中&#xff0c;了解它们的原理、特点和应用场景是非常重要的。在本文中…

抖音电商发展路径:从外链种草到达人/品牌直播

复盘抖音电商发展&#xff0c;可以总结出以下几点发展特征&#xff1a; 策略重心的变化 以种草为核心&#xff0c;给电商引流站外成交&#xff08;2019 年及之前&#xff09;→ 力推达人直播但效 果一般&#xff08;2020 上半年&#xff09;→ 推品牌自播并彻底闭环&#xff0…

postgres 简单导入导出sql脚本

postgres 简单导入导出sql脚本 导出 backup选择类型 导入功能 导出 backup 选择类型 右键点击backup&#xff1a; 成功导出sql 数据文件 导入功能 cd 进入 Postgres 安装目录进入bin目录下执行一下命令 psql -d ${database_name} -h localhost -p 5432 -U postgres -f C:\…