Skip to content

Unable to add new pin points in real time #101

@YueMiyuki

Description

@YueMiyuki
import createGlobe from "cobe";
import { useEffect, useRef } from "react";
import { useSpring } from "react-spring";

const THETA = 0.3;

// m = [-sin(phi)*cos(theta), sin(theta), cos(phi)*cos(theta)]
function centerLatLon(phi: number, theta: number): [number, number] {
  const c = Math.cos(theta);
  const s = Math.sin(theta);
  const sf = Math.sin(phi);
  const cf = Math.cos(phi);

  const mx = -sf * c;
  const my = s;
  const mz = cf * c;

  const lat = Math.asin(my) * (180 / Math.PI);
  const lon = Math.atan2(-mz, mx) * (180 / Math.PI);
  return [lat, lon];
}

export function Cobe() {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const pointerInteracting = useRef<number | null>(null);
  const pointerInteractionMovement = useRef(0);
  // Base rotation (committed after each drag)
  const phiRef = useRef(0);
  // Persistent markers across drags
  const markersRef = useRef<{ location: [number, number]; size: number }[]>([]);
  const [{ r }, api] = useSpring(() => ({
    r: 0,
    config: {
      mass: 1,
      tension: 280,
      friction: 40,
      precision: 0.001,
    },
  }));

  useEffect(() => {
    let width = 0;
    const onResize = () => {
      if (canvasRef.current) width = canvasRef.current.offsetWidth || 0;
    };
    window.addEventListener("resize", onResize);
    onResize();

    const globe = createGlobe(canvasRef.current as HTMLCanvasElement, {
      devicePixelRatio: 2,
      width: width * 2,
      height: width * 2,
      phi: 0,
      theta: THETA,
      dark: 1,
      diffuse: 3,
      mapSamples: 16000,
      mapBrightness: 1.2,
      baseColor: [1, 1, 1],
      markerColor: [251 / 255, 100 / 255, 21 / 255],
      glowColor: [1.2, 1.2, 1.2],
      markers: [],
      onRender: (state) => {
        // Only rotate based on current drag offset + committed base
        const currentPhi = phiRef.current + r.get();
        state.phi = currentPhi;
        state.theta = THETA;
        state.width = width * 2;
        state.height = width * 2;
        // Persist existing pins only
        state.markers = markersRef.current;
      },
    });

    setTimeout(() => {
      if (canvasRef.current) canvasRef.current.style.opacity = "1";
    });

    return () => {
      globe.destroy();
      window.removeEventListener("resize", onResize);
    };
  }, [r]);

  const finalizeDragAndPin = () => {
    // Only finalize if we were dragging
    if (pointerInteracting.current !== null) {
      const currentPhi = phiRef.current + r.get();
      const [lat, lon] = centerLatLon(currentPhi, THETA);
      // Add a persistent pin at the current center
      markersRef.current = [
        ...markersRef.current,
        { location: [lat, lon], size: 0.06 },
      ];
      // Commit the rotation and reset drag delta
      phiRef.current = currentPhi;
      pointerInteracting.current = null;
      pointerInteractionMovement.current = 0;
      api.start({ r: 0 });
      if (canvasRef.current) canvasRef.current.style.cursor = "grab";
    }
  };

  return (
    <div
      style={{
        width: "100%",
        maxWidth: 600,
        aspectRatio: 1,
        margin: "auto",
        position: "relative",
      }}
    >
      <canvas
        ref={canvasRef}
        onPointerDown={(e) => {
          pointerInteracting.current =
            e.clientX - pointerInteractionMovement.current;
          if (canvasRef.current) canvasRef.current.style.cursor = "grabbing";
        }}
        onPointerUp={finalizeDragAndPin}
        onPointerOut={finalizeDragAndPin}
        onTouchEnd={finalizeDragAndPin}
        onMouseMove={(e) => {
          if (pointerInteracting.current !== null) {
            const delta = e.clientX - pointerInteracting.current;
            pointerInteractionMovement.current = delta;
            api.start({ r: delta / 200 });
          }
        }}
        onTouchMove={(e) => {
          if (pointerInteracting.current !== null && e.touches[0]) {
            const delta = e.touches[0].clientX - pointerInteracting.current;
            pointerInteractionMovement.current = delta;
            api.start({ r: delta / 100 });
          }
        }}
        style={{
          width: "100%",
          height: "100%",
          cursor: "grab",
          contain: "layout paint size",
          opacity: 0,
          transition: "opacity 1s ease",
        }}
      />
    </div>
  );
}

It is required to update twice before the last pin point can be added:
https://github.com/user-attachments/assets/a9c08048-13c1-42f0-b492-f2ea0ee35471

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions