import { useMap } from "@vis.gl/react-google-maps";
import { useEffect, useRef } from "react";
type EventCircleProps = {
center: google.maps.LatLngLiteral;
radius: number;
fillColor: string;
strokeColor: string;
strokeWeight: number;
onClick?: () => void;
};
export const EventCircle: React.FC<EventCircleProps> = ({
center,
radius,
fillColor,
strokeColor,
strokeWeight,
onClick,
}) => {
const map = useMap();
const overlayRef = useRef<google.maps.OverlayView | null>(null);
const circleRef = useRef<google.maps.Circle | null>(null);
useEffect(() => {
if (!map) return;
const circle = new google.maps.Circle({
center,
radius,
fillColor,
fillOpacity: 0.8,
strokeColor,
strokeWeight,
map,
});
circleRef.current = circle;
class TextOverlay extends google.maps.OverlayView {
private div: HTMLDivElement | null = null;
private position: google.maps.LatLngLiteral;
private radius: number;
constructor(position: google.maps.LatLngLiteral, radius: number) {
super();
this.position = position;
this.radius = radius;
}
onAdd() {
const div = document.createElement("div");
div.style.position = "absolute";
div.style.transform = "translate(-50%, -50%)";
div.style.pointerEvents = "none";
div.innerHTML = `<span style="color: black; font-weight: bold; line-height: 1;">E</span>`;
this.div = div;
const panes = this.getPanes();
panes?.overlayMouseTarget.appendChild(div);
}
draw() {
if (!this.div) return;
const overlayProjection = this.getProjection();
const map = this.getMap() as google.maps.Map;
if (!map) return;
const centerPos = overlayProjection.fromLatLngToDivPixel(
new google.maps.LatLng(this.position.lat, this.position.lng),
);
// Calculate circle radius in pixels at current zoom
const scale = 2 ** (map.getZoom() || 0);
const metersPerPixel =
(156543.03392 * Math.cos((this.position.lat * Math.PI) / 180)) /
scale;
const radiusInPixels = this.radius / metersPerPixel;
// Scale font size based on circle size
const fontSize = Math.max(12, Math.min(64, radiusInPixels * 0.8));
if (centerPos) {
this.div.style.left = `${centerPos.x}px`;
this.div.style.top = `${centerPos.y}px`;
const span = this.div.querySelector("span") as HTMLSpanElement;
if (span) {
span.style.fontSize = `${fontSize}px`;
}
}
}
onRemove() {
if (this.div) {
this.div.parentNode?.removeChild(this.div);
this.div = null;
}
}
updatePosition(position: google.maps.LatLngLiteral) {
this.position = position;
this.draw();
}
}
const overlay = new TextOverlay(center, radius);
overlay.setMap(map);
overlayRef.current = overlay;
const listeners: google.maps.MapsEventListener[] = [];
const zoomListener = map.addListener("zoom_changed", () => {
overlay.draw();
});
listeners.push(zoomListener);
if (onClick) {
const clickListener = circle.addListener("click", onClick);
listeners.push(clickListener);
}
return () => {
for (const listener of listeners) {
google.maps.event.removeListener(listener);
}
circle.setMap(null);
overlay.setMap(null);
};
}, [map, center, radius, fillColor, strokeColor, strokeWeight, onClick]);
return null;
};