Skip to content

判断点是否在面内

使用 leaflet、vue、turfjs 判断一个点是否在一个面内(多边形、圆)。

Turf.js用于处理地理空间数据和地理空间分析。它提供了一系列函数和工具,如空间分析、地图制图、坐标转换、地理编码等。它很不错哦!

示例

安装依赖

shell
pnpm install @turf/turf

项目引入

js
// 命名别名是为了不和其他冲突
import {
    booleanPointInPolygon,
    point as turfPoint,
    polygon as turfPolygon
} from '@turf/turf';

代码实现

vue
<script setup>
import { ref, defineAsyncComponent } from 'vue';
import {
  booleanPointInPolygon,
  point as turfPoint,
  polygon as turfPolygon
} from '@turf/turf';

const InitMap = defineAsyncComponent(() =>
  import('../../components/InitMapTianditu.vue')
);

const mapObj = ref();

// 创建多边形
const polygonData = [
  {
    lat: 31.979921773775636,
    lng: 118.75190734863283
  },
  {
    lat: 32.042300782300785,
    lng: 118.75190734863283
  },
  {
    lat: 32.042300782300785,
    lng: 118.85696411132814
  },
  {
    lat: 31.979921773775636,
    lng: 118.85696411132814
  }
];

const polygonOverlay = ref();
const createPolygon = () => {
  polygonOverlay.value = L.polygon(polygonData, { color: 'red' }).addTo(
    mapObj.value
  );
};

// 创建与圆形逼近的正多边形的顶点坐标数组
const getCirclePolygonCoords = (point, radius) => {
  let coords = [];

  // 正多边形的边数,数值越大逼近越精确(使用多少个点围成一个圆)
  const numVertices = 64;

  for (let i = 0; i < numVertices; i++) {
    const angle = (i / numVertices) * Math.PI * 2;
    const x = point.lng + (radius / 100000) * Math.cos(angle);
    const y = point.lat + (radius / 100000) * Math.sin(angle);
    coords.push([x, y]);
  }

  // 关闭多边形,多边形头尾节点需要一样。
  coords.push(coords[0]);

  return coords;
};

// 获取圆形多边形坐标
const circlePolygonCoords = ref([]);
const createCircle = () => {
  const point = {
    lat: 32.04959129198556,
    lng: 118.68255615234376
  };

  const radius = 3740;

  L.circle([point.lat, point.lng], {
    radius
  }).addTo(mapObj.value);

  circlePolygonCoords.value = getCirclePolygonCoords(point, radius);
};

// 创建信息 marker
const infoMarker = ref();
const createMarker = (point, text) => {
  if (infoMarker.value) {
    mapObj.value.removeLayer(infoMarker.value);
    infoMarker.value = null;
  }

  infoMarker.value = L.marker([point.lat, point.lng], {}).addTo(mapObj.value);

  infoMarker.value.bindPopup(`<p>${text}<p>`).openPopup();
};

// 地图点击事件
const handleClick = (e) => {
  const point = e.latlng;

  const isPointInsidePolygon = booleanPointInPolygon(
    turfPoint([point.lng, point.lat]),
    polygonOverlay.value.toGeoJSON()
  );

  if (isPointInsidePolygon) {
    createMarker(point, '在多边形内');
    return;
  }

  const isPointInsideCircle = booleanPointInPolygon(
    turfPoint([point.lng, point.lat]),
    turfPolygon([circlePolygonCoords.value])
  );

  if (isPointInsideCircle) {
    createMarker(point, '在圆内');
    return;
  }

  createMarker(point, '不再任何图形内');
};

const mapLoad = (map) => {
  mapObj.value = map;
  createPolygon();
  createCircle();
  map.on('click', handleClick);
};
</script>

<template>
  <init-map @map-load="mapLoad"></init-map>
</template>

<style scoped></style>
vue
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
// todo 项目使用请放开 leaflet 引入
// import L from 'leaflet';

const props = defineProps({
  center: {
    type: Array,
    default: [32.0237855, 118.8075675]
  }
})

const emit = defineEmits(['mapLoad']);

const mapRef = ref();

const initMap = () => {
  const map = L.map(mapRef.value, {
    center: props.center,
    zoom: 11,
    minZoom: 6,
    maxZoom: 20
  });

  const mapType = 'vec';
  L.tileLayer(
    'https://t{s}.tianditu.gov.cn/' +
      mapType +
      '_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' +
      mapType +
      '&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=b72aa81ac2b3cae941d1eb213499e15e',
    {
      subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
      attribution:
        '&copy; <a href="http://lbs.tianditu.gov.cn/home.html">天地图 GS(2022)3124号 - 甲测资字1100471</a>'
    }
  ).addTo(map);

  const mapLabelType = 'cva';
  L.tileLayer(
    'https://t{s}.tianditu.gov.cn/' +
      mapLabelType +
      '_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=' +
      mapLabelType +
      '&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=b72aa81ac2b3cae941d1eb213499e15e',
    {
      subdomains: ['0', '1', '2', '3', '4', '5', '6', '7']
    }
  ).addTo(map);

  // 地图初始化完成发送事件
  emit('mapLoad', map);

  return map;
};

const mapObj = ref();

// 在 onMounted 中初始化地图
onMounted(() => {
  mapObj.value = initMap();
});

const removeMap = () => {
  if (mapObj.value) {
    mapObj.value.remove();
  }
};

// 在组件卸载时删除地图
onUnmounted(() => {
  removeMap();
});
</script>

<template>
  <div ref="mapRef" class="map"></div>
</template>

<style scoped>
.map {
  height: 40vh;
  z-index: 0;
}
</style>

贡献者

Released under the MIT License.