旋转和缩放图像以匹配 Leaflet 中地图上的参考点

问题描述 投票:0回答:1

我正在实现一项功能,用户可以将图像叠加到地图上。这个想法是让用户选择地图上的两个参考点和图像上的两个参考点(但使用我收到的地图坐标)。基于这些点,我的目标是适当旋转、缩放和平移图像以适合地图上的所需位置。

目前,旋转和缩放在一定程度上有效,但并不完美。另外,我还没有解决翻译方面的问题。

const PlantEditDrawer = () => {
  const { isPlantEditDrawerOpen } = useDashBoardContext();
  const [mounted, setMounted] = useState(false);
  const { leafletMap } = useLeafletMapContext();
  const { north, east, south, west } = usePositionBoundsContext();
  const [firstImage, setFirstImage] = useState<L.LatLng>();
  const [secondImage, setSecondImage] = useState<L.LatLng>();
  const [firstMap, setFirstMap] = useState<L.LatLng>();
  const [secondMap, setSecondMap] = useState<L.LatLng>();
  const [isDone, setDone] = useState(false);
  // const [imageBounds, setImageBounds] = useState<number[]>()
  const imageRef = useRef<L.ImageOverlay>();

  useEffect(() => {
    if (mounted) return;
    setMounted(true);
  }, [mounted]);
  const handleSecondMapClick = useCallback(
    (e: LeafletMouseEvent) => {
      setSecondMap(e.latlng);
      leafletMap?.current!.off("click");
      setDone(true);
    },
    [leafletMap]
  );

  const handleSecondImageClick = useCallback(
    (e: LeafletMouseEvent) => {
      setSecondImage(e.latlng);
      if (imageRef.current && leafletMap?.current) {
        leafletMap.current.removeLayer(imageRef.current);
        leafletMap.current.on("click", handleSecondMapClick);
      }
    },
    [handleSecondMapClick, leafletMap]
  );

  const handleFirstMapClick = useCallback(
    (e: LeafletMouseEvent) => {
      setFirstMap(e.latlng);
      imageRef.current?.addTo(leafletMap?.current!);
      imageRef.current?.on("click", handleSecondImageClick);
      leafletMap?.current!.off("click");
    },
    [handleSecondImageClick, leafletMap]
  );

  const handleFirstImageClick = useCallback(
    (e: LeafletMouseEvent) => {
      setFirstImage(e.latlng);
      if (imageRef.current && leafletMap?.current) {
        imageRef.current.off("click");
        leafletMap.current.removeLayer(imageRef.current);
        leafletMap.current.on("click", handleFirstMapClick);
      }
    },
    [handleFirstMapClick, leafletMap]
  );


  useEffect(() => {
    if (!leafletMap?.current) return;
    const bounds = L.latLngBounds([
      [south, west],
      [north, east],
    ]);
    imageRef.current = L.imageOverlay(image, bounds, {
      interactive: true,
      opacity: 0.5,
    }).addTo(leafletMap.current);
    imageRef.current?.on("click", handleFirstImageClick);
  }, [east, leafletMap, north, south, west, mounted, handleFirstImageClick]);

  function calculateAngle(p1:any, p2:any) {
    const deltaX = p2.lng - p1.lng;
    const deltaY = p2.lat - p1.lat;
    return Math.atan2(deltaX, deltaY) * (180 / Math.PI);
}


  useEffect(() => {
    if (!isDone || !firstImage || !secondImage || !firstMap || !secondMap) return;
    //calculate the angle here
    const mapAngle = calculateAngle(firstMap, secondMap)
    const imageAngle = calculateAngle(firstImage, secondImage)

    const rotationRequired = mapAngle - imageAngle;

    const firstImageLatLng = L.latLng(firstImage.lat, firstImage.lng);
    const secondImageLatLng = L.latLng(secondImage.lat, secondImage.lng);
    const imageDistance = firstImageLatLng.distanceTo(secondImageLatLng);

    const firstMapLatLng = L.latLng(firstMap.lat, firstMap.lng);
    const secondMapLatLng = L.latLng(secondMap.lat, secondMap.lng);
    const mapDistance = firstMapLatLng.distanceTo(secondMapLatLng);

    const scaleFactor = mapDistance / imageDistance;
    if (imageRef.current) {
      imageRef.current.addTo(leafletMap?.current!);
      const test = imageRef.current.getElement();
      test!.style.transformOrigin = "center";
      const { transform } = test!.style;
      test!.style.transform = ${transform} rotate(${rotationRequired}deg) scale(${scaleFactor});
    }
  }, [firstImage, firstMap, isDone, leafletMap, secondImage, secondMap]);

!!Did not add return value!!
}
css reactjs math leaflet transformation
1个回答
0
投票

