获取一段被点击的Polyline

Obtain a segment of Polyline which was clicked

在我的应用程序中我有:

<MapContainer {...opts} scrollWheelZoom>
  <>..</>
  <Polyline eventHandlers={{ click: e => { console.log('line clicked', e.sourceTarget) }, }} positions={positonArrat}/>}
</MapContainer>

每条折线由N条直线段组成。

如何在不使用 rocket science 的情况下获取被点击的折线段?

另一种选择是用多条线替换一条折线,这样每次点击都会得到一个结果,但开箱即用的解决方案会很好...

有几种方法可以做到这一点,但据我所知,传单没有内置/原生的方法。正如您提到的,将您的多段线重新绘制为一系列单段多段线是一种方法。看来你心里已经有了这个攻略计划,那我们换个方式讨论吧。

这不涉及火箭科学,但涉及数学。您的评论提到您不需要计算距离,因为您已经知道该点属于折线。仅仅知道该点属于折线是不够的。您需要知道单击点与折线的每段之间的最短距离。所以你需要距离。

leaflet-geometryutil 提供了一些有用的功能。以下代码使用 distanceSegment 函数获取点与单段折线之间的最短距离。让我们看看“onClick”:

eventHandlers={{
  click: (e) => {
    const closest = getSegment(e.latlng, e.sourceTarget);
    setResults([...results, closest]);
  }
}}

单击时,我们将 latlngL.polyline 实例传递给 getSegmentgeSegment returns 一个对象,其中包含 L.polyline._latlngs 数组的索引,该数组与到单击点的最短距离关联,距离,为了方便起见,latlng 的数组以及它之后的一个:

function getSegment(latlng, polyline) {

  // get layerpoint of user click
  const latlngs = polyline._latlngs;
  let segments = [];

  // get segments of polyline
  // calculate distances from point to each polyline
  for (let i = 0; i < latlngs.length - 1; i++) {
    const pointToLineDistance = GeoUtil.distanceSegment(
      polyline._map,
      latlng,
      latlngs[i],
      latlngs[i + 1]
    );

    segments.push({
      index: i,
      pointToLineDistance,
      segment: [latlngs[i], latlngs[i + 1]]
    });
  }

  // sort segments by shortest distance
  segments.sort((a, b) =>
    a.pointToLineDistance < b.pointToLineDistance ? -1 : 1
  );

  // return first entry, which has shortest distance
  return segments[0];
}

因为我们使用的是 react-leaflet,所以我使用了 useState 函数来捕获任何点击的片段,然后将它们渲染到地图上:

{results.map((r, i) => (
  <Polyline key={`polyline-${i}`} positions={r.segment} color="green" />
))}

Working codesandbox

单击沙盒中的折线。最接近点击的片段将被识别、保存到状态并呈现为绿色。

除了您分解折线的方法或做一些数学运算之外,我不确定还有什么方法可以做到这一点。我相信你能想出一些变化

我最终使用了 simple math 并且 onClick 函数看起来像:

const TOLERANCE = 3

onPolylineClick = e => {
  const { x, y } = e.layerPoint
  const parts = e.target._parts[ 0 ] // some undocumented shit
  let prev = parts[ 0 ]
  const section = parts.slice( 1, parts.length ).findIndex( p => {
    try{
      // check if in bounds
      if( !( Math.min( prev.x, p.x ) <= x <= Math.max( prev.x, p.x ) && 
             Math.min( prev.y, p.y ) <= y <= Math.max( prev.y, p.y ) ) ) return false
      const yCalc = ( x - prev.x ) * ( p.y - prev.y ) / ( p.x - prev.x ) + prev.y
      return Math.abs( y - yCalc ) < TOLERANCE
    }finally{
      prev = p
    }
  })
  this.setState( { selectedSection:section } )
}

设置state中匹配段第1点的索引。

然后为了可视化所选部分,我在顶部显示了一条由 2 个点组成的橙色多段​​线:

<MapContainer {...opts} scrollWheelZoom>

  <Polyline eventHandlers={{ click:this.onPolylineClick }} positions={points} />}
  
  {-1 !== selectedSection && 
     <Polyline color="orange" positions={points.slice( selectedSection, selectedSection + 2 )}/>}

</MapContainer>