// Generated bundle - DO NOT EDIT DIRECTLY. Edit files in src/cesiumjs_anywidget/js/ instead.


// src/cesiumjs_anywidget/js/logger.js
var debugEnabled = false;
function setDebugMode(enabled) {
  debugEnabled = enabled;
  if (enabled) {
    console.log("[CesiumWidget] Debug mode enabled");
  }
}
function log(prefix, ...args) {
  if (debugEnabled) {
    console.log(`[CesiumWidget:${prefix}]`, ...args);
  }
}
function warn(prefix, ...args) {
  console.warn(`[CesiumWidget:${prefix}]`, ...args);
}
function error(prefix, ...args) {
  console.error(`[CesiumWidget:${prefix}]`, ...args);
}

// src/cesiumjs_anywidget/js/viewer-init.js
var PREFIX = "ViewerInit";
var CESIUM_CDN_VERSION = "1.138";
var CONSTANTS = {
  // CesiumJS CDN
  CESIUM_CDN_VERSION,
  // Render Policy
  RUNNING_TARGET_FPS: 60,
  // Interaction Timing
  TIMELINE_SCRUB_DEBOUNCE_MS: 500,
  // GeoJSON Defaults
  GEOJSON_STROKE_WIDTH: 3,
  GEOJSON_FILL_ALPHA: 0.5
};
function patchWorkerForCSP() {
  if (typeof window === "undefined" || !window.Worker) return;
  if (window.__workerCSPPatched) return;
  const OriginalWorker = window.Worker;
  const origin = window.location.origin;
  function PatchedWorker(url, options) {
    if (typeof url === "string" && url.startsWith("http") && !url.startsWith(origin)) {
      const blob = new Blob(
        [`importScripts('${url}')`],
        { type: "application/javascript" }
      );
      url = URL.createObjectURL(blob);
    }
    return new OriginalWorker(url, options);
  }
  PatchedWorker.prototype = OriginalWorker.prototype;
  window.Worker = PatchedWorker;
  window.__workerCSPPatched = true;
  log(PREFIX, "Worker patched for CSP blob: compatibility");
}
async function loadCesiumJS() {
  log(PREFIX, "Loading CesiumJS...");
  if (window.Cesium) {
    log(PREFIX, "CesiumJS already loaded, reusing existing instance");
    return window.Cesium;
  }
  const cesiumCssId = "cesium-widgets-css";
  if (!document.getElementById(cesiumCssId)) {
    const link = document.createElement("link");
    link.id = cesiumCssId;
    link.rel = "stylesheet";
    link.href = `https://cesium.com/downloads/cesiumjs/releases/${CESIUM_CDN_VERSION}/Build/Cesium/Widgets/widgets.css`;
    document.head.appendChild(link);
  }
  const script = document.createElement("script");
  script.src = `https://cesium.com/downloads/cesiumjs/releases/${CESIUM_CDN_VERSION}/Build/Cesium/Cesium.js`;
  log(PREFIX, "Loading CesiumJS from CDN...");
  await new Promise((resolve, reject) => {
    script.onload = () => {
      log(PREFIX, "CesiumJS script loaded successfully");
      resolve();
    };
    script.onerror = (err) => {
      error(PREFIX, "Failed to load CesiumJS script:", err);
      reject(err);
    };
    document.head.appendChild(script);
  });
  log(PREFIX, "CesiumJS initialized");
  return window.Cesium;
}
function createLoadingIndicator(container, hasToken) {
  const loadingDiv = document.createElement("div");
  loadingDiv.textContent = "Loading CesiumJS...";
  loadingDiv.style.cssText = "position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 18px; color: #fff; background: rgba(0,0,0,0.7); padding: 20px; border-radius: 5px;";
  if (!hasToken) {
    loadingDiv.innerHTML = `
      <div style="text-align: center;">
        <div>Loading CesiumJS...</div>
        <div style="font-size: 12px; margin-top: 10px; color: #ffa500;">
          \u26A0\uFE0F No Cesium Ion token set<br>
          Some features may not work
        </div>
      </div>
    `;
  }
  container.appendChild(loadingDiv);
  return loadingDiv;
}
function createViewer(container, model, Cesium) {
  log(PREFIX, "Creating viewer with options...");
  const shouldAnimate = model.get("should_animate") === true;
  const requestRenderMode = shouldAnimate ? false : model.get("request_render_mode") ?? true;
  const maximumRenderTimeChange = model.get("maximum_render_time_change") ?? (shouldAnimate ? 0 : Number.POSITIVE_INFINITY);
  const viewerOptions = {
    timeline: model.get("show_timeline"),
    animation: model.get("show_animation"),
    requestRenderMode,
    maximumRenderTimeChange,
    baseLayerPicker: true,
    geocoder: true,
    homeButton: true,
    sceneModePicker: true,
    navigationHelpButton: true,
    fullscreenButton: true,
    scene3DOnly: false,
    shadows: false,
    shouldAnimate: false
  };
  const showGlobe = model.get("show_globe");
  if (showGlobe === false) {
    viewerOptions.globe = false;
    log(PREFIX, "Globe disabled (typically for photorealistic tiles)");
  }
  log(PREFIX, "Viewer options:", viewerOptions);
  if (model.get("enable_terrain") && showGlobe !== false) {
    viewerOptions.terrain = Cesium.Terrain.fromWorldTerrain();
    log(PREFIX, "Terrain enabled");
  }
  const viewer = new Cesium.Viewer(container, viewerOptions);
  if (viewer.scene.globe) {
    viewer.scene.globe.enableLighting = model.get("enable_lighting");
    log(PREFIX, "Viewer created, lighting:", model.get("enable_lighting"));
  } else {
    log(PREFIX, "Viewer created without globe");
  }
  return viewer;
}
function setupViewerListeners(viewer, model, container, Cesium) {
  log(PREFIX, "Setting up viewer listeners");
  let isDestroyed = false;
  let scrubTimeout = null;
  let isApplyingRunningPolicy = false;
  function requestSceneRender() {
    if (!viewer || !viewer.scene || !viewer.scene.requestRender) return;
    viewer.scene.requestRender();
  }
  function resolveMaximumRenderTimeChange() {
    const configured = model.get("maximum_render_time_change");
    if (configured !== void 0 && configured !== null) {
      return configured;
    }
    return viewer.clock?.shouldAnimate ? 0 : Number.POSITIVE_INFINITY;
  }
  function applyAnimationRenderPolicy(isAnimating) {
    if (!viewer || !viewer.scene) return;
    if (isApplyingRunningPolicy === isAnimating) return;
    if (isAnimating) {
      viewer.scene.requestRenderMode = false;
      viewer.scene.maximumRenderTimeChange = 0;
      viewer.targetFrameRate = CONSTANTS.RUNNING_TARGET_FPS;
      isApplyingRunningPolicy = true;
      log(PREFIX, "Applied running render policy:", {
        requestRenderMode: viewer.scene.requestRenderMode,
        maximumRenderTimeChange: viewer.scene.maximumRenderTimeChange,
        targetFrameRate: viewer.targetFrameRate
      });
      return;
    }
    viewer.scene.requestRenderMode = model.get("request_render_mode") ?? true;
    viewer.scene.maximumRenderTimeChange = resolveMaximumRenderTimeChange();
    viewer.targetFrameRate = void 0;
    isApplyingRunningPolicy = false;
    log(PREFIX, "Applied paused render policy:", {
      requestRenderMode: viewer.scene.requestRenderMode,
      maximumRenderTimeChange: viewer.scene.maximumRenderTimeChange,
      targetFrameRate: viewer.targetFrameRate
    });
  }
  model.on("change:request_render_mode", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.scene) return;
    const isAnimating = viewer.clock?.shouldAnimate === true;
    applyAnimationRenderPolicy(isAnimating);
    log(PREFIX, "requestRenderMode changed, animating:", isAnimating);
    if (!isAnimating) {
      requestSceneRender();
    }
  });
  model.on("change:maximum_render_time_change", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.scene) return;
    const isAnimating = viewer.clock?.shouldAnimate === true;
    applyAnimationRenderPolicy(isAnimating);
    log(PREFIX, "maximumRenderTimeChange changed, animating:", isAnimating);
    if (!isAnimating) {
      requestSceneRender();
    }
  });
  model.on("change:request_render_trigger", () => {
    if (isDestroyed) return;
    log(PREFIX, "Explicit render requested from Python");
    requestSceneRender();
  });
  model.on("change:enable_terrain", () => {
    if (isDestroyed) {
      log(PREFIX, "Skipping enable_terrain change - destroyed");
      return;
    }
    if (!viewer) return;
    log(PREFIX, "Terrain setting changed:", model.get("enable_terrain"));
    if (model.get("enable_terrain")) {
      viewer.scene.setTerrain(Cesium.Terrain.fromWorldTerrain());
    } else {
      viewer.scene.setTerrain(void 0);
    }
  });
  model.on("change:enable_lighting", () => {
    if (isDestroyed) return;
    if (!viewer) return;
    log(PREFIX, "Lighting setting changed:", model.get("enable_lighting"));
    viewer.scene.globe.enableLighting = model.get("enable_lighting");
    requestSceneRender();
  });
  model.on("change:height", () => {
    if (isDestroyed) return;
    if (!viewer) return;
    log(PREFIX, "Height changed:", model.get("height"));
    container.style.height = model.get("height");
    viewer.resize();
  });
  model.on("change:show_timeline", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.timeline) return;
    log(PREFIX, "Timeline visibility changed:", model.get("show_timeline"));
    viewer.timeline.container.style.visibility = model.get("show_timeline") ? "visible" : "hidden";
  });
  model.on("change:show_animation", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.animation) return;
    log(PREFIX, "Animation visibility changed:", model.get("show_animation"));
    viewer.animation.container.style.visibility = model.get("show_animation") ? "visible" : "hidden";
  });
  model.on("change:atmosphere_settings", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.scene || !viewer.scene.atmosphere) return;
    const settings = model.get("atmosphere_settings");
    if (!settings || Object.keys(settings).length === 0) return;
    log(PREFIX, "Atmosphere settings changed:", settings);
    const atmosphere = viewer.scene.atmosphere;
    if (settings.brightnessShift !== void 0) {
      atmosphere.brightnessShift = settings.brightnessShift;
    }
    if (settings.hueShift !== void 0) {
      atmosphere.hueShift = settings.hueShift;
    }
    if (settings.saturationShift !== void 0) {
      atmosphere.saturationShift = settings.saturationShift;
    }
    if (settings.lightIntensity !== void 0) {
      atmosphere.lightIntensity = settings.lightIntensity;
    }
    if (settings.rayleighCoefficient !== void 0 && Array.isArray(settings.rayleighCoefficient) && settings.rayleighCoefficient.length === 3) {
      atmosphere.rayleighCoefficient = new Cesium.Cartesian3(
        settings.rayleighCoefficient[0],
        settings.rayleighCoefficient[1],
        settings.rayleighCoefficient[2]
      );
    }
    if (settings.rayleighScaleHeight !== void 0) {
      atmosphere.rayleighScaleHeight = settings.rayleighScaleHeight;
    }
    if (settings.mieCoefficient !== void 0 && Array.isArray(settings.mieCoefficient) && settings.mieCoefficient.length === 3) {
      atmosphere.mieCoefficient = new Cesium.Cartesian3(
        settings.mieCoefficient[0],
        settings.mieCoefficient[1],
        settings.mieCoefficient[2]
      );
    }
    if (settings.mieScaleHeight !== void 0) {
      atmosphere.mieScaleHeight = settings.mieScaleHeight;
    }
    if (settings.mieAnisotropy !== void 0) {
      atmosphere.mieAnisotropy = settings.mieAnisotropy;
    }
    requestSceneRender();
  });
  model.on("change:sky_atmosphere_settings", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.scene || !viewer.scene.skyAtmosphere) return;
    const settings = model.get("sky_atmosphere_settings");
    if (!settings || Object.keys(settings).length === 0) return;
    log(PREFIX, "Sky atmosphere settings changed:", settings);
    const skyAtmosphere = viewer.scene.skyAtmosphere;
    if (settings.show !== void 0) {
      skyAtmosphere.show = settings.show;
    }
    if (settings.brightnessShift !== void 0) {
      skyAtmosphere.brightnessShift = settings.brightnessShift;
    }
    if (settings.hueShift !== void 0) {
      skyAtmosphere.hueShift = settings.hueShift;
    }
    if (settings.saturationShift !== void 0) {
      skyAtmosphere.saturationShift = settings.saturationShift;
    }
    if (settings.atmosphereLightIntensity !== void 0) {
      skyAtmosphere.atmosphereLightIntensity = settings.atmosphereLightIntensity;
    }
    if (settings.atmosphereRayleighCoefficient !== void 0 && Array.isArray(settings.atmosphereRayleighCoefficient) && settings.atmosphereRayleighCoefficient.length === 3) {
      skyAtmosphere.atmosphereRayleighCoefficient = new Cesium.Cartesian3(
        settings.atmosphereRayleighCoefficient[0],
        settings.atmosphereRayleighCoefficient[1],
        settings.atmosphereRayleighCoefficient[2]
      );
    }
    if (settings.atmosphereRayleighScaleHeight !== void 0) {
      skyAtmosphere.atmosphereRayleighScaleHeight = settings.atmosphereRayleighScaleHeight;
    }
    if (settings.atmosphereMieCoefficient !== void 0 && Array.isArray(settings.atmosphereMieCoefficient) && settings.atmosphereMieCoefficient.length === 3) {
      skyAtmosphere.atmosphereMieCoefficient = new Cesium.Cartesian3(
        settings.atmosphereMieCoefficient[0],
        settings.atmosphereMieCoefficient[1],
        settings.atmosphereMieCoefficient[2]
      );
    }
    if (settings.atmosphereMieScaleHeight !== void 0) {
      skyAtmosphere.atmosphereMieScaleHeight = settings.atmosphereMieScaleHeight;
    }
    if (settings.atmosphereMieAnisotropy !== void 0) {
      skyAtmosphere.atmosphereMieAnisotropy = settings.atmosphereMieAnisotropy;
    }
    if (settings.perFragmentAtmosphere !== void 0) {
      skyAtmosphere.perFragmentAtmosphere = settings.perFragmentAtmosphere;
    }
    requestSceneRender();
  });
  model.on("change:skybox_settings", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.scene || !viewer.scene.skyBox) return;
    const settings = model.get("skybox_settings");
    if (!settings || Object.keys(settings).length === 0) return;
    log(PREFIX, "SkyBox settings changed:", settings);
    const skyBox = viewer.scene.skyBox;
    if (settings.show !== void 0) {
      skyBox.show = settings.show;
    }
    if (settings.sources !== void 0 && settings.sources !== null) {
      const sources = settings.sources;
      if (sources.positiveX && sources.negativeX && sources.positiveY && sources.negativeY && sources.positiveZ && sources.negativeZ) {
        viewer.scene.skyBox = new Cesium.SkyBox({
          sources: {
            positiveX: sources.positiveX,
            negativeX: sources.negativeX,
            positiveY: sources.positiveY,
            negativeY: sources.negativeY,
            positiveZ: sources.positiveZ,
            negativeZ: sources.negativeZ
          }
        });
        if (settings.show !== void 0) {
          viewer.scene.skyBox.show = settings.show;
        }
      }
    }
    requestSceneRender();
  });
  const initialTime = model.get("current_time");
  if (initialTime && viewer.clock) {
    try {
      const julianDate = Cesium.JulianDate.fromIso8601(initialTime);
      viewer.clock.currentTime = julianDate;
      viewer.clock.startTime = julianDate.clone();
      viewer.clock.stopTime = Cesium.JulianDate.addDays(julianDate.clone(), 1, new Cesium.JulianDate());
      log(PREFIX, "Clock initialized with time:", initialTime);
    } catch (err) {
      warn(PREFIX, "Failed to parse initial time:", initialTime, err);
    }
  }
  const initialMultiplier = model.get("clock_multiplier");
  if (initialMultiplier !== void 0 && viewer.clock) {
    viewer.clock.multiplier = initialMultiplier;
    log(PREFIX, "Clock multiplier initialized:", initialMultiplier);
  }
  const initialAnimate = model.get("should_animate");
  if (initialAnimate !== void 0 && viewer.clock) {
    viewer.clock.shouldAnimate = initialAnimate;
    log(PREFIX, "Clock animation initialized:", initialAnimate);
  }
  applyAnimationRenderPolicy(viewer.clock?.shouldAnimate === true);
  model.on("change:current_time", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.clock) return;
    const timeStr = model.get("current_time");
    if (!timeStr) return;
    try {
      const julianDate = Cesium.JulianDate.fromIso8601(timeStr);
      viewer.clock.currentTime = julianDate;
      log(PREFIX, "Clock time updated:", timeStr);
      requestSceneRender();
    } catch (err) {
      warn(PREFIX, "Failed to parse time:", timeStr, err);
    }
  });
  model.on("change:clock_multiplier", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.clock) return;
    const multiplier = model.get("clock_multiplier");
    if (multiplier !== void 0) {
      viewer.clock.multiplier = multiplier;
      log(PREFIX, "Clock multiplier updated:", multiplier);
      requestSceneRender();
    }
  });
  model.on("change:should_animate", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.clock) return;
    const shouldAnimate = model.get("should_animate");
    if (shouldAnimate !== void 0) {
      viewer.clock.shouldAnimate = shouldAnimate;
      applyAnimationRenderPolicy(shouldAnimate === true);
      log(PREFIX, "Clock animation updated:", shouldAnimate);
      if (!shouldAnimate) {
        requestSceneRender();
      }
    }
  });
  model.on("change:clock_command", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.clock) return;
    const command = model.get("clock_command");
    if (!command || !command.command) return;
    log(PREFIX, "Clock command received:", command.command);
    switch (command.command) {
      case "setTime":
        if (command.time) {
          try {
            const julianDate = Cesium.JulianDate.fromIso8601(command.time);
            viewer.clock.currentTime = julianDate;
            log(PREFIX, "Clock time set via command:", command.time);
            requestSceneRender();
          } catch (err) {
            warn(PREFIX, "Failed to parse time in command:", command.time, err);
          }
        }
        break;
      case "play":
        viewer.clock.shouldAnimate = true;
        applyAnimationRenderPolicy(true);
        log(PREFIX, "Clock animation started");
        break;
      case "pause":
        viewer.clock.shouldAnimate = false;
        applyAnimationRenderPolicy(false);
        log(PREFIX, "Clock animation paused");
        requestSceneRender();
        break;
      case "setMultiplier":
        if (command.multiplier !== void 0) {
          viewer.clock.multiplier = command.multiplier;
          log(PREFIX, "Clock multiplier set via command:", command.multiplier);
          requestSceneRender();
        }
        break;
      case "setRange":
        if (command.startTime && command.stopTime) {
          try {
            const startDate = Cesium.JulianDate.fromIso8601(command.startTime);
            const stopDate = Cesium.JulianDate.fromIso8601(command.stopTime);
            viewer.clock.startTime = startDate;
            viewer.clock.stopTime = stopDate;
            log(PREFIX, "Clock range set via command:", command.startTime, "to", command.stopTime);
            requestSceneRender();
          } catch (err) {
            warn(PREFIX, "Failed to parse time range in command:", err);
          }
        }
        break;
      default:
        warn(PREFIX, "Unknown clock command:", command.command);
    }
  });
  model.on("change:czml_entity_update", () => {
    if (isDestroyed) return;
    if (!viewer || !viewer.dataSources) return;
    const update = model.get("czml_entity_update");
    if (!update || !update.entity_id || !update.properties) return;
    log(PREFIX, "CZML entity update:", update);
    let entity = null;
    for (let i = 0; i < viewer.dataSources.length; i++) {
      const dataSource = viewer.dataSources.get(i);
      if (dataSource instanceof Cesium.CzmlDataSource) {
        entity = dataSource.entities.getById(update.entity_id);
        if (entity) break;
      }
    }
    if (!entity) {
      warn(PREFIX, "Entity not found:", update.entity_id);
      return;
    }
    const props = update.properties;
    if (props.orientation && !props.position) {
      const heading = Cesium.Math.toRadians(props.orientation.heading ?? 0);
      const pitch = Cesium.Math.toRadians(props.orientation.pitch ?? 0);
      const roll = Cesium.Math.toRadians(props.orientation.roll ?? 0);
      const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
      if (entity.position) {
        const position = entity.position.getValue(viewer.clock.currentTime);
        if (position) {
          const orientation = Cesium.Transforms.headingPitchRollQuaternion(
            position,
            hpr
          );
          entity.orientation = new Cesium.ConstantProperty(orientation);
          log(PREFIX, "Updated entity orientation:", props.orientation);
        } else {
          warn(PREFIX, "Cannot update orientation - no valid position");
        }
      } else {
        warn(PREFIX, "Cannot update orientation - entity has no position");
      }
    }
    if (props.position) {
      const lat = Cesium.Math.toRadians(props.position.latitude);
      const lon = Cesium.Math.toRadians(props.position.longitude);
      const alt = props.position.altitude ?? 0;
      const position = Cesium.Cartesian3.fromRadians(lon, lat, alt);
      entity.position = new Cesium.ConstantPositionProperty(position);
      log(PREFIX, "Updated entity position:", props.position);
      if (props.orientation && entity.orientation) {
        const heading = Cesium.Math.toRadians(props.orientation.heading ?? 0);
        const pitch = Cesium.Math.toRadians(props.orientation.pitch ?? 0);
        const roll = Cesium.Math.toRadians(props.orientation.roll ?? 0);
        const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
        const orientation = Cesium.Transforms.headingPitchRollQuaternion(position, hpr);
        entity.orientation = new Cesium.ConstantProperty(orientation);
        log(PREFIX, "Updated entity orientation after position change:", props.orientation);
      }
    }
    for (const [key, value] of Object.entries(props)) {
      if (key !== "orientation" && key !== "position" && entity[key] !== void 0) {
        try {
          if (typeof value === "object" && value !== null) {
            for (const [subKey, subValue] of Object.entries(value)) {
              if (entity[key][subKey] !== void 0) {
                entity[key][subKey] = new Cesium.ConstantProperty(subValue);
              }
            }
          } else {
            entity[key] = new Cesium.ConstantProperty(value);
          }
          log(PREFIX, `Updated entity ${key}:`, value);
        } catch (err) {
          warn(PREFIX, `Failed to update property ${key}:`, err);
        }
      }
    }
    requestSceneRender();
  });
  function getCameraState() {
    if (!viewer || !viewer.camera || !viewer.camera.positionCartographic) {
      warn(PREFIX, "Cannot get camera state - viewer or camera not available");
      return null;
    }
    try {
      const cartographic = viewer.camera.positionCartographic;
      return {
        latitude: Cesium.Math.toDegrees(cartographic.latitude),
        longitude: Cesium.Math.toDegrees(cartographic.longitude),
        altitude: cartographic.height,
        heading: Cesium.Math.toDegrees(viewer.camera.heading),
        pitch: Cesium.Math.toDegrees(viewer.camera.pitch),
        roll: Cesium.Math.toDegrees(viewer.camera.roll)
      };
    } catch (error2) {
      warn(PREFIX, "Error getting camera state:", error2);
      return null;
    }
  }
  function getClockState() {
    if (!viewer || !viewer.clock) return null;
    try {
      return {
        current_time: Cesium.JulianDate.toIso8601(viewer.clock.currentTime),
        multiplier: viewer.clock.multiplier,
        is_animating: viewer.clock.shouldAnimate
      };
    } catch (error2) {
      warn(PREFIX, "Error getting clock state:", error2);
      return null;
    }
  }
  function getPickedPosition(click) {
    if (!viewer || !viewer.camera || !viewer.scene || !viewer.scene.globe) {
      return null;
    }
    try {
      const ray = viewer.camera.getPickRay(click.position);
      if (!ray) return null;
      const cartesian = viewer.scene.globe.pick(ray, viewer.scene);
      if (!cartesian) return null;
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      return {
        latitude: Cesium.Math.toDegrees(cartographic.latitude),
        longitude: Cesium.Math.toDegrees(cartographic.longitude),
        altitude: cartographic.height
      };
    } catch (error2) {
      warn(PREFIX, "Error picking position:", error2);
      return null;
    }
  }
  function getPickedEntity(click) {
    if (!viewer || !viewer.scene || !viewer.clock) {
      return null;
    }
    try {
      const pickedObject = viewer.scene.pick(click.position);
      if (!Cesium.defined(pickedObject) || !Cesium.defined(pickedObject.id)) {
        return null;
      }
      const entity = pickedObject.id;
      const entityData = {
        id: entity.id,
        name: entity.name || null
      };
      if (entity.properties) {
        const props = {};
        const propertyNames = entity.properties.propertyNames;
        if (propertyNames && propertyNames.length > 0) {
          propertyNames.forEach((name) => {
            try {
              props[name] = entity.properties[name].getValue(viewer.clock.currentTime);
            } catch (e) {
            }
          });
          if (Object.keys(props).length > 0) {
            entityData.properties = props;
          }
        }
      }
      return entityData;
    } catch (error2) {
      warn(PREFIX, "Error picking entity:", error2);
      return null;
    }
  }
  function sendInteractionEvent(type, additionalData = {}) {
    if (isDestroyed) {
      log(PREFIX, "Skipping interaction event - destroyed:", type);
      return;
    }
    if (!viewer) {
      warn(PREFIX, "Cannot send interaction event - viewer not available");
      return;
    }
    const cameraState = getCameraState();
    if (!cameraState) {
      warn(PREFIX, "Skipping interaction event - camera state not available");
      return;
    }
    const event = {
      type,
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      camera: cameraState,
      clock: getClockState(),
      ...additionalData
    };
    log(PREFIX, "Interaction event:", type, event);
    model.set("interaction_event", event);
    model.save_changes();
  }
  const camera = viewer.camera;
  camera.moveEnd.addEventListener(() => {
    if (isDestroyed || !viewer) return;
    sendInteractionEvent("camera_move");
  });
  const scene = viewer.scene;
  const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
  handler.setInputAction((click) => {
    if (isDestroyed || !viewer || !viewer.scene || !viewer.camera) return;
    const pickedData = {};
    const pickedPosition = getPickedPosition(click);
    if (pickedPosition) {
      pickedData.picked_position = pickedPosition;
    }
    const pickedEntity = getPickedEntity(click);
    if (pickedEntity) {
      pickedData.picked_entity = pickedEntity;
    }
    sendInteractionEvent("left_click", pickedData);
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  handler.setInputAction((click) => {
    if (isDestroyed || !viewer || !viewer.scene || !viewer.camera) return;
    const pickedData = {};
    const pickedPosition = getPickedPosition(click);
    if (pickedPosition) {
      pickedData.picked_position = pickedPosition;
    }
    sendInteractionEvent("right_click", pickedData);
  }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
  if (viewer.timeline) {
    let timelineScrubbing = false;
    let lastTimelineTime = viewer.clock?.currentTime ? Cesium.JulianDate.clone(viewer.clock.currentTime) : null;
    viewer.clock.onTick.addEventListener(() => {
      if (isDestroyed) return;
      const isAnimating = viewer.clock.shouldAnimate === true;
      applyAnimationRenderPolicy(isAnimating);
      if (viewer.timeline) {
        if (isAnimating) {
          if (timelineScrubbing) {
            timelineScrubbing = false;
          }
          if (scrubTimeout) {
            clearTimeout(scrubTimeout);
            scrubTimeout = null;
          }
          return;
        }
        if (!isAnimating && viewer.clock.currentTime) {
          if (!lastTimelineTime || !Cesium.JulianDate.equals(viewer.clock.currentTime, lastTimelineTime)) {
            requestSceneRender();
            lastTimelineTime = Cesium.JulianDate.clone(viewer.clock.currentTime);
          }
        }
        if (scrubTimeout) {
          clearTimeout(scrubTimeout);
          scrubTimeout = null;
        }
        scrubTimeout = setTimeout(() => {
          if (!isDestroyed && timelineScrubbing) {
            timelineScrubbing = false;
            sendInteractionEvent("timeline_scrub");
          }
        }, CONSTANTS.TIMELINE_SCRUB_DEBOUNCE_MS);
        timelineScrubbing = true;
      }
    });
  }
  log(PREFIX, "Viewer listeners setup complete");
  return {
    destroy: () => {
      log(PREFIX, "Destroying viewer listeners");
      isDestroyed = true;
      if (scrubTimeout) {
        clearTimeout(scrubTimeout);
        scrubTimeout = null;
      }
      if (handler) {
        handler.destroy();
      }
      log(PREFIX, "Viewer listeners destroyed");
    }
  };
}
function setupGeoJSONLoader(viewer, model, Cesium) {
  log(PREFIX, "Setting up GeoJSON loader");
  let geojsonDataSources = [];
  let isDestroyed = false;
  async function loadGeoJSONData(flyToData = true) {
    if (isDestroyed) {
      log(PREFIX, "Skipping geojson_data load - destroyed");
      return;
    }
    if (!viewer || !viewer.dataSources) {
      warn(PREFIX, "Cannot load GeoJSON - viewer or dataSources not available");
      return;
    }
    const geojsonDataArray = model.get("geojson_data");
    log(PREFIX, "Loading GeoJSON data, count:", geojsonDataArray?.length || 0);
    geojsonDataSources.forEach((dataSource) => {
      if (viewer && viewer.dataSources) {
        viewer.dataSources.remove(dataSource);
      }
    });
    geojsonDataSources = [];
    if (geojsonDataArray && Array.isArray(geojsonDataArray)) {
      for (const geojsonData of geojsonDataArray) {
        try {
          log(PREFIX, "Loading GeoJSON dataset...");
          const dataSource = await Cesium.GeoJsonDataSource.load(geojsonData, {
            stroke: Cesium.Color.HOTPINK,
            fill: Cesium.Color.PINK.withAlpha(CONSTANTS.GEOJSON_FILL_ALPHA),
            strokeWidth: CONSTANTS.GEOJSON_STROKE_WIDTH
          });
          if (!isDestroyed && viewer && viewer.dataSources) {
            viewer.dataSources.add(dataSource);
            geojsonDataSources.push(dataSource);
            log(PREFIX, "GeoJSON dataset loaded successfully");
          }
        } catch (error2) {
          error2(PREFIX, "Error loading GeoJSON:", error2);
        }
      }
      if (flyToData && geojsonDataSources.length > 0 && viewer && viewer.flyTo) {
        log(PREFIX, "Flying to GeoJSON data");
        viewer.flyTo(geojsonDataSources[0]);
      }
    }
  }
  model.on("change:geojson_data", () => loadGeoJSONData(true));
  const initialData = model.get("geojson_data");
  if (initialData && Array.isArray(initialData) && initialData.length > 0) {
    log(PREFIX, "Loading initial GeoJSON data...");
    loadGeoJSONData(true);
  }
  return {
    destroy: () => {
      log(PREFIX, "Destroying GeoJSON loader");
      isDestroyed = true;
      geojsonDataSources.forEach((dataSource) => {
        if (viewer) {
          viewer.dataSources.remove(dataSource);
        }
      });
      geojsonDataSources = [];
    }
  };
}
function setupCZMLLoader(viewer, model, Cesium) {
  log(PREFIX, "Setting up CZML loader");
  let czmlDataSources = [];
  let isDestroyed = false;
  viewer._czmlDataSources = czmlDataSources;
  async function loadCZMLData(flyToData = true) {
    if (isDestroyed) {
      log(PREFIX, "Skipping czml_data load - destroyed");
      return;
    }
    if (!viewer || !viewer.dataSources) {
      warn(PREFIX, "Cannot load CZML - viewer or dataSources not available");
      return;
    }
    const czmlDataArray = model.get("czml_data");
    log(PREFIX, "Loading CZML data, count:", czmlDataArray?.length || 0);
    czmlDataSources.forEach((dataSource) => {
      if (viewer && viewer.dataSources) {
        viewer.dataSources.remove(dataSource);
      }
    });
    czmlDataSources = [];
    if (czmlDataArray && Array.isArray(czmlDataArray)) {
      for (const czmlData of czmlDataArray) {
        if (Array.isArray(czmlData) && czmlData.length > 0) {
          try {
            log(PREFIX, "Loading CZML document with", czmlData.length, "packets...");
            const dataSource = await Cesium.CzmlDataSource.load(czmlData);
            if (!isDestroyed && viewer && viewer.dataSources) {
              viewer.dataSources.add(dataSource);
              czmlDataSources.push(dataSource);
              log(PREFIX, "CZML document loaded successfully, entities:", dataSource.entities.values.length);
            }
          } catch (error2) {
            error2(PREFIX, "Error loading CZML:", error2);
          }
        } else {
          warn(PREFIX, "Skipping invalid CZML data (not an array or empty):", czmlData);
        }
      }
      if (flyToData && czmlDataSources.length > 0 && viewer && viewer.flyTo) {
        log(PREFIX, "Flying to CZML data");
        viewer.flyTo(czmlDataSources[0]);
      }
    }
  }
  model.on("change:czml_data", () => loadCZMLData(true));
  const initialData = model.get("czml_data");
  if (initialData && Array.isArray(initialData) && initialData.length > 0) {
    log(PREFIX, "Loading initial CZML data...");
    loadCZMLData(true);
  }
  return {
    destroy: () => {
      log(PREFIX, "Destroying CZML loader");
      isDestroyed = true;
      czmlDataSources.forEach((dataSource) => {
        if (viewer) {
          viewer.dataSources.remove(dataSource);
        }
      });
      czmlDataSources = [];
    }
  };
}
function setupPhotorealisticTiles(viewer, model, Cesium) {
  log(PREFIX, "Setting up Photorealistic 3D Tiles loader");
  let photorealisticTileset = null;
  let isDestroyed = false;
  async function updatePhotorealisticTiles() {
    if (isDestroyed) {
      log(PREFIX, "Skipping photorealistic tiles update - destroyed");
      return;
    }
    if (!viewer || !viewer.scene || !viewer.scene.primitives) {
      warn(PREFIX, "Cannot update photorealistic tiles - viewer not available");
      return;
    }
    const enabled = model.get("enable_photorealistic_tiles");
    log(PREFIX, "Photorealistic tiles enabled:", enabled);
    if (photorealisticTileset) {
      log(PREFIX, "Removing existing photorealistic tileset");
      viewer.scene.primitives.remove(photorealisticTileset);
      photorealisticTileset = null;
    }
    if (enabled) {
      try {
        log(PREFIX, "Creating Google Photorealistic 3D Tileset...");
        photorealisticTileset = await Cesium.createGooglePhotorealistic3DTileset();
        if (viewer && viewer.scene && viewer.scene.primitives && !isDestroyed) {
          viewer.scene.primitives.add(photorealisticTileset);
          log(PREFIX, "Google Photorealistic 3D Tileset loaded successfully");
        } else {
          log(PREFIX, "Viewer destroyed during tileset load, skipping add");
          photorealisticTileset = null;
        }
      } catch (err) {
        error(PREFIX, "Failed to load Google Photorealistic 3D Tileset:", err);
        photorealisticTileset = null;
      }
    }
  }
  model.on("change:enable_photorealistic_tiles", () => updatePhotorealisticTiles());
  if (model.get("enable_photorealistic_tiles")) {
    log(PREFIX, "Loading initial photorealistic tiles...");
    updatePhotorealisticTiles();
  }
  return {
    destroy: () => {
      log(PREFIX, "Destroying photorealistic tiles loader");
      isDestroyed = true;
      if (photorealisticTileset && viewer && viewer.scene && viewer.scene.primitives) {
        viewer.scene.primitives.remove(photorealisticTileset);
      }
      photorealisticTileset = null;
    }
  };
}

// src/cesiumjs_anywidget/js/camera-sync.js
var PREFIX2 = "CameraSync";
function initializeCameraSync(viewer, model) {
  const Cesium = window.Cesium;
  let cameraUpdateTimeout = null;
  let modelUpdateTimeout = null;
  let isDestroyed = false;
  let syncEnabled = model.get("camera_sync_enabled") || false;
  log(PREFIX2, "Initializing camera synchronization, sync enabled:", syncEnabled);
  if (!Cesium) {
    error(PREFIX2, "Cesium global not available");
    throw new Error("Cesium is not loaded");
  }
  const handleSyncEnabledChange = () => {
    syncEnabled = model.get("camera_sync_enabled");
    log(PREFIX2, "Camera sync enabled changed:", syncEnabled);
  };
  model.on("change:camera_sync_enabled", handleSyncEnabledChange);
  function validateCameraParameters(lat, lon, alt) {
    if (typeof lat !== "number" || isNaN(lat) || lat < -90 || lat > 90) {
      warn(PREFIX2, "Invalid latitude:", lat);
      return false;
    }
    if (typeof lon !== "number" || isNaN(lon)) {
      warn(PREFIX2, "Invalid longitude:", lon);
      return false;
    }
    if (typeof alt !== "number" || isNaN(alt)) {
      warn(PREFIX2, "Invalid altitude:", alt);
      return false;
    }
    return true;
  }
  function validateOrientationAngles(heading, pitch, roll) {
    if (typeof heading !== "number" || isNaN(heading)) {
      warn(PREFIX2, "Invalid heading:", heading);
      return false;
    }
    if (typeof pitch !== "number" || isNaN(pitch)) {
      warn(PREFIX2, "Invalid pitch:", pitch);
      return false;
    }
    if (typeof roll !== "number" || isNaN(roll)) {
      warn(PREFIX2, "Invalid roll:", roll);
      return false;
    }
    return true;
  }
  function updateCameraFromModelImmediate() {
    if (isDestroyed) {
      log(PREFIX2, "Skipping updateCameraFromModel - module destroyed");
      return;
    }
    if (!viewer) {
      warn(PREFIX2, "updateCameraFromModel called but viewer is null");
      return;
    }
    const lat = model.get("latitude");
    const lon = model.get("longitude");
    const alt = model.get("altitude");
    const heading = model.get("heading");
    const pitch = model.get("pitch");
    const roll = model.get("roll");
    if (!validateCameraParameters(lat, lon, alt)) {
      return;
    }
    if (!validateOrientationAngles(heading, pitch, roll)) {
      return;
    }
    log(PREFIX2, "Updating camera from model:", { lat, lon, alt });
    try {
      viewer.camera.setView({
        destination: Cesium.Cartesian3.fromDegrees(lon, lat, alt),
        orientation: {
          heading: Cesium.Math.toRadians(heading),
          pitch: Cesium.Math.toRadians(pitch),
          roll: Cesium.Math.toRadians(roll)
        }
      });
    } catch (err) {
      error(PREFIX2, "Error updating camera from model:", err);
    }
  }
  function updateCameraFromModel() {
    if (modelUpdateTimeout) {
      clearTimeout(modelUpdateTimeout);
    }
    modelUpdateTimeout = setTimeout(() => {
      if (!isDestroyed) {
        updateCameraFromModelImmediate();
      }
    }, 50);
  }
  function updateModelFromCamera() {
    if (isDestroyed) {
      log(PREFIX2, "Skipping updateModelFromCamera - module destroyed");
      return;
    }
    if (!syncEnabled) {
      log(PREFIX2, "Skipping updateModelFromCamera - sync disabled");
      return;
    }
    if (!viewer) {
      warn(PREFIX2, "updateModelFromCamera called but viewer is null");
      return;
    }
    const position = viewer.camera.positionCartographic;
    const heading = viewer.camera.heading;
    const pitch = viewer.camera.pitch;
    const roll = viewer.camera.roll;
    log(PREFIX2, "Updating model from camera:", {
      lat: Cesium.Math.toDegrees(position.latitude),
      lon: Cesium.Math.toDegrees(position.longitude),
      alt: position.height
    });
    model.set("latitude", Cesium.Math.toDegrees(position.latitude));
    model.set("longitude", Cesium.Math.toDegrees(position.longitude));
    model.set("altitude", position.height);
    model.set("heading", Cesium.Math.toDegrees(heading));
    model.set("pitch", Cesium.Math.toDegrees(pitch));
    model.set("roll", Cesium.Math.toDegrees(roll));
    model.save_changes();
  }
  function handleCameraChanged() {
    if (isDestroyed) {
      log(PREFIX2, "Skipping handleCameraChanged - module destroyed");
      return;
    }
    if (!syncEnabled) {
      return;
    }
    if (cameraUpdateTimeout) {
      clearTimeout(cameraUpdateTimeout);
    }
    cameraUpdateTimeout = setTimeout(() => {
      if (!isDestroyed && syncEnabled) {
        updateModelFromCamera();
      }
    }, 500);
  }
  updateCameraFromModelImmediate();
  viewer.camera.changed.addEventListener(handleCameraChanged);
  model.on("change:latitude", updateCameraFromModel);
  model.on("change:longitude", updateCameraFromModel);
  model.on("change:altitude", updateCameraFromModel);
  model.on("change:heading", updateCameraFromModel);
  model.on("change:pitch", updateCameraFromModel);
  model.on("change:roll", updateCameraFromModel);
  const handleCameraCommand = () => {
    if (isDestroyed) {
      log(PREFIX2, "Skipping camera_command - module destroyed");
      return;
    }
    const command = model.get("camera_command");
    if (!command || !command.command || !command.timestamp) return;
    const cmd = command.command;
    log(PREFIX2, "Executing camera command:", cmd, command);
    try {
      switch (cmd) {
        case "flyTo":
          if (!validateCameraParameters(command.latitude, command.longitude, command.altitude)) {
            error(PREFIX2, "Invalid parameters for flyTo command");
            return;
          }
          viewer.camera.flyTo({
            destination: Cesium.Cartesian3.fromDegrees(
              command.longitude,
              command.latitude,
              command.altitude
            ),
            orientation: {
              heading: Cesium.Math.toRadians(command.heading || 0),
              pitch: Cesium.Math.toRadians(command.pitch || -15),
              roll: Cesium.Math.toRadians(command.roll || 0)
            },
            duration: command.duration || 3
          });
          break;
        case "setView":
          if (!validateCameraParameters(command.latitude, command.longitude, command.altitude)) {
            error(PREFIX2, "Invalid parameters for setView command");
            return;
          }
          viewer.camera.setView({
            destination: Cesium.Cartesian3.fromDegrees(
              command.longitude,
              command.latitude,
              command.altitude
            ),
            orientation: {
              heading: Cesium.Math.toRadians(command.heading || 0),
              pitch: Cesium.Math.toRadians(command.pitch || -15),
              roll: Cesium.Math.toRadians(command.roll || 0)
            }
          });
          if (command.fov !== void 0 || command.aspectRatio !== void 0 || command.near !== void 0 || command.far !== void 0) {
            const frustum = viewer.camera.frustum;
            if (frustum instanceof Cesium.PerspectiveFrustum) {
              if (command.fov !== void 0) {
                frustum.fov = Cesium.Math.toRadians(command.fov);
              }
              if (command.aspectRatio !== void 0) {
                frustum.aspectRatio = command.aspectRatio;
              }
              if (command.near !== void 0) {
                frustum.near = command.near;
              }
              if (command.far !== void 0) {
                frustum.far = command.far;
              }
              log(PREFIX2, "Applied frustum parameters:", {
                fov: command.fov,
                aspectRatio: command.aspectRatio,
                near: command.near,
                far: command.far
              });
            }
          }
          break;
        case "lookAt":
          if (!validateCameraParameters(command.targetLatitude, command.targetLongitude, command.targetAltitude || 0)) {
            error(PREFIX2, "Invalid parameters for lookAt command");
            return;
          }
          const target = Cesium.Cartesian3.fromDegrees(
            command.targetLongitude,
            command.targetLatitude,
            command.targetAltitude || 0
          );
          const offset = new Cesium.HeadingPitchRange(
            Cesium.Math.toRadians(command.offsetHeading || 0),
            Cesium.Math.toRadians(command.offsetPitch || -45),
            command.offsetRange || 1e3
          );
          viewer.camera.lookAt(target, offset);
          viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
          break;
        case "moveForward":
          viewer.camera.moveForward(command.distance || 100);
          break;
        case "moveBackward":
          viewer.camera.moveBackward(command.distance || 100);
          break;
        case "moveUp":
          viewer.camera.moveUp(command.distance || 100);
          break;
        case "moveDown":
          viewer.camera.moveDown(command.distance || 100);
          break;
        case "moveLeft":
          viewer.camera.moveLeft(command.distance || 100);
          break;
        case "moveRight":
          viewer.camera.moveRight(command.distance || 100);
          break;
        case "rotateLeft":
          viewer.camera.rotateLeft(Cesium.Math.toRadians(command.angle || 15));
          break;
        case "rotateRight":
          viewer.camera.rotateRight(Cesium.Math.toRadians(command.angle || 15));
          break;
        case "rotateUp":
          viewer.camera.rotateUp(Cesium.Math.toRadians(command.angle || 15));
          break;
        case "rotateDown":
          viewer.camera.rotateDown(Cesium.Math.toRadians(command.angle || 15));
          break;
        case "zoomIn":
          viewer.camera.zoomIn(command.distance || 100);
          break;
        case "zoomOut":
          viewer.camera.zoomOut(command.distance || 100);
          break;
        default:
          warn(PREFIX2, `Unknown camera command: ${cmd}`);
      }
    } catch (err) {
      error(PREFIX2, `Error executing camera command ${cmd}:`, err);
    }
  };
  model.on("change:camera_command", handleCameraCommand);
  return {
    updateCameraFromModel,
    updateCameraFromModelImmediate,
    updateModelFromCamera,
    destroy: () => {
      log(PREFIX2, "Destroying camera sync module");
      isDestroyed = true;
      if (cameraUpdateTimeout) {
        clearTimeout(cameraUpdateTimeout);
        cameraUpdateTimeout = null;
      }
      if (modelUpdateTimeout) {
        clearTimeout(modelUpdateTimeout);
        modelUpdateTimeout = null;
      }
      if (viewer && viewer.camera) {
        viewer.camera.changed.removeEventListener(handleCameraChanged);
      }
      model.off("change:camera_sync_enabled", handleSyncEnabledChange);
      model.off("change:latitude", updateCameraFromModel);
      model.off("change:longitude", updateCameraFromModel);
      model.off("change:altitude", updateCameraFromModel);
      model.off("change:heading", updateCameraFromModel);
      model.off("change:pitch", updateCameraFromModel);
      model.off("change:roll", updateCameraFromModel);
      model.off("change:camera_command", handleCameraCommand);
      log(PREFIX2, "Camera sync module destroyed");
    }
  };
}

// src/cesiumjs_anywidget/js/measurement-tools.js
var PREFIX3 = "Measurements";
var CONSTANTS2 = {
  // UI Dimensions
  MARKER_SIZE: 10,
  MARKER_SIZE_HOVER: 12,
  MARKER_SIZE_SELECTED: 15,
  MARKER_OUTLINE_WIDTH: 2,
  MARKER_OUTLINE_WIDTH_HOVER: 3,
  MARKER_OUTLINE_WIDTH_SELECTED: 4,
  // Panel Dimensions
  PANEL_MIN_WIDTH: 250,
  PANEL_MIN_HEIGHT: 200,
  PANEL_MAX_WIDTH: 600,
  PANEL_MAX_HEIGHT: 800,
  // Measurement Thresholds
  MAX_ADD_POINT_DISTANCE_METERS: 50,
  AREA_UNIT_THRESHOLD: 1e6,
  // m² to km² conversion
  DISTANCE_UNIT_THRESHOLD: 1e3,
  // m to km conversion
  // Visual
  POLYLINE_WIDTH: 3,
  LABEL_PIXEL_OFFSET_Y: -20,
  POLYGON_ALPHA: 0.3,
  POLYGON_OUTLINE_WIDTH: 2
};
function getColors() {
  const Cesium = window.Cesium;
  return {
    distance: { main: Cesium.Color.RED, button: "#e74c3c" },
    "multi-distance": { main: Cesium.Color.BLUE, button: "#3498db" },
    height: { main: Cesium.Color.GREEN, button: "#2ecc71" },
    area: { main: Cesium.Color.ORANGE, button: "#e67e22" }
  };
}
var TYPE_LABELS = {
  distance: "Distance",
  "multi-distance": "Multi-Distance",
  height: "Height",
  area: "Area"
};
function calculateGeodesicArea(positions) {
  const Cesium = window.Cesium;
  const polygonHierarchy = new Cesium.PolygonHierarchy(positions);
  const geometry = Cesium.PolygonGeometry.createGeometry(
    new Cesium.PolygonGeometry({
      polygonHierarchy,
      perPositionHeight: false,
      arcType: Cesium.ArcType.GEODESIC
    })
  );
  if (!geometry) return 0;
  const positionsArray = geometry.attributes.position.values;
  const indices = geometry.indices;
  let area = 0;
  for (let i = 0; i < indices.length; i += 3) {
    const i0 = indices[i] * 3;
    const i1 = indices[i + 1] * 3;
    const i2 = indices[i + 2] * 3;
    const v0 = new Cesium.Cartesian3(positionsArray[i0], positionsArray[i0 + 1], positionsArray[i0 + 2]);
    const v1 = new Cesium.Cartesian3(positionsArray[i1], positionsArray[i1 + 1], positionsArray[i1 + 2]);
    const v2 = new Cesium.Cartesian3(positionsArray[i2], positionsArray[i2 + 1], positionsArray[i2 + 2]);
    const edge1 = Cesium.Cartesian3.subtract(v1, v0, new Cesium.Cartesian3());
    const edge2 = Cesium.Cartesian3.subtract(v2, v0, new Cesium.Cartesian3());
    const crossProduct = Cesium.Cartesian3.cross(edge1, edge2, new Cesium.Cartesian3());
    area += Cesium.Cartesian3.magnitude(crossProduct) / 2;
  }
  return area;
}
function calculateCentroid(positions) {
  const Cesium = window.Cesium;
  let centroidLon = 0, centroidLat = 0;
  positions.forEach((pos) => {
    const carto = Cesium.Cartographic.fromCartesian(pos);
    centroidLon += carto.longitude;
    centroidLat += carto.latitude;
  });
  return new Cesium.Cartographic(centroidLon / positions.length, centroidLat / positions.length);
}
function formatValue(value, isArea = false) {
  if (isArea) {
    return value >= CONSTANTS2.AREA_UNIT_THRESHOLD ? `${(value / CONSTANTS2.AREA_UNIT_THRESHOLD).toFixed(2)} km\xB2` : `${value.toFixed(2)} m\xB2`;
  }
  return value >= CONSTANTS2.DISTANCE_UNIT_THRESHOLD ? `${(value / CONSTANTS2.DISTANCE_UNIT_THRESHOLD).toFixed(2)} km` : `${value.toFixed(2)} m`;
}
function createStyledButton(text, baseColor, activeColor = "#e74c3c") {
  const btn = document.createElement("button");
  btn.textContent = text;
  btn.dataset.baseColor = baseColor;
  btn.dataset.activeColor = activeColor;
  btn.style.cssText = `
    padding: 8px 12px;
    background: ${baseColor};
    color: white;
    border: none;
    border-radius: 3px;
    cursor: pointer;
    font-size: 12px;
    transition: background 0.2s;
  `;
  return btn;
}
function makeDraggable(panel, handle) {
  let isDragging = false;
  let startX, startY, initialLeft, initialTop;
  handle.style.cursor = "move";
  const handleMouseDown = (e) => {
    if (e.button !== 0 || e.target.tagName === "BUTTON" || e.target.tagName === "INPUT") return;
    isDragging = true;
    startX = e.clientX;
    startY = e.clientY;
    const rect = panel.getBoundingClientRect();
    const parentRect = panel.parentElement.getBoundingClientRect();
    initialLeft = rect.left - parentRect.left;
    initialTop = rect.top - parentRect.top;
    panel.style.right = "auto";
    panel.style.bottom = "auto";
    panel.style.left = initialLeft + "px";
    panel.style.top = initialTop + "px";
    e.preventDefault();
  };
  const handleMouseMove = (e) => {
    if (!isDragging) return;
    const deltaX = e.clientX - startX;
    const deltaY = e.clientY - startY;
    const parentRect = panel.parentElement.getBoundingClientRect();
    const panelRect = panel.getBoundingClientRect();
    let newLeft = initialLeft + deltaX;
    let newTop = initialTop + deltaY;
    newLeft = Math.max(0, Math.min(newLeft, parentRect.width - panelRect.width));
    newTop = Math.max(0, Math.min(newTop, parentRect.height - panelRect.height));
    panel.style.left = newLeft + "px";
    panel.style.top = newTop + "px";
  };
  const handleMouseUp = () => {
    isDragging = false;
  };
  handle.addEventListener("mousedown", handleMouseDown);
  document.addEventListener("mousemove", handleMouseMove);
  document.addEventListener("mouseup", handleMouseUp);
  return () => {
    handle.removeEventListener("mousedown", handleMouseDown);
    document.removeEventListener("mousemove", handleMouseMove);
    document.removeEventListener("mouseup", handleMouseUp);
  };
}
function makeResizable(panel, minWidth = CONSTANTS2.PANEL_MIN_WIDTH, minHeight = CONSTANTS2.PANEL_MIN_HEIGHT, maxWidth = CONSTANTS2.PANEL_MAX_WIDTH, maxHeight = CONSTANTS2.PANEL_MAX_HEIGHT) {
  const resizeHandle = document.createElement("div");
  resizeHandle.style.cssText = `
    position: absolute;
    bottom: 2px;
    right: 2px;
    width: 20px;
    height: 20px;
    cursor: nwse-resize;
    z-index: 1002;
  `;
  resizeHandle.innerHTML = `
    <svg width="20" height="20" viewBox="0 0 20 20" style="pointer-events: none;">
      <path d="M 20 0 L 20 20 L 0 20" fill="none" stroke="rgba(255,255,255,0.3)" stroke-width="1"/>
      <path d="M 14 20 L 20 14" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
      <path d="M 8 20 L 20 8" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
    </svg>
  `;
  panel.appendChild(resizeHandle);
  let isResizing = false;
  let startX, startY, startWidth, startHeight;
  const handleMouseDown = (e) => {
    isResizing = true;
    startX = e.clientX;
    startY = e.clientY;
    const rect = panel.getBoundingClientRect();
    startWidth = rect.width;
    startHeight = rect.height;
    e.preventDefault();
    e.stopPropagation();
  };
  const handleMouseMove = (e) => {
    if (!isResizing) return;
    const deltaX = e.clientX - startX;
    const deltaY = e.clientY - startY;
    let newWidth = startWidth + deltaX;
    let newHeight = startHeight + deltaY;
    newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth));
    newHeight = Math.max(minHeight, Math.min(newHeight, maxHeight));
    panel.style.width = newWidth + "px";
    panel.style.height = newHeight + "px";
    panel.style.maxWidth = "none";
    panel.style.maxHeight = "none";
  };
  const handleMouseUp = () => {
    isResizing = false;
  };
  resizeHandle.addEventListener("mousedown", handleMouseDown);
  document.addEventListener("mousemove", handleMouseMove);
  document.addEventListener("mouseup", handleMouseUp);
  return () => {
    resizeHandle.removeEventListener("mousedown", handleMouseDown);
    document.removeEventListener("mousemove", handleMouseMove);
    document.removeEventListener("mouseup", handleMouseUp);
    if (resizeHandle.parentNode) {
      resizeHandle.remove();
    }
  };
}
function initializeMeasurementTools(viewer, model, container) {
  log(PREFIX3, "Initializing measurement tools");
  const Cesium = window.Cesium;
  let measurementHandler = null;
  let editHandler = null;
  let isDestroyed = false;
  let measurementState = {
    mode: null,
    points: [],
    entities: [],
    labels: [],
    polylines: [],
    polyline: null,
    tempPolyline: null
  };
  let editState = {
    enabled: false,
    selectedPoint: null,
    selectedEntity: null,
    dragging: false,
    measurementIndex: null,
    pointIndex: null,
    addPointMode: false
  };
  const cleanupFunctions = [];
  const toolbarDiv = document.createElement("div");
  toolbarDiv.style.cssText = `
    position: absolute;
    top: 10px;
    left: 10px;
    background: rgba(42, 42, 42, 0.9);
    padding: 0;
    border-radius: 5px;
    z-index: 1000;
    display: flex;
    flex-direction: column;
    gap: 0;
  `;
  container.appendChild(toolbarDiv);
  const toolbarHeader = document.createElement("div");
  toolbarHeader.style.cssText = `
    padding: 8px 10px;
    background: rgba(60, 60, 60, 0.95);
    border-radius: 5px 5px 0 0;
    font-family: sans-serif;
    font-size: 11px;
    color: #aaa;
    border-bottom: 1px solid #555;
    user-select: none;
  `;
  toolbarHeader.textContent = "\u22EE\u22EE Measurement Tools";
  toolbarDiv.appendChild(toolbarHeader);
  const toolbarContent = document.createElement("div");
  toolbarContent.style.cssText = `
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 5px;
  `;
  toolbarDiv.appendChild(toolbarContent);
  cleanupFunctions.push(makeDraggable(toolbarDiv, toolbarHeader));
  function setupButtonHover(btn, hoverColor, getActiveState = () => false) {
    const baseColor = btn.dataset.baseColor;
    btn.onmouseover = () => {
      btn.style.background = hoverColor;
    };
    btn.onmouseout = () => {
      btn.style.background = getActiveState() ? btn.dataset.activeColor : baseColor;
    };
  }
  function createMeasurementButton(text, mode) {
    const btn = createStyledButton(text, "#3498db");
    setupButtonHover(btn, "#2980b9", () => measurementState.mode === mode);
    btn.onclick = () => {
      model.set("measurement_mode", measurementState.mode === mode ? "" : mode);
      model.save_changes();
    };
    return btn;
  }
  const modeButtons = {
    distance: createMeasurementButton("\u{1F4CF} Distance", "distance"),
    "multi-distance": createMeasurementButton("\u{1F4D0} Multi Distance", "multi-distance"),
    height: createMeasurementButton("\u{1F4CA} Height", "height"),
    area: createMeasurementButton("\u2B1B Area", "area")
  };
  const clearBtn = createStyledButton("\u{1F5D1}\uFE0F Clear", "#e74c3c");
  setupButtonHover(clearBtn, "#c0392b");
  clearBtn.onclick = () => {
    clearAllMeasurements();
    model.set("measurement_mode", "");
    model.set("measurement_results", []);
    model.save_changes();
  };
  Object.values(modeButtons).forEach((btn) => toolbarContent.appendChild(btn));
  toolbarContent.appendChild(clearBtn);
  const editBtn = createStyledButton("\u270F\uFE0F Edit Points", "#9b59b6");
  setupButtonHover(editBtn, "#8e44ad", () => editState.enabled);
  editBtn.onclick = () => {
    editState.enabled = !editState.enabled;
    editBtn.style.background = editState.enabled ? "#e74c3c" : "#9b59b6";
    editState.enabled ? enableEditMode() : disableEditMode();
  };
  toolbarContent.appendChild(editBtn);
  const addPointBtn = createStyledButton("\u2795 Add Point", "#16a085");
  addPointBtn.style.display = "none";
  setupButtonHover(addPointBtn, "#138d75", () => editState.addPointMode);
  addPointBtn.onclick = () => {
    editState.addPointMode = !editState.addPointMode;
    addPointBtn.style.background = editState.addPointMode ? "#e74c3c" : "#16a085";
    if (editState.addPointMode) {
      deselectPoint();
    }
  };
  toolbarContent.appendChild(addPointBtn);
  const editorPanel = document.createElement("div");
  editorPanel.style.cssText = `
    position: absolute;
    top: 10px;
    right: 10px;
    background: rgba(42, 42, 42, 0.95);
    padding: 0;
    border-radius: 5px;
    z-index: 1000;
    display: none;
    color: white;
    font-family: sans-serif;
    font-size: 12px;
    min-width: 250px;
  `;
  const editorHeader = document.createElement("div");
  editorHeader.style.cssText = `
    padding: 10px 15px;
    background: rgba(60, 60, 60, 0.95);
    border-radius: 5px 5px 0 0;
    font-weight: bold;
    border-bottom: 1px solid #555;
    user-select: none;
    cursor: move;
  `;
  editorHeader.textContent = "\u22EE\u22EE Edit Point";
  editorPanel.appendChild(editorHeader);
  const editorContent = document.createElement("div");
  editorContent.style.cssText = `padding: 15px;`;
  editorPanel.appendChild(editorContent);
  container.appendChild(editorPanel);
  cleanupFunctions.push(makeDraggable(editorPanel, editorHeader));
  const measurementsListPanel = document.createElement("div");
  measurementsListPanel.style.cssText = `
    position: absolute;
    bottom: 10px;
    right: 10px;
    background: rgba(42, 42, 42, 0.95);
    padding: 0;
    border-radius: 5px;
    z-index: 1000;
    color: white;
    font-family: sans-serif;
    font-size: 12px;
    width: 350px;
    height: 400px;
    min-width: 250px;
    min-height: 200px;
    max-width: 600px;
    max-height: 800px;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    overflow: hidden;
  `;
  const measurementsHeader = document.createElement("div");
  measurementsHeader.style.cssText = `
    padding: 10px 15px;
    background: rgba(60, 60, 60, 0.95);
    border-radius: 5px 5px 0 0;
    font-weight: bold;
    border-bottom: 1px solid #555;
    user-select: none;
    cursor: move;
    flex-shrink: 0;
    box-sizing: border-box;
  `;
  measurementsHeader.textContent = "\u22EE\u22EE Measurements";
  measurementsListPanel.appendChild(measurementsHeader);
  const measurementsContent = document.createElement("div");
  measurementsContent.className = "measurement-panel-scrollable";
  measurementsContent.style.cssText = `
    padding: 10px 15px 25px 15px;
    overflow-y: scroll;
    overflow-x: hidden;
    flex: 1 1 auto;
    min-height: 0;
    max-height: 100%;
    word-wrap: break-word;
    overflow-wrap: break-word;
    box-sizing: border-box;
    position: relative;
    z-index: 1;
  `;
  measurementsContent.innerHTML = `<div id="measurements-list-content" style="width: 100%; box-sizing: border-box;"></div>`;
  measurementsListPanel.appendChild(measurementsContent);
  container.appendChild(measurementsListPanel);
  cleanupFunctions.push(makeDraggable(measurementsListPanel, measurementsHeader));
  cleanupFunctions.push(makeResizable(measurementsListPanel));
  function getPosition(screenPosition) {
    try {
      const pickedObject = viewer.scene.pick(screenPosition);
      if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
        const cartesian = viewer.scene.pickPosition(screenPosition);
        if (Cesium.defined(cartesian)) {
          return cartesian;
        }
      }
      const ray = viewer.camera.getPickRay(screenPosition);
      if (!ray) {
        warn(PREFIX3, "Unable to get pick ray from camera");
        return null;
      }
      return viewer.scene.globe.pick(ray, viewer.scene);
    } catch (err) {
      error(PREFIX3, "Error getting position:", err);
      return null;
    }
  }
  function addMarker(position, color = Cesium.Color.RED) {
    const marker = viewer.entities.add({
      position,
      point: {
        pixelSize: CONSTANTS2.MARKER_SIZE,
        color,
        outlineColor: Cesium.Color.WHITE,
        outlineWidth: CONSTANTS2.MARKER_OUTLINE_WIDTH,
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      }
    });
    measurementState.entities.push(marker);
    return marker;
  }
  function addLabel(position, text) {
    const label = viewer.entities.add({
      position,
      label: {
        text,
        font: "14px sans-serif",
        fillColor: Cesium.Color.WHITE,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        outlineWidth: 2,
        outlineColor: Cesium.Color.BLACK,
        pixelOffset: new Cesium.Cartesian2(0, CONSTANTS2.LABEL_PIXEL_OFFSET_Y),
        showBackground: true,
        backgroundColor: Cesium.Color.fromAlpha(Cesium.Color.BLACK, 0.7),
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      }
    });
    measurementState.labels.push(label);
    return label;
  }
  function calculateDistance(point1, point2) {
    return Cesium.Cartesian3.distance(point1, point2);
  }
  function getMidpoint(point1, point2) {
    return Cesium.Cartesian3.lerp(point1, point2, 0.5, new Cesium.Cartesian3());
  }
  function cartesianToLatLonAlt(cartesian) {
    const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
    return {
      lat: Cesium.Math.toDegrees(cartographic.latitude),
      lon: Cesium.Math.toDegrees(cartographic.longitude),
      alt: cartographic.height
    };
  }
  function updateOrCreateMeasurement(type, value, points) {
    const results = model.get("measurement_results") || [];
    const lastResult = results[results.length - 1];
    const isActiveType = lastResult && lastResult.type === type && lastResult.isActive;
    let newResults;
    if (isActiveType) {
      newResults = [...results];
      newResults[newResults.length - 1] = { ...lastResult, value, points };
    } else {
      const count = results.filter((r) => r.type === type).length + 1;
      newResults = [...results, {
        type,
        value,
        points,
        isActive: type === "multi-distance" || type === "area",
        name: `${TYPE_LABELS[type]} ${count}`
      }];
    }
    model.set("measurement_results", newResults);
    model.save_changes();
  }
  function clearAllMeasurements() {
    log(PREFIX3, "Clearing all measurements");
    measurementState.entities.forEach((e) => viewer.entities.remove(e));
    measurementState.labels.forEach((l) => viewer.entities.remove(l));
    measurementState.polylines.forEach((p) => viewer.entities.remove(p));
    if (measurementState.polyline) {
      viewer.entities.remove(measurementState.polyline);
    }
    if (measurementState.tempPolyline) {
      viewer.entities.remove(measurementState.tempPolyline);
    }
    measurementState.points = [];
    measurementState.entities = [];
    measurementState.labels = [];
    measurementState.polylines = [];
    measurementState.polyline = null;
    measurementState.tempPolyline = null;
  }
  function clearInProgressMeasurement() {
    if (measurementState.tempPolyline) {
      viewer.entities.remove(measurementState.tempPolyline);
      measurementState.tempPolyline = null;
    }
    if ((measurementState.mode === "multi-distance" || measurementState.mode === "area") && measurementState.polyline) {
      viewer.entities.remove(measurementState.polyline);
      measurementState.polyline = null;
      measurementState.polylines = measurementState.polylines.filter((p) => p !== measurementState.polyline);
    }
    measurementState.points = [];
    measurementState.tempPoint = null;
  }
  function enableEditMode() {
    if (measurementState.mode) {
      model.set("measurement_mode", "");
      model.save_changes();
    }
    addPointBtn.style.display = "block";
    measurementState.entities.forEach((entity) => {
      if (entity.point) {
        entity.point.pixelSize = CONSTANTS2.MARKER_SIZE_HOVER;
        entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_HOVER;
      }
    });
    editHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    editHandler.setInputAction((click) => {
      if (editState.addPointMode) {
        handleAddPointClick(click);
      } else {
        const pickedObject = viewer.scene.pick(click.position);
        if (Cesium.defined(pickedObject) && pickedObject.id && pickedObject.id.point) {
          selectPoint(pickedObject.id, click.position);
        } else {
          deselectPoint();
        }
      }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    editHandler.setInputAction((movement) => {
      if (editState.dragging && editState.selectedEntity) {
        const position = getPosition(movement.endPosition);
        if (position) {
          updatePointPosition(position);
        }
      }
    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    editHandler.setInputAction(() => {
      if (editState.selectedEntity && !editState.addPointMode) {
        editState.dragging = true;
        viewer.scene.screenSpaceCameraController.enableRotate = false;
      }
    }, Cesium.ScreenSpaceEventType.LEFT_DOWN);
    editHandler.setInputAction(() => {
      if (editState.dragging) {
        editState.dragging = false;
        viewer.scene.screenSpaceCameraController.enableRotate = true;
        finalizeMeasurementUpdate();
      }
    }, Cesium.ScreenSpaceEventType.LEFT_UP);
  }
  function disableEditMode() {
    if (editHandler) {
      editHandler.destroy();
      editHandler = null;
    }
    addPointBtn.style.display = "none";
    editState.addPointMode = false;
    addPointBtn.style.background = "#16a085";
    deselectPoint();
    measurementState.entities.forEach((entity) => {
      if (entity.point) {
        entity.point.pixelSize = CONSTANTS2.MARKER_SIZE;
        entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH;
      }
    });
    viewer.scene.screenSpaceCameraController.enableRotate = true;
  }
  function selectPoint(entity, screenPosition) {
    const results = model.get("measurement_results") || [];
    let measurementIndex = -1;
    let pointIndex = -1;
    for (let i = 0; i < measurementState.entities.length; i++) {
      if (measurementState.entities[i] === entity) {
        let entityCount = 0;
        for (let m = 0; m < results.length; m++) {
          const measurement = results[m];
          const numPoints = measurement.points.length;
          if (i < entityCount + numPoints) {
            measurementIndex = m;
            pointIndex = i - entityCount;
            break;
          }
          entityCount += numPoints;
        }
        break;
      }
    }
    if (measurementIndex === -1) return;
    editState.selectedEntity = entity;
    editState.measurementIndex = measurementIndex;
    editState.pointIndex = pointIndex;
    editState.selectedPoint = entity.position.getValue(Cesium.JulianDate.now());
    entity.point.pixelSize = CONSTANTS2.MARKER_SIZE_SELECTED;
    entity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_SELECTED;
    entity.point.outlineColor = Cesium.Color.YELLOW;
    showCoordinateEditor(results[measurementIndex], pointIndex);
  }
  function deselectPoint() {
    if (editState.selectedEntity && editState.selectedEntity.point) {
      editState.selectedEntity.point.pixelSize = CONSTANTS2.MARKER_SIZE_HOVER;
      editState.selectedEntity.point.outlineWidth = CONSTANTS2.MARKER_OUTLINE_WIDTH_HOVER;
      editState.selectedEntity.point.outlineColor = Cesium.Color.WHITE;
    }
    editState.selectedEntity = null;
    editState.selectedPoint = null;
    editState.measurementIndex = null;
    editState.pointIndex = null;
    editState.dragging = false;
    editorPanel.style.display = "none";
  }
  function showCoordinateEditor(measurement, pointIndex) {
    const point = measurement.points[pointIndex];
    editorHeader.textContent = `\u22EE\u22EE Edit Point ${pointIndex + 1} (${measurement.type})`;
    editorContent.innerHTML = `
      <div style="margin-bottom: 8px;">
        <label style="display: block; margin-bottom: 3px;">Longitude (\xB0):</label>
        <input type="number" id="edit-lon" value="${point.lon.toFixed(6)}" step="0.000001" 
               style="width: 100%; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #2c2c2c; color: white;">
      </div>
      <div style="margin-bottom: 8px;">
        <label style="display: block; margin-bottom: 3px;">Latitude (\xB0):</label>
        <input type="number" id="edit-lat" value="${point.lat.toFixed(6)}" step="0.000001"
               style="width: 100%; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #2c2c2c; color: white;">
      </div>
      <div style="margin-bottom: 10px;">
        <label style="display: block; margin-bottom: 3px;">Altitude (m):</label>
        <input type="number" id="edit-alt" value="${point.alt.toFixed(2)}" step="1"
               style="width: 100%; padding: 5px; border-radius: 3px; border: 1px solid #555; background: #2c2c2c; color: white;">
      </div>
      <button id="apply-coords" style="width: 100%; padding: 8px; background: #27ae60; color: white; border: none; border-radius: 3px; cursor: pointer; margin-bottom: 5px;">
        Apply
      </button>
      <button id="close-editor" style="width: 100%; padding: 8px; background: #95a5a6; color: white; border: none; border-radius: 3px; cursor: pointer;">
        Close
      </button>
    `;
    editorPanel.style.display = "block";
    const applyBtn = document.getElementById("apply-coords");
    const closeBtn = document.getElementById("close-editor");
    const editLonInput = document.getElementById("edit-lon");
    const editLatInput = document.getElementById("edit-lat");
    const editAltInput = document.getElementById("edit-alt");
    if (!applyBtn || !closeBtn || !editLonInput || !editLatInput || !editAltInput) {
      warn(PREFIX3, "Editor panel input elements not found in DOM");
    }
    if (applyBtn) {
      applyBtn.onclick = () => {
        if (!editLonInput || !editLatInput || !editAltInput) {
          warn(PREFIX3, "Editor input fields not available");
          return;
        }
        const lon = parseFloat(editLonInput.value);
        const lat = parseFloat(editLatInput.value);
        const alt = parseFloat(editAltInput.value);
        const newPosition = Cesium.Cartesian3.fromDegrees(lon, lat, alt);
        updatePointPosition(newPosition);
        finalizeMeasurementUpdate();
      };
    }
    if (closeBtn) {
      closeBtn.onclick = () => {
        deselectPoint();
      };
    }
    ["edit-lon", "edit-lat", "edit-alt"].forEach((id) => {
      const element = document.getElementById(id);
      if (element) {
        element.onkeypress = (e) => {
          if (e.key === "Enter" && applyBtn) {
            applyBtn.click();
          }
        };
      }
    });
  }
  function updatePointPosition(newPosition) {
    if (!editState.selectedEntity) return;
    editState.selectedEntity.position = newPosition;
    editState.selectedPoint = newPosition;
    updateMeasurementVisuals();
  }
  function updateMeasurementVisuals() {
    const results = model.get("measurement_results") || [];
    if (editState.measurementIndex === null) return;
    const measurement = results[editState.measurementIndex];
    let entityStartIndex = 0;
    for (let i = 0; i < editState.measurementIndex; i++) {
      entityStartIndex += results[i].points.length;
    }
    const positions = [];
    for (let i = 0; i < measurement.points.length; i++) {
      const entity = measurementState.entities[entityStartIndex + i];
      if (entity && entity.position) {
        positions.push(entity.position.getValue(Cesium.JulianDate.now()));
      }
    }
    const polylineStartIndex = editState.measurementIndex;
    if (measurementState.polylines[polylineStartIndex]) {
      const oldEntity = measurementState.polylines[polylineStartIndex];
      if (measurement.type === "area" && oldEntity.polygon) {
        viewer.entities.remove(oldEntity);
        const newPolygon = viewer.entities.add({
          polygon: {
            hierarchy: new Cesium.PolygonHierarchy(positions),
            material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
            outline: true,
            outlineColor: Cesium.Color.ORANGE,
            outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
          }
        });
        measurementState.polylines[polylineStartIndex] = newPolygon;
      } else if (oldEntity.polyline) {
        if (measurement.type === "height") {
          const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
          const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
          oldEntity.polyline.positions = [
            positions[0],
            Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, carto0.height),
            positions[1]
          ];
        } else {
          oldEntity.polyline.positions = positions;
        }
      }
    }
    updateMeasurementLabels(measurement.type, positions);
  }
  function updateMeasurementLabels(type, positions) {
    const labelStartIndex = editState.measurementIndex;
    if (type === "distance") {
      const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
      const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
      if (measurementState.labels[labelStartIndex]) {
        measurementState.labels[labelStartIndex].position = midpoint;
        measurementState.labels[labelStartIndex].label.text = formatValue(distance);
      }
    } else if (type === "height") {
      const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
      const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
      const verticalDistance = Math.abs(carto1.height - carto0.height);
      const midHeight = (carto0.height + carto1.height) / 2;
      const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
      if (measurementState.labels[labelStartIndex]) {
        measurementState.labels[labelStartIndex].position = labelPos;
        measurementState.labels[labelStartIndex].label.text = formatValue(verticalDistance);
      }
    }
  }
  function finalizeMeasurementUpdate() {
    if (editState.measurementIndex === null || editState.pointIndex === null) return;
    const results = model.get("measurement_results") || [];
    const measurement = results[editState.measurementIndex];
    const cartographic = Cesium.Cartographic.fromCartesian(editState.selectedPoint);
    measurement.points[editState.pointIndex] = {
      lat: Cesium.Math.toDegrees(cartographic.latitude),
      lon: Cesium.Math.toDegrees(cartographic.longitude),
      alt: cartographic.height
    };
    let entityStartIndex = 0;
    for (let i = 0; i < editState.measurementIndex; i++) {
      entityStartIndex += results[i].points.length;
    }
    const positions = [];
    for (let i = 0; i < measurement.points.length; i++) {
      const entity = measurementState.entities[entityStartIndex + i];
      if (entity && entity.position) {
        positions.push(entity.position.getValue(Cesium.JulianDate.now()));
      }
    }
    if (measurement.type === "distance") {
      measurement.value = Cesium.Cartesian3.distance(positions[0], positions[1]);
    } else if (measurement.type === "height") {
      const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
      const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
      measurement.value = Math.abs(carto1.height - carto0.height);
    } else if (measurement.type === "multi-distance") {
      let totalDistance = 0;
      for (let i = 0; i < positions.length - 1; i++) {
        totalDistance += Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
      }
      measurement.value = totalDistance;
    } else if (measurement.type === "area") {
      measurement.value = calculateGeodesicArea(positions);
    }
    const newResults = [...results];
    model.set("measurement_results", newResults);
    model.save_changes();
    updateMeasurementsList();
    if (editorPanel.style.display !== "none") {
      showCoordinateEditor(measurement, editState.pointIndex);
    }
  }
  function handleAddPointClick(click) {
    const position = getPosition(click.position);
    if (!position) {
      log(PREFIX3, "No valid position for add point");
      return;
    }
    const results = model.get("measurement_results") || [];
    let nearestMeasurement = null;
    let nearestSegment = null;
    let minDistance = Number.POSITIVE_INFINITY;
    let insertIndex = -1;
    for (let measIdx = 0; measIdx < results.length; measIdx++) {
      const measurement = results[measIdx];
      if (measurement.type !== "multi-distance" && measurement.type !== "area") {
        continue;
      }
      let entityStartIndex = 0;
      for (let i = 0; i < measIdx; i++) {
        entityStartIndex += results[i].points.length;
      }
      const positions = [];
      for (let i = 0; i < measurement.points.length; i++) {
        const entity = measurementState.entities[entityStartIndex + i];
        if (entity && entity.position) {
          positions.push(entity.position.getValue(Cesium.JulianDate.now()));
        }
      }
      for (let i = 0; i < positions.length - 1; i++) {
        const p1 = positions[i];
        const p2 = positions[i + 1];
        const closestPoint = closestPointOnSegment(position, p1, p2);
        const dist = Cesium.Cartesian3.distance(position, closestPoint);
        if (dist < minDistance) {
          minDistance = dist;
          nearestMeasurement = measIdx;
          nearestSegment = { start: p1, end: p2 };
          insertIndex = i + 1;
        }
      }
      if (measurement.type === "area" && positions.length > 2) {
        const p1 = positions[positions.length - 1];
        const p2 = positions[0];
        const closestPoint = closestPointOnSegment(position, p1, p2);
        const dist = Cesium.Cartesian3.distance(position, closestPoint);
        if (dist < minDistance) {
          minDistance = dist;
          nearestMeasurement = measIdx;
          nearestSegment = { start: p1, end: p2 };
          insertIndex = positions.length;
        }
      }
    }
    if (nearestMeasurement !== null && minDistance < CONSTANTS2.MAX_ADD_POINT_DISTANCE_METERS) {
      addPointToMeasurement(nearestMeasurement, insertIndex, position);
    } else {
      log(PREFIX3, "No nearby line segment found to add point (min distance:", minDistance.toFixed(2), "m)");
    }
  }
  function closestPointOnSegment(point, segStart, segEnd) {
    const segmentVector = Cesium.Cartesian3.subtract(segEnd, segStart, new Cesium.Cartesian3());
    const pointVector = Cesium.Cartesian3.subtract(point, segStart, new Cesium.Cartesian3());
    const segmentLength = Cesium.Cartesian3.magnitude(segmentVector);
    if (segmentLength === 0) return segStart;
    const normalizedSegment = Cesium.Cartesian3.normalize(segmentVector, new Cesium.Cartesian3());
    const projection = Cesium.Cartesian3.dot(pointVector, normalizedSegment);
    const t = Math.max(0, Math.min(segmentLength, projection));
    const closestPoint = Cesium.Cartesian3.add(
      segStart,
      Cesium.Cartesian3.multiplyByScalar(normalizedSegment, t, new Cesium.Cartesian3()),
      new Cesium.Cartesian3()
    );
    return closestPoint;
  }
  function addPointToMeasurement(measurementIndex, insertIndex, position) {
    const results = model.get("measurement_results") || [];
    const measurement = results[measurementIndex];
    const cartographic = Cesium.Cartographic.fromCartesian(position);
    const newPoint = {
      lat: Cesium.Math.toDegrees(cartographic.latitude),
      lon: Cesium.Math.toDegrees(cartographic.longitude),
      alt: cartographic.height
    };
    measurement.points.splice(insertIndex, 0, newPoint);
    const colors = getColors();
    const color = colors[measurement.type]?.main || Cesium.Color.WHITE;
    const marker = addMarker(position, color);
    let entityInsertIndex = 0;
    for (let i = 0; i < measurementIndex; i++) {
      entityInsertIndex += results[i].points.length;
    }
    entityInsertIndex += insertIndex;
    measurementState.entities.splice(entityInsertIndex, 0, marker);
    const positions = [];
    let entityStartIndex = 0;
    for (let i = 0; i < measurementIndex; i++) {
      entityStartIndex += results[i].points.length;
    }
    for (let i = 0; i < measurement.points.length; i++) {
      const entity = measurementState.entities[entityStartIndex + i];
      if (entity && entity.position) {
        positions.push(entity.position.getValue(Cesium.JulianDate.now()));
      }
    }
    if (measurement.type === "multi-distance") {
      let totalDistance = 0;
      for (let i = 0; i < positions.length - 1; i++) {
        totalDistance += Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
      }
      measurement.value = totalDistance;
    } else if (measurement.type === "area") {
      measurement.value = calculateGeodesicArea(positions);
    }
    updateMeasurementVisualsForIndex(measurementIndex);
    const newResults = [...results];
    model.set("measurement_results", newResults);
    model.save_changes();
    updateMeasurementsList();
    log(PREFIX3, `Added point to ${measurement.type} measurement at index ${insertIndex}`);
  }
  function updateMeasurementVisualsForIndex(measurementIndex) {
    const results = model.get("measurement_results") || [];
    const measurement = results[measurementIndex];
    let entityStartIndex = 0;
    for (let i = 0; i < measurementIndex; i++) {
      entityStartIndex += results[i].points.length;
    }
    const positions = [];
    for (let i = 0; i < measurement.points.length; i++) {
      const entity = measurementState.entities[entityStartIndex + i];
      if (entity && entity.position) {
        positions.push(entity.position.getValue(Cesium.JulianDate.now()));
      }
    }
    const colors = getColors();
    const color = colors[measurement.type]?.main || Cesium.Color.WHITE;
    if (measurementState.polylines[measurementIndex]) {
      viewer.entities.remove(measurementState.polylines[measurementIndex]);
    }
    if (measurement.type === "multi-distance") {
      const polyline = viewer.entities.add({
        polyline: {
          positions,
          width: CONSTANTS2.POLYLINE_WIDTH,
          material: color,
          clampToGround: false
        }
      });
      measurementState.polylines[measurementIndex] = polyline;
      const labelStartIndex = measurementIndex * 20;
    } else if (measurement.type === "area") {
      const polygon = viewer.entities.add({
        polygon: {
          hierarchy: new Cesium.PolygonHierarchy(positions),
          material: Cesium.Color.fromAlpha(color, CONSTANTS2.POLYGON_ALPHA),
          outline: true,
          outlineColor: color,
          outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH,
          perPositionHeight: true
        }
      });
      measurementState.polylines[measurementIndex] = polygon;
    }
  }
  function updateMeasurementsList() {
    const results = model.get("measurement_results") || [];
    log(PREFIX3, "Updating measurements list, count:", results.length);
    const listContent = document.getElementById("measurements-list-content");
    if (!listContent) {
      warn(PREFIX3, "Measurements list content element not found in DOM");
      return;
    }
    if (results.length === 0) {
      listContent.innerHTML = '<div style="color: #888; font-style: italic;">No measurements yet</div>';
      return;
    }
    listContent.innerHTML = "";
    results.forEach((measurement, index) => {
      const measurementDiv = document.createElement("div");
      measurementDiv.style.cssText = `
        background: rgba(255, 255, 255, 0.05);
        padding: 10px;
        margin-bottom: 8px;
        border-radius: 3px;
        cursor: pointer;
        transition: background 0.2s;
        border-left: 3px solid ${getMeasurementColor(measurement.type)};
        box-sizing: border-box;
        width: 100%;
        word-wrap: break-word;
        overflow-wrap: break-word;
        overflow: hidden;
      `;
      measurementDiv.onmouseover = () => {
        measurementDiv.style.background = "rgba(255, 255, 255, 0.15)";
      };
      measurementDiv.onmouseout = () => {
        measurementDiv.style.background = "rgba(255, 255, 255, 0.05)";
      };
      const name = measurement.name || `${getMeasurementTypeLabel(measurement.type)} ${index + 1}`;
      const nameDiv = document.createElement("div");
      nameDiv.style.cssText = `
        font-weight: bold;
        margin-bottom: 5px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 8px;
      `;
      nameDiv.innerHTML = `
        <span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;">${name}</span>
        <button id="rename-${index}" style="padding: 2px 6px; background: #3498db; color: white; border: none; border-radius: 2px; cursor: pointer; font-size: 10px; flex-shrink: 0;">\u270E</button>
      `;
      measurementDiv.appendChild(nameDiv);
      const valueDiv = document.createElement("div");
      valueDiv.style.cssText = "color: #aaa; font-size: 11px; margin-bottom: 3px;";
      valueDiv.textContent = formatValue(measurement.value, measurement.type === "area");
      measurementDiv.appendChild(valueDiv);
      const pointsDiv = document.createElement("div");
      pointsDiv.style.cssText = "color: #888; font-size: 10px;";
      pointsDiv.textContent = `${measurement.points.length} point${measurement.points.length > 1 ? "s" : ""}`;
      measurementDiv.appendChild(pointsDiv);
      measurementDiv.onclick = (e) => {
        if (!e.target.id.startsWith("rename-")) {
          focusOnMeasurement(index);
        }
      };
      listContent.appendChild(measurementDiv);
      const renameBtn = document.getElementById(`rename-${index}`);
      if (renameBtn) {
        renameBtn.onclick = (e) => {
          e.stopPropagation();
          renameMeasurement(index, name);
        };
      } else {
        warn(PREFIX3, `Rename button not found for measurement ${index}`);
      }
    });
  }
  function getMeasurementColor(type) {
    return getColors()[type]?.button || "#95a5a6";
  }
  function getMeasurementTypeLabel(type) {
    return TYPE_LABELS[type] || type;
  }
  function renameMeasurement(index, currentName) {
    const newName = prompt("Enter new name for measurement:", currentName);
    if (newName && newName.trim()) {
      const results = model.get("measurement_results") || [];
      const newResults = [...results];
      newResults[index] = { ...newResults[index], name: newName.trim() };
      model.set("measurement_results", newResults);
      model.save_changes();
      updateMeasurementsList();
    }
  }
  function focusOnMeasurement(index) {
    const results = model.get("measurement_results") || [];
    if (index < 0 || index >= results.length) return;
    const measurement = results[index];
    if (!measurement.points || measurement.points.length === 0) return;
    const positions = measurement.points.map(
      (p) => Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.alt || 0)
    );
    const boundingSphere = Cesium.BoundingSphere.fromPoints(positions);
    viewer.camera.flyToBoundingSphere(boundingSphere, {
      duration: 1.5,
      offset: new Cesium.HeadingPitchRange(
        0,
        Cesium.Math.toRadians(-45),
        boundingSphere.radius * 3
      )
    });
  }
  function handleDistanceClick(click) {
    const position = getPosition(click.position);
    if (!position) return;
    if (measurementState.points.length === 0) {
      measurementState.points.push(position);
      addMarker(position);
      measurementState.tempPolyline = viewer.entities.add({
        polyline: {
          positions: new Cesium.CallbackProperty(() => {
            if (measurementState.points.length === 1 && measurementState.tempPoint) {
              return [measurementState.points[0], measurementState.tempPoint];
            }
            return measurementState.points;
          }, false),
          width: CONSTANTS2.POLYLINE_WIDTH,
          material: Cesium.Color.YELLOW,
          depthFailMaterial: Cesium.Color.YELLOW
        }
      });
    } else if (measurementState.points.length === 1) {
      measurementState.points.push(position);
      addMarker(position);
      const distance = calculateDistance(measurementState.points[0], measurementState.points[1]);
      const midpoint = getMidpoint(measurementState.points[0], measurementState.points[1]);
      addLabel(midpoint, formatValue(distance));
      if (measurementState.tempPolyline) {
        viewer.entities.remove(measurementState.tempPolyline);
        measurementState.tempPolyline = null;
      }
      measurementState.polyline = viewer.entities.add({
        polyline: {
          positions: measurementState.points,
          width: CONSTANTS2.POLYLINE_WIDTH,
          material: Cesium.Color.RED,
          depthFailMaterial: Cesium.Color.RED
        }
      });
      measurementState.polylines.push(measurementState.polyline);
      const results = model.get("measurement_results") || [];
      const newResults = [...results, {
        type: "distance",
        value: distance,
        points: measurementState.points.map(cartesianToLatLonAlt),
        name: `Distance ${results.filter((r) => r.type === "distance").length + 1}`
      }];
      model.set("measurement_results", newResults);
      model.save_changes();
      measurementState.points = [];
    }
  }
  function handleMultiDistanceClick(click) {
    const position = getPosition(click.position);
    if (!position) return;
    measurementState.points.push(position);
    addMarker(position, Cesium.Color.BLUE);
    if (measurementState.points.length === 1) {
      measurementState.polyline = viewer.entities.add({
        polyline: {
          positions: new Cesium.CallbackProperty(() => measurementState.points, false),
          width: CONSTANTS2.POLYLINE_WIDTH,
          material: Cesium.Color.BLUE,
          depthFailMaterial: Cesium.Color.BLUE
        }
      });
      measurementState.polylines.push(measurementState.polyline);
    } else {
      const p1 = measurementState.points[measurementState.points.length - 2];
      const p2 = measurementState.points[measurementState.points.length - 1];
      const distance = calculateDistance(p1, p2);
      addLabel(getMidpoint(p1, p2), formatValue(distance));
      let totalDistance = 0;
      for (let i = 0; i < measurementState.points.length - 1; i++) {
        totalDistance += calculateDistance(measurementState.points[i], measurementState.points[i + 1]);
      }
      updateOrCreateMeasurement("multi-distance", totalDistance, measurementState.points.map(cartesianToLatLonAlt));
    }
  }
  function handleHeightClick(click) {
    const pickedPosition = getPosition(click.position);
    if (!pickedPosition) return;
    const cartographic = Cesium.Cartographic.fromCartesian(pickedPosition);
    const terrainHeight = viewer.scene.globe.getHeight(cartographic) || 0;
    const pickedHeight = cartographic.height;
    const height = pickedHeight - terrainHeight;
    const groundPosition = Cesium.Cartesian3.fromRadians(
      cartographic.longitude,
      cartographic.latitude,
      terrainHeight
    );
    addMarker(groundPosition, Cesium.Color.GREEN);
    addMarker(pickedPosition, Cesium.Color.GREEN);
    const heightLine = viewer.entities.add({
      polyline: {
        positions: [groundPosition, pickedPosition],
        width: CONSTANTS2.POLYLINE_WIDTH,
        material: Cesium.Color.GREEN,
        depthFailMaterial: Cesium.Color.GREEN
      }
    });
    measurementState.polylines.push(heightLine);
    const midpoint = getMidpoint(groundPosition, pickedPosition);
    addLabel(midpoint, `${height.toFixed(2)} m`);
    const results = model.get("measurement_results") || [];
    const newResults = [...results, {
      type: "height",
      value: height,
      points: [cartesianToLatLonAlt(groundPosition), cartesianToLatLonAlt(pickedPosition)],
      name: `Height ${results.filter((r) => r.type === "height").length + 1}`
    }];
    model.set("measurement_results", newResults);
    model.save_changes();
  }
  function handleAreaClick(click) {
    const position = getPosition(click.position);
    if (!position) return;
    measurementState.points.push(position);
    addMarker(position, Cesium.Color.ORANGE);
    if (measurementState.points.length === 1) {
      measurementState.polyline = viewer.entities.add({
        polygon: {
          hierarchy: new Cesium.CallbackProperty(() => {
            return new Cesium.PolygonHierarchy(measurementState.points);
          }, false),
          material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
          outline: true,
          outlineColor: Cesium.Color.ORANGE,
          outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
        }
      });
      measurementState.polylines.push(measurementState.polyline);
    }
    if (measurementState.points.length >= 3) {
      const positions = measurementState.points;
      const area = calculateGeodesicArea(positions);
      const oldLabel = measurementState.labels.find((l) => l.label && l.label.text._value.includes("m\xB2") || l.label.text._value.includes("km\xB2"));
      if (oldLabel) {
        viewer.entities.remove(oldLabel);
        measurementState.labels = measurementState.labels.filter((l) => l !== oldLabel);
      }
      const centroidCarto = calculateCentroid(positions);
      Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]).then(() => {
        addLabel(Cesium.Cartographic.toCartesian(centroidCarto), formatValue(area, true));
      });
      updateOrCreateMeasurement("area", area, measurementState.points.map(cartesianToLatLonAlt));
    }
  }
  function handleMouseMove(movement) {
    if (measurementState.mode === "distance" && measurementState.points.length === 1) {
      const position = getPosition(movement.endPosition);
      if (position) {
        measurementState.tempPoint = position;
      }
    }
  }
  function enableMeasurementMode(mode) {
    log(PREFIX3, "Enabling measurement mode:", mode);
    if (measurementHandler) {
      measurementHandler.destroy();
      measurementHandler = null;
    }
    clearInProgressMeasurement();
    measurementState.mode = mode;
    Object.entries(modeButtons).forEach(([btnMode, btn]) => {
      btn.style.background = mode === btnMode ? "#e74c3c" : "#3498db";
    });
    if (!mode) return;
    measurementHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    if (mode === "distance") {
      measurementHandler.setInputAction(handleDistanceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
      measurementHandler.setInputAction(handleMouseMove, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
    } else if (mode === "multi-distance") {
      measurementHandler.setInputAction(handleMultiDistanceClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
      measurementHandler.setInputAction(() => {
        if (measurementState.points.length > 0) {
          const results = model.get("measurement_results") || [];
          const lastResult = results[results.length - 1];
          if (lastResult && lastResult.isActive) {
            const newResults = [...results];
            const { isActive, ...finalResult } = lastResult;
            newResults[newResults.length - 1] = finalResult;
            model.set("measurement_results", newResults);
            model.save_changes();
          }
          measurementState.points = [];
        }
      }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
    } else if (mode === "height") {
      measurementHandler.setInputAction(handleHeightClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    } else if (mode === "area") {
      measurementHandler.setInputAction(handleAreaClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
      measurementHandler.setInputAction(() => {
        if (measurementState.points.length >= 3) {
          const results = model.get("measurement_results") || [];
          const lastResult = results[results.length - 1];
          if (lastResult && lastResult.isActive) {
            const newResults = [...results];
            const { isActive, ...finalResult } = lastResult;
            newResults[newResults.length - 1] = finalResult;
            model.set("measurement_results", newResults);
            model.save_changes();
          }
          measurementState.points = [];
        }
      }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
    }
  }
  function loadAndDisplayMeasurements(measurements) {
    if (!Array.isArray(measurements)) return;
    measurements.forEach((measurement) => {
      const { type, points } = measurement;
      if (!type || !Array.isArray(points) || points.length < 2) return;
      const positions = points.map((point) => {
        const [lon, lat, alt] = point;
        return Cesium.Cartesian3.fromDegrees(lon, lat, alt || 0);
      });
      if (type === "distance" && positions.length === 2) {
        displayDistance(positions);
      } else if (type === "multi-distance" && positions.length >= 2) {
        displayMultiDistance(positions);
      } else if (type === "height" && positions.length === 2) {
        displayHeight(positions);
      } else if (type === "area" && positions.length >= 3) {
        displayArea(positions);
      }
    });
  }
  function displayDistance(positions) {
    positions.forEach((pos) => addMarker(pos, Cesium.Color.RED));
    const line = viewer.entities.add({
      polyline: {
        positions,
        width: CONSTANTS2.POLYLINE_WIDTH,
        material: Cesium.Color.RED
      }
    });
    measurementState.polylines.push(line);
    const distance = Cesium.Cartesian3.distance(positions[0], positions[1]);
    const midpoint = Cesium.Cartesian3.midpoint(positions[0], positions[1], new Cesium.Cartesian3());
    addLabel(midpoint, formatValue(distance));
  }
  function displayMultiDistance(positions) {
    positions.forEach((pos) => addMarker(pos, Cesium.Color.BLUE));
    const line = viewer.entities.add({
      polyline: {
        positions,
        width: CONSTANTS2.POLYLINE_WIDTH,
        material: Cesium.Color.BLUE
      }
    });
    measurementState.polylines.push(line);
    let totalDistance = 0;
    for (let i = 0; i < positions.length - 1; i++) {
      const segmentDistance = Cesium.Cartesian3.distance(positions[i], positions[i + 1]);
      totalDistance += segmentDistance;
      const midpoint = Cesium.Cartesian3.midpoint(positions[i], positions[i + 1], new Cesium.Cartesian3());
      addLabel(midpoint, formatValue(segmentDistance));
    }
    addLabel(positions[positions.length - 1], `Total: ${formatValue(totalDistance)}`);
  }
  function displayHeight(positions) {
    positions.forEach((pos) => addMarker(pos, Cesium.Color.GREEN));
    const carto0 = Cesium.Cartographic.fromCartesian(positions[0]);
    const carto1 = Cesium.Cartographic.fromCartesian(positions[1]);
    const verticalDistance = Math.abs(carto1.height - carto0.height);
    const line = viewer.entities.add({
      polyline: {
        positions: [
          positions[0],
          Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, carto0.height),
          positions[1]
        ],
        width: CONSTANTS2.POLYLINE_WIDTH,
        material: Cesium.Color.GREEN
      }
    });
    measurementState.polylines.push(line);
    const midHeight = (carto0.height + carto1.height) / 2;
    const labelPos = Cesium.Cartesian3.fromRadians(carto1.longitude, carto1.latitude, midHeight);
    addLabel(labelPos, formatValue(verticalDistance));
  }
  function displayArea(positions) {
    positions.forEach((pos) => addMarker(pos, Cesium.Color.ORANGE));
    const polygon = viewer.entities.add({
      polygon: {
        hierarchy: new Cesium.PolygonHierarchy(positions),
        material: Cesium.Color.ORANGE.withAlpha(CONSTANTS2.POLYGON_ALPHA),
        outline: true,
        outlineColor: Cesium.Color.ORANGE,
        outlineWidth: CONSTANTS2.POLYGON_OUTLINE_WIDTH
      }
    });
    measurementState.polylines.push(polygon);
    const area = calculateGeodesicArea(positions);
    const centroidCarto = calculateCentroid(positions);
    Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [centroidCarto]).then(() => {
      addLabel(Cesium.Cartographic.toCartesian(centroidCarto), formatValue(area, true));
    });
  }
  model.on("change:measurement_mode", () => {
    if (isDestroyed) {
      log(PREFIX3, "Skipping measurement_mode change - destroyed");
      return;
    }
    const mode = model.get("measurement_mode");
    log(PREFIX3, "Measurement mode changed:", mode);
    enableMeasurementMode(mode);
  });
  model.on("change:measurement_results", () => {
    if (isDestroyed) {
      log(PREFIX3, "Skipping measurement_results change - destroyed");
      return;
    }
    const results = model.get("measurement_results") || [];
    log(PREFIX3, "Measurement results changed, count:", results.length);
    if (results.length === 0) {
      clearAllMeasurements();
    }
    updateMeasurementsList();
  });
  model.on("change:load_measurements_trigger", () => {
    if (isDestroyed) return;
    const triggerData = model.get("load_measurements_trigger");
    log(PREFIX3, "Load measurements trigger:", triggerData);
    if (triggerData && triggerData.measurements) {
      loadAndDisplayMeasurements(triggerData.measurements);
      updateMeasurementsList();
    }
  });
  model.on("change:focus_measurement_trigger", () => {
    if (isDestroyed) return;
    const triggerData = model.get("focus_measurement_trigger");
    log(PREFIX3, "Focus measurement trigger:", triggerData);
    if (triggerData && typeof triggerData.index === "number") {
      focusOnMeasurement(triggerData.index);
    }
  });
  model.on("change:show_measurement_tools", () => {
    if (isDestroyed) return;
    const show = model.get("show_measurement_tools");
    log(PREFIX3, "Show measurement tools:", show);
    toolbarDiv.style.display = show ? "flex" : "none";
    editorPanel.style.display = show ? editorPanel.style.display : "none";
    if (!show && editState.enabled) {
      editState.enabled = false;
      disableEditMode();
    }
  });
  model.on("change:show_measurements_list", () => {
    if (isDestroyed) return;
    const show = model.get("show_measurements_list");
    log(PREFIX3, "Show measurements list:", show);
    measurementsListPanel.style.display = show ? "block" : "none";
  });
  toolbarDiv.style.display = model.get("show_measurement_tools") ? "flex" : "none";
  measurementsListPanel.style.display = model.get("show_measurements_list") ? "block" : "none";
  updateMeasurementsList();
  return {
    enableMeasurementMode,
    clearAllMeasurements,
    destroy: () => {
      log(PREFIX3, "Destroying measurement tools");
      isDestroyed = true;
      if (measurementHandler) {
        measurementHandler.destroy();
        measurementHandler = null;
      }
      if (editHandler) {
        editHandler.destroy();
        editHandler = null;
      }
      cleanupFunctions.forEach((cleanup) => {
        try {
          cleanup();
        } catch (err) {
          warn(PREFIX3, "Error during cleanup:", err);
        }
      });
      cleanupFunctions.length = 0;
      clearAllMeasurements();
      if (toolbarDiv.parentNode) {
        toolbarDiv.remove();
      }
      if (editorPanel.parentNode) {
        editorPanel.remove();
      }
      if (measurementsListPanel.parentNode) {
        measurementsListPanel.remove();
      }
      log(PREFIX3, "Measurement tools destroyed");
    }
  };
}

// src/cesiumjs_anywidget/js/point-picking.js
var PREFIX4 = "PointPicking";
function initializePointPicking(viewer, model, container) {
  const Cesium = window.Cesium;
  let handler = null;
  let pickedPointEntities = [];
  let isPickingMode = false;
  let pickingPanel = null;
  let statusText = null;
  function createPickingPanel() {
    pickingPanel = document.createElement("div");
    pickingPanel.id = "point-picking-panel";
    pickingPanel.style.cssText = `
      position: absolute;
      top: 10px;
      left: 50%;
      transform: translateX(-50%);
      background: rgba(0, 0, 0, 0.8);
      color: white;
      padding: 10px 20px;
      border-radius: 5px;
      font-family: sans-serif;
      font-size: 14px;
      z-index: 1000;
      display: none;
      pointer-events: none;
      text-align: center;
    `;
    statusText = document.createElement("span");
    statusText.textContent = "\u{1F3AF} Point Picking Mode - Click on the scene to pick a point";
    pickingPanel.appendChild(statusText);
    container.appendChild(pickingPanel);
  }
  function setPickingPanelVisible(visible) {
    if (pickingPanel) {
      pickingPanel.style.display = visible ? "block" : "none";
    }
  }
  function updateStatus(text) {
    if (statusText) {
      statusText.textContent = text;
    }
  }
  function getPositionFromScreen(screenPosition) {
    const pickedObject = viewer.scene.pick(screenPosition);
    if (viewer.scene.pickPositionSupported && Cesium.defined(pickedObject)) {
      const cartesian = viewer.scene.pickPosition(screenPosition);
      if (Cesium.defined(cartesian)) {
        return cartesian;
      }
    }
    const ray = viewer.camera.getPickRay(screenPosition);
    if (ray) {
      return viewer.scene.globe.pick(ray, viewer.scene);
    }
    return null;
  }
  function cartesianToLatLonAlt(cartesian) {
    const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
    return {
      latitude: Cesium.Math.toDegrees(cartographic.latitude),
      longitude: Cesium.Math.toDegrees(cartographic.longitude),
      altitude_wgs84: cartographic.height
    };
  }
  async function getAltitudeMSL(latitude, longitude, altitude_wgs84) {
    try {
      const positions = [Cesium.Cartographic.fromDegrees(longitude, latitude)];
      const terrainProvider = viewer.terrainProvider;
      if (terrainProvider && terrainProvider.availability) {
        const sampledPositions = await Cesium.sampleTerrainMostDetailed(terrainProvider, positions);
        const terrainHeight = sampledPositions[0].height || 0;
        return altitude_wgs84;
      }
    } catch (e) {
      warn(PREFIX4, "Could not sample terrain for MSL conversion:", e);
    }
    return altitude_wgs84;
  }
  function addPointMarker(position, config) {
    const color = config.color || [255, 0, 0, 255];
    const label = config.label || config.point_id || "";
    const pointId = config.point_id || `point_${Date.now()}`;
    const cesiumColor = new Cesium.Color(
      color[0] / 255,
      color[1] / 255,
      color[2] / 255,
      color[3] / 255
    );
    const entity = viewer.entities.add({
      id: `picked_point_${pointId}`,
      position,
      point: {
        pixelSize: 12,
        color: cesiumColor,
        outlineColor: Cesium.Color.WHITE,
        outlineWidth: 2,
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      },
      label: {
        text: label,
        font: "12pt sans-serif",
        fillColor: cesiumColor,
        outlineColor: Cesium.Color.BLACK,
        outlineWidth: 2,
        style: Cesium.LabelStyle.FILL_AND_OUTLINE,
        pixelOffset: new Cesium.Cartesian2(15, 0),
        horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
        disableDepthTestDistance: Number.POSITIVE_INFINITY
      }
    });
    pickedPointEntities.push(entity);
    return entity;
  }
  function clearPointMarkers() {
    for (const entity of pickedPointEntities) {
      viewer.entities.remove(entity);
    }
    pickedPointEntities = [];
    log(PREFIX4, "Cleared all point markers");
  }
  function removePointMarker(pointId) {
    const entityId = `picked_point_${pointId}`;
    const entity = viewer.entities.getById(entityId);
    if (entity) {
      viewer.entities.remove(entity);
      pickedPointEntities = pickedPointEntities.filter((e) => e.id !== entityId);
      log(PREFIX4, `Removed point marker: ${pointId}`);
    }
  }
  async function handleClick(click) {
    if (!isPickingMode) return;
    const position = getPositionFromScreen(click.position);
    if (!position) {
      warn(PREFIX4, "Could not determine click position");
      updateStatus("\u26A0\uFE0F Could not pick point at this location. Try clicking on visible terrain or 3D tiles.");
      return;
    }
    const coords = cartesianToLatLonAlt(position);
    const altitude_msl = await getAltitudeMSL(coords.latitude, coords.longitude, coords.altitude_wgs84);
    const config = model.get("point_picking_config") || {};
    const currentPoints = model.get("picked_points") || [];
    const pointNumber = currentPoints.length + 1;
    const labelPrefix = config.label_prefix || "GCP";
    const pointIdPrefix = config.point_id_prefix || labelPrefix;
    const pointId = `${pointIdPrefix}_${pointNumber}`;
    const label = `${labelPrefix}_${pointNumber}`;
    addPointMarker(position, {
      ...config,
      point_id: pointId,
      label
    });
    const pointData = {
      id: pointId,
      latitude: coords.latitude,
      longitude: coords.longitude,
      altitude_wgs84: coords.altitude_wgs84,
      altitude_msl,
      color: config.color || [255, 0, 0, 255],
      label,
      timestamp: (/* @__PURE__ */ new Date()).toISOString()
    };
    log(PREFIX4, "Picked point:", pointData);
    model.set("picked_points", [...currentPoints, pointData]);
    model.save_changes();
    model.set("point_picking_event", pointData);
    model.save_changes();
    updateStatus(`\u2705 Picked ${label} at (${coords.latitude.toFixed(6)}\xB0, ${coords.longitude.toFixed(6)}\xB0, ${altitude_msl.toFixed(1)}m) - Click for next point`);
    const continuous = config.continuous !== false;
    if (!continuous) {
      isPickingMode = false;
      model.set("point_picking_mode", false);
      model.save_changes();
      setPickingPanelVisible(false);
      log(PREFIX4, "Single-point mode: picking stopped after one point");
    }
  }
  function startPickingMode() {
    if (handler) {
      handler.destroy();
    }
    handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    handler.setInputAction(handleClick, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    isPickingMode = true;
    setPickingPanelVisible(true);
    const config = model.get("point_picking_config") || {};
    const currentPoints = model.get("picked_points") || [];
    const nextNumber = currentPoints.length + 1;
    const labelPrefix = config.label_prefix || "GCP";
    const continuous = config.continuous !== false;
    if (continuous) {
      updateStatus(`\u{1F3AF} Point Picking Mode (continuous) - Click to pick ${labelPrefix}_${nextNumber}`);
    } else {
      updateStatus(`\u{1F3AF} Point Picking Mode (single) - Click to pick ${labelPrefix}_${nextNumber}`);
    }
    log(PREFIX4, "Started point picking mode", { continuous, labelPrefix });
  }
  function stopPickingMode() {
    if (handler) {
      handler.destroy();
      handler = null;
    }
    isPickingMode = false;
    setPickingPanelVisible(false);
    log(PREFIX4, "Stopped point picking mode");
  }
  createPickingPanel();
  model.on("change:point_picking_mode", () => {
    const enabled = model.get("point_picking_mode");
    if (enabled) {
      startPickingMode();
    } else {
      stopPickingMode();
    }
  });
  model.on("change:point_picking_config", () => {
    if (isPickingMode) {
      const config = model.get("point_picking_config") || {};
      const currentPoints = model.get("picked_points") || [];
      const nextNumber = currentPoints.length + 1;
      const labelPrefix = config.label_prefix || "GCP";
      updateStatus(`\u{1F3AF} Ready to pick ${labelPrefix}_${nextNumber} - Click on scene`);
    }
  });
  model.on("change:picked_points", () => {
    const points = model.get("picked_points") || [];
    if (points.length === 0 && pickedPointEntities.length > 0) {
      clearPointMarkers();
    }
    const currentIds = new Set(pickedPointEntities.map((e) => e.id.replace("picked_point_", "")));
    const newIds = new Set(points.map((p) => p.id));
    for (const id of currentIds) {
      if (!newIds.has(id)) {
        removePointMarker(id);
      }
    }
  });
  if (model.get("point_picking_mode")) {
    startPickingMode();
  }
  const existingPoints = model.get("picked_points") || [];
  for (const point of existingPoints) {
    const cartesian = Cesium.Cartesian3.fromDegrees(
      point.longitude,
      point.latitude,
      point.altitude_wgs84
    );
    addPointMarker(cartesian, {
      point_id: point.id,
      color: point.color,
      label: point.label
    });
  }
  return {
    destroy: () => {
      stopPickingMode();
      clearPointMarkers();
      if (pickingPanel && pickingPanel.parentNode) {
        pickingPanel.remove();
      }
      log(PREFIX4, "Point picking module destroyed");
    }
  };
}

// src/cesiumjs_anywidget/js/index.js
window.CESIUM_BASE_URL = `https://cesium.com/downloads/cesiumjs/releases/${CESIUM_CDN_VERSION}/Build/Cesium/`;
async function render({ model, el }) {
  setDebugMode(model.get("debug_mode") || false);
  model.on("change:debug_mode", () => {
    setDebugMode(model.get("debug_mode"));
  });
  log("Main", "Starting render");
  patchWorkerForCSP();
  log("Main", "Loading CesiumJS...");
  const Cesium = await loadCesiumJS();
  log("Main", "CesiumJS loaded successfully");
  const container = document.createElement("div");
  container.style.width = "100%";
  container.style.height = model.get("height");
  container.style.position = "relative";
  el.appendChild(container);
  log("Main", "Container created with height:", model.get("height"));
  const ionToken = model.get("ion_access_token");
  if (ionToken) {
    Cesium.Ion.defaultAccessToken = ionToken;
    log("Main", "Ion access token set");
  } else {
    warn("Main", "No Ion access token provided");
  }
  const loadingDiv = createLoadingIndicator(container, !!ionToken);
  let viewer = null;
  let cameraSync = null;
  let measurementTools = null;
  let pointPicking = null;
  let photoProjection = null;
  let geoJsonLoader = null;
  let czmlLoader = null;
  let photorealisticTiles = null;
  (async () => {
    try {
      log("Main", "Creating Cesium Viewer...");
      viewer = createViewer(container, model, Cesium);
      log("Main", "Cesium Viewer created successfully");
      if (loadingDiv.parentNode) {
        loadingDiv.remove();
      }
      log("Main", "Initializing camera synchronization...");
      cameraSync = initializeCameraSync(viewer, model);
      log("Main", "Camera synchronization initialized");
      log("Main", "Initializing measurement tools...");
      measurementTools = initializeMeasurementTools(viewer, model, container);
      log("Main", "Measurement tools initialized");
      log("Main", "Initializing point picking...");
      pointPicking = initializePointPicking(viewer, model, container);
      log("Main", "Point picking initialized");
      log("Main", "Setting up viewer listeners...");
      setupViewerListeners(viewer, model, container, Cesium);
      log("Main", "Viewer listeners set up");
      log("Main", "Setting up GeoJSON loader...");
      geoJsonLoader = setupGeoJSONLoader(viewer, model, Cesium);
      log("Main", "GeoJSON loader set up");
      log("Main", "Setting up CZML loader...");
      czmlLoader = setupCZMLLoader(viewer, model, Cesium);
      log("Main", "CZML loader set up");
      log("Main", "Setting up Photorealistic 3D Tiles...");
      photorealisticTiles = setupPhotorealisticTiles(viewer, model, Cesium);
      log("Main", "Photorealistic 3D Tiles set up");
      model.on("change:camera_command", () => {
        const command = model.get("camera_command");
        if (command && command.command === "projectPhoto" && photoProjection) {
          log("Main", "Handling projectPhoto command");
          photoProjection.projectPhoto(command);
        }
      });
      log("Main", "Initialization complete");
    } catch (err) {
      error("Main", "Error initializing CesiumJS viewer:", err);
      loadingDiv.textContent = `Error: ${err.message}`;
      loadingDiv.style.background = "rgba(255,0,0,0.8)";
    }
  })();
  return () => {
    log("Main", "Starting cleanup...");
    if (cameraSync) {
      log("Main", "Destroying camera sync...");
      cameraSync.destroy();
    }
    if (measurementTools) {
      log("Main", "Destroying measurement tools...");
      measurementTools.destroy();
    }
    if (photoProjection) {
      log("Main", "Destroying photo projection...");
      photoProjection.destroy();
    }
    if (pointPicking) {
      log("Main", "Destroying point picking...");
      pointPicking.destroy();
    }
    if (geoJsonLoader) {
      log("Main", "Destroying GeoJSON loader...");
      geoJsonLoader.destroy();
    }
    if (czmlLoader) {
      log("Main", "Destroying CZML loader...");
      czmlLoader.destroy();
    }
    if (photorealisticTiles) {
      log("Main", "Destroying photorealistic tiles...");
      photorealisticTiles.destroy();
    }
    if (viewer) {
      log("Main", "Destroying viewer...");
      viewer.destroy();
    }
    log("Main", "Cleanup complete");
  };
}
var index_default = { render };
export {
  index_default as default
};