警告:我对传单一无所知。我不确定您的转换需要什么格式。我希望您能以某种方式利用 CSS 矩阵变换

我假设您只有各向同性缩放(即所有方向上的缩放因子相同,并且也没有剪切)是否正确?那么2分应该就够了。

您是否在与地球曲率相关的尺度上进行操作,或者我们是否可以将其视为本质上是一个二维平面问题?为了简单起见,目前我将坚持使用平面设置。平面保角(正交)仿射变换将是

x_out = a*x_in - b*y_in + c
y_out = b*x_in + a*y_in + d

在普通 JS 和 CSS 中,这对应于

style.transform = `matrix(${a}, ${b}, ${-b}, ${a}, ${c}, ${d})`;
。通常,您会在左上角使用变换原点,但在纬度/经度坐标系中,原点实际上可能远离页面。

给定两个点的输入坐标和所得输出坐标,这会产生四个未知数的四个线性方程。您可以用它来求解

a,b,c,d
。请注意,尽管您可以从
atan2(b,a)
确定旋转角度,但不需要三角学。

我可能会象征性地写出方程,并使用 Sage 求解一次,然后将所得公式插入到我的应用程序中,这样我就不必自己实现一些高斯消除。假设我们将

(x1i, y1i)
映射到
(x1o, y1o)
并将
(x2i, y2i)
映射到
(x2o, y2o)
我得到这个:

const dxi = x1i - x2i;
const dyi = y1i - y2i;
const dxo = x1o - x2o;
const dyo = y1o - y2o;
const denom = dxi*dxi + dyi*dyi;
const a = (dxi*dxo + dyi*dyo)/denom;
const b = (dxi*dyo - dyi*dxo)/denom;
const c = ((x1i*y2i - x2i*y1i)*dyo + dxi*(x1i*x2o - x2i*x1o) + dyi*(y1i*x2o - y2i*x1o))/denom;
const d = ((y1i*x2i - y2i*x1i)*dxo + dyi*(y1i*y2o - y2i*y1o) + dxi*(x1i*y2o - x2i*y1o))/denom;
elt.style.transform = `matrix(${a}, ${b}, ${-b}, ${a}, ${c}, ${d})`;

请注意,此公式作为矩阵假定为方形坐标系,即沿一个轴的一个单位与沿另一个轴的一个单位一样长。从地理角度来说,北距一单位应与东距一单位一样长。如果情况并非如此,例如因为您正在处理以度或弧度给出的纬度和经度,那么您可能需要首先转换为方形坐标系,然后再转换回角度坐标系。只需用平均纬度的余弦缩放一个轴就足够了。

但是你真的必须使用地理坐标吗?如果您可以使用像素坐标而不是地理坐标,所有这一切都会简单得多。阅读 leaflet MouseEvent 文档 我看到有一个

layerPoint
属性可以为您提供像素坐标。如果我是你,我会在这里放弃此任务的所有地理概念,而是使用像素坐标来进行计算并表达变换。

© www.soinside.com 2019 - 2024. All rights reserved.