我正在实现一项功能,用户可以将图像叠加到地图上。这个想法是让用户选择地图上的两个参考点和图像上的两个参考点(但使用我收到的地图坐标)。基于这些点,我的目标是适当旋转、缩放和平移图像以适合地图上的所需位置。
目前,旋转和缩放在一定程度上有效,但并不完美。另外,我还没有解决翻译方面的问题。
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 矩阵变换。
我假设您只有各向同性缩放(即所有方向上的缩放因子相同,并且也没有剪切)是否正确?那么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
属性可以为您提供像素坐标。如果我是你,我会在这里放弃此任务的所有地理概念,而是使用像素坐标来进行计算并表达变换。