I'm implementing a feature where users can overlay an image onto a map. The idea is for users to select two reference points on the map and two on the image (but using map coordinates i receive). Based on these points, I aim to appropriately rotate, scale, and translate the image to fit the desired location on the map.
Currently, the rotation and scaling work to some extent, but it's not perfect. Additionally, I haven't addressed the translation aspect yet.
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!!
}