获取一段被点击的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]);
}
}}
单击时,我们将 latlng
和 L.polyline
实例传递给 getSegment
。 geSegment
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>
在我的应用程序中我有:
<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]);
}
}}
单击时,我们将 latlng
和 L.polyline
实例传递给 getSegment
。 geSegment
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>