Skip to content

解决cesium地图鼠标点击不准确、偏移问题 #110

@demaria-max

Description

@demaria-max

在项目中全局引入autofit后,cesium地图会因为transform适配导致鼠标点击事件不准,解决办法是重新修改ceiusm中的ScreenSpaceEventHandler中的getPosition方法,重新计算纠正点击位置。

一、如果直接引入ceiusm sdk代码则修改\cesium\Source\Core\ScreenSpaceEventHandler.js

function getPosition(screenSpaceEventHandler, event, result) { 
  const element = screenSpaceEventHandler._element;
  if (element === document) {
    result.x = event.clientX;
    result.y = event.clientY;
    return result;
  }
  var fa = element.offsetWidth / element.getBoundingClientRect().width; //计算缩放值
  var faHeight = element.offsetHeight / element.getBoundingClientRect().height; //计算缩放值
  const rect = element.getBoundingClientRect();
  result.x = (event.clientX - rect.left) * fa; //做差之后乘以缩放值就可以实现正常效果
  result.y = (event.clientY - rect.top) * faHeight;
  return result;
}

二、通过npm引入的cesium

通过npm引入的cesium包可能没有ScreenSpaceEventHandler.js文件

  1. 新建一个CustomScreenSpaceEventHandler.js文件,将cesium\Source\Core\ScreenSpaceEventHandler.js文件内容复制到CustomScreenSpaceEventHandler.js文件中
  2. 修改getPosition方法,参考上述的方法
  3. 在项目中引入CustomScreenSpaceEventHandler.js文件,并使用CustomScreenSpaceEventHandler替换ScreenSpaceEventHandler,参考如下:
//原代码
const cesiumHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
//修改后
const cesiumHandler = new CustomScreenSpaceEventHandler(viewer.scene.canvas);

CustomScreenSpaceEventHandler.js源码文件如下:

import {
  AssociativeArray,
  Cartesian2,
  defaultValue,
  defined,
  destroyObject,
  DeveloperError,
  FeatureDetection,
  getTimestamp,
  KeyboardEventModifier,
  ScreenSpaceEventType,
} from "cesium";

function getPosition(screenSpaceEventHandler, event, result) {
  const element = screenSpaceEventHandler._element;
  if (element === document) {
    result.x = event.clientX;
    result.y = event.clientY;
    return result;
  }
  var fa = element.offsetWidth / element.getBoundingClientRect().width; //计算缩放值
  var faHeight = element.offsetHeight / element.getBoundingClientRect().height; //计算缩放值
  const rect = element.getBoundingClientRect();
  result.x = (event.clientX - rect.left) * fa; //做差之后乘以缩放值就可以实现正常效果
  result.y = (event.clientY - rect.top) * faHeight;
  return result;
}

function getInputEventKey(type, modifier) {
  let key = type;
  if (defined(modifier)) {
    key += `+${modifier}`;
  }
  return key;
}

function getModifier(event) {
  if (event.shiftKey) {
    return KeyboardEventModifier.SHIFT;
  } else if (event.ctrlKey) {
    return KeyboardEventModifier.CTRL;
  } else if (event.altKey) {
    return KeyboardEventModifier.ALT;
  }

  return undefined;
}

const MouseButton = {
  LEFT: 0,
  MIDDLE: 1,
  RIGHT: 2,
};

function registerListener(screenSpaceEventHandler, domType, element, callback) {
  function listener(e) {
    callback(screenSpaceEventHandler, e);
  }

  if (FeatureDetection.isInternetExplorer()) {
    element.addEventListener(domType, listener, false);
  } else {
    element.addEventListener(domType, listener, {
      capture: false,
      passive: false,
    });
  }

  screenSpaceEventHandler._removalFunctions.push(function () {
    element.removeEventListener(domType, listener, false);
  });
}

function registerListeners(screenSpaceEventHandler) {
  const element = screenSpaceEventHandler._element;

  // some listeners may be registered on the document, so we still get events even after
  // leaving the bounds of element.
  // this is affected by the existence of an undocumented disableRootEvents property on element.
  const alternateElement = !defined(element.disableRootEvents)
    ? document
    : element;

  if (FeatureDetection.supportsPointerEvents()) {
    registerListener(
      screenSpaceEventHandler,
      "pointerdown",
      element,
      handlePointerDown
    );
    registerListener(
      screenSpaceEventHandler,
      "pointerup",
      element,
      handlePointerUp
    );
    registerListener(
      screenSpaceEventHandler,
      "pointermove",
      element,
      handlePointerMove
    );
    registerListener(
      screenSpaceEventHandler,
      "pointercancel",
      element,
      handlePointerUp
    );
  } else {
    registerListener(
      screenSpaceEventHandler,
      "mousedown",
      element,
      handleMouseDown
    );
    registerListener(
      screenSpaceEventHandler,
      "mouseup",
      alternateElement,
      handleMouseUp
    );
    registerListener(
      screenSpaceEventHandler,
      "mousemove",
      alternateElement,
      handleMouseMove
    );
    registerListener(
      screenSpaceEventHandler,
      "touchstart",
      element,
      handleTouchStart
    );
    registerListener(
      screenSpaceEventHandler,
      "touchend",
      alternateElement,
      handleTouchEnd
    );
    registerListener(
      screenSpaceEventHandler,
      "touchmove",
      alternateElement,
      handleTouchMove
    );
    registerListener(
      screenSpaceEventHandler,
      "touchcancel",
      alternateElement,
      handleTouchEnd
    );
  }

  registerListener(
    screenSpaceEventHandler,
    "dblclick",
    element,
    handleDblClick
  );

  // detect available wheel event
  let wheelEvent;
  if ("onwheel" in element) {
    // spec event type
    wheelEvent = "wheel";
  } else if (document.onmousewheel !== undefined) {
    // legacy event type
    wheelEvent = "mousewheel";
  } else {
    // older Firefox
    wheelEvent = "DOMMouseScroll";
  }

  registerListener(screenSpaceEventHandler, wheelEvent, element, handleWheel);
}

function unregisterListeners(screenSpaceEventHandler) {
  const removalFunctions = screenSpaceEventHandler._removalFunctions;
  for (let i = 0; i < removalFunctions.length; ++i) {
    removalFunctions[i]();
  }
}

const mouseDownEvent = {
  position: new Cartesian2(),
};

function gotTouchEvent(screenSpaceEventHandler) {
  screenSpaceEventHandler._lastSeenTouchEvent = getTimestamp();
}

function canProcessMouseEvent(screenSpaceEventHandler) {
  return (
    getTimestamp() - screenSpaceEventHandler._lastSeenTouchEvent >
    ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds
  );
}

function checkPixelTolerance(startPosition, endPosition, pixelTolerance) {
  const xDiff = startPosition.x - endPosition.x;
  const yDiff = startPosition.y - endPosition.y;
  const totalPixels = Math.sqrt(xDiff * xDiff + yDiff * yDiff);

  return totalPixels < pixelTolerance;
}

function handleMouseDown(screenSpaceEventHandler, event) {
  if (!canProcessMouseEvent(screenSpaceEventHandler)) {
    return;
  }

  const button = event.button;
  screenSpaceEventHandler._buttonDown[button] = true;

  let screenSpaceEventType;
  if (button === MouseButton.LEFT) {
    screenSpaceEventType = ScreenSpaceEventType.LEFT_DOWN;
  } else if (button === MouseButton.MIDDLE) {
    screenSpaceEventType = ScreenSpaceEventType.MIDDLE_DOWN;
  } else if (button === MouseButton.RIGHT) {
    screenSpaceEventType = ScreenSpaceEventType.RIGHT_DOWN;
  } else {
    return;
  }

  const position = getPosition(
    screenSpaceEventHandler,
    event,
    screenSpaceEventHandler._primaryPosition
  );
  Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
  Cartesian2.clone(position, screenSpaceEventHandler._primaryPreviousPosition);

  const modifier = getModifier(event);

  const action = screenSpaceEventHandler.getInputAction(
    screenSpaceEventType,
    modifier
  );

  if (defined(action)) {
    Cartesian2.clone(position, mouseDownEvent.position);

    action(mouseDownEvent);

    event.preventDefault();
  }
}

const mouseUpEvent = {
  position: new Cartesian2(),
};
const mouseClickEvent = {
  position: new Cartesian2(),
};

function cancelMouseEvent(
  screenSpaceEventHandler,
  screenSpaceEventType,
  clickScreenSpaceEventType,
  event
) {
  const modifier = getModifier(event);

  const action = screenSpaceEventHandler.getInputAction(
    screenSpaceEventType,
    modifier
  );
  const clickAction = screenSpaceEventHandler.getInputAction(
    clickScreenSpaceEventType,
    modifier
  );

  if (defined(action) || defined(clickAction)) {
    const position = getPosition(
      screenSpaceEventHandler,
      event,
      screenSpaceEventHandler._primaryPosition
    );

    if (defined(action)) {
      Cartesian2.clone(position, mouseUpEvent.position);

      action(mouseUpEvent);
    }

    if (defined(clickAction)) {
      const startPosition = screenSpaceEventHandler._primaryStartPosition;
      if (
        checkPixelTolerance(
          startPosition,
          position,
          screenSpaceEventHandler._clickPixelTolerance
        )
      ) {
        Cartesian2.clone(position, mouseClickEvent.position);

        clickAction(mouseClickEvent);
      }
    }
  }
}

function handleMouseUp(screenSpaceEventHandler, event) {
  if (!canProcessMouseEvent(screenSpaceEventHandler)) {
    return;
  }

  const button = event.button;

  if (
    button !== MouseButton.LEFT &&
    button !== MouseButton.MIDDLE &&
    button !== MouseButton.RIGHT
  ) {
    return;
  }

  if (screenSpaceEventHandler._buttonDown[MouseButton.LEFT]) {
    cancelMouseEvent(
      screenSpaceEventHandler,
      ScreenSpaceEventType.LEFT_UP,
      ScreenSpaceEventType.LEFT_CLICK,
      event
    );
    screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;
  }
  if (screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE]) {
    cancelMouseEvent(
      screenSpaceEventHandler,
      ScreenSpaceEventType.MIDDLE_UP,
      ScreenSpaceEventType.MIDDLE_CLICK,
      event
    );
    screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE] = false;
  }
  if (screenSpaceEventHandler._buttonDown[MouseButton.RIGHT]) {
    cancelMouseEvent(
      screenSpaceEventHandler,
      ScreenSpaceEventType.RIGHT_UP,
      ScreenSpaceEventType.RIGHT_CLICK,
      event
    );
    screenSpaceEventHandler._buttonDown[MouseButton.RIGHT] = false;
  }
}

const mouseMoveEvent = {
  startPosition: new Cartesian2(),
  endPosition: new Cartesian2(),
};

function handleMouseMove(screenSpaceEventHandler, event) {
  if (!canProcessMouseEvent(screenSpaceEventHandler)) {
    return;
  }

  const modifier = getModifier(event);

  const position = getPosition(
    screenSpaceEventHandler,
    event,
    screenSpaceEventHandler._primaryPosition
  );
  const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;

  const action = screenSpaceEventHandler.getInputAction(
    ScreenSpaceEventType.MOUSE_MOVE,
    modifier
  );

  if (defined(action)) {
    Cartesian2.clone(previousPosition, mouseMoveEvent.startPosition);
    Cartesian2.clone(position, mouseMoveEvent.endPosition);

    action(mouseMoveEvent);
  }

  Cartesian2.clone(position, previousPosition);

  if (
    screenSpaceEventHandler._buttonDown[MouseButton.LEFT] ||
    screenSpaceEventHandler._buttonDown[MouseButton.MIDDLE] ||
    screenSpaceEventHandler._buttonDown[MouseButton.RIGHT]
  ) {
    event.preventDefault();
  }
}

const mouseDblClickEvent = {
  position: new Cartesian2(),
};

function handleDblClick(screenSpaceEventHandler, event) {
  const button = event.button;

  let screenSpaceEventType;
  if (button === MouseButton.LEFT) {
    screenSpaceEventType = ScreenSpaceEventType.LEFT_DOUBLE_CLICK;
  } else {
    return;
  }

  const modifier = getModifier(event);

  const action = screenSpaceEventHandler.getInputAction(
    screenSpaceEventType,
    modifier
  );

  if (defined(action)) {
    getPosition(screenSpaceEventHandler, event, mouseDblClickEvent.position);

    action(mouseDblClickEvent);
  }
}

function handleWheel(screenSpaceEventHandler, event) {
  // currently this event exposes the delta value in terms of
  // the obsolete mousewheel event type.  so, for now, we adapt the other
  // values to that scheme.
  let delta;

  // standard wheel event uses deltaY.  sign is opposite wheelDelta.
  // deltaMode indicates what unit it is in.
  if (defined(event.deltaY)) {
    const deltaMode = event.deltaMode;
    if (deltaMode === event.DOM_DELTA_PIXEL) {
      delta = -event.deltaY;
    } else if (deltaMode === event.DOM_DELTA_LINE) {
      delta = -event.deltaY * 40;
    } else {
      // DOM_DELTA_PAGE
      delta = -event.deltaY * 120;
    }
  } else if (event.detail > 0) {
    // old Firefox versions use event.detail to count the number of clicks. The sign
    // of the integer is the direction the wheel is scrolled.
    delta = event.detail * -120;
  } else {
    delta = event.wheelDelta;
  }

  if (!defined(delta)) {
    return;
  }

  const modifier = getModifier(event);
  const action = screenSpaceEventHandler.getInputAction(
    ScreenSpaceEventType.WHEEL,
    modifier
  );

  if (defined(action)) {
    action(delta);

    event.preventDefault();
  }
}

function handleTouchStart(screenSpaceEventHandler, event) {
  gotTouchEvent(screenSpaceEventHandler);

  const changedTouches = event.changedTouches;

  let i;
  const length = changedTouches.length;
  let touch;
  let identifier;
  const positions = screenSpaceEventHandler._positions;

  for (i = 0; i < length; ++i) {
    touch = changedTouches[i];
    identifier = touch.identifier;
    positions.set(
      identifier,
      getPosition(screenSpaceEventHandler, touch, new Cartesian2())
    );
  }

  fireTouchEvents(screenSpaceEventHandler, event);

  const previousPositions = screenSpaceEventHandler._previousPositions;

  for (i = 0; i < length; ++i) {
    touch = changedTouches[i];
    identifier = touch.identifier;
    previousPositions.set(
      identifier,
      Cartesian2.clone(positions.get(identifier))
    );
  }
}

function handleTouchEnd(screenSpaceEventHandler, event) {
  gotTouchEvent(screenSpaceEventHandler);

  const changedTouches = event.changedTouches;

  let i;
  const length = changedTouches.length;
  let touch;
  let identifier;
  const positions = screenSpaceEventHandler._positions;

  for (i = 0; i < length; ++i) {
    touch = changedTouches[i];
    identifier = touch.identifier;
    positions.remove(identifier);
  }

  fireTouchEvents(screenSpaceEventHandler, event);

  const previousPositions = screenSpaceEventHandler._previousPositions;

  for (i = 0; i < length; ++i) {
    touch = changedTouches[i];
    identifier = touch.identifier;
    previousPositions.remove(identifier);
  }
}

const touchStartEvent = {
  position: new Cartesian2(),
};
const touch2StartEvent = {
  position1: new Cartesian2(),
  position2: new Cartesian2(),
};
const touchEndEvent = {
  position: new Cartesian2(),
};
const touchClickEvent = {
  position: new Cartesian2(),
};
const touchHoldEvent = {
  position: new Cartesian2(),
};

function fireTouchEvents(screenSpaceEventHandler, event) {
  const modifier = getModifier(event);
  const positions = screenSpaceEventHandler._positions;
  const numberOfTouches = positions.length;
  let action;
  let clickAction;
  const pinching = screenSpaceEventHandler._isPinching;

  if (
    numberOfTouches !== 1 &&
    screenSpaceEventHandler._buttonDown[MouseButton.LEFT]
  ) {
    // transitioning from single touch, trigger UP and might trigger CLICK
    screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = false;

    if (defined(screenSpaceEventHandler._touchHoldTimer)) {
      clearTimeout(screenSpaceEventHandler._touchHoldTimer);
      screenSpaceEventHandler._touchHoldTimer = undefined;
    }

    action = screenSpaceEventHandler.getInputAction(
      ScreenSpaceEventType.LEFT_UP,
      modifier
    );

    if (defined(action)) {
      Cartesian2.clone(
        screenSpaceEventHandler._primaryPosition,
        touchEndEvent.position
      );

      action(touchEndEvent);
    }

    if (numberOfTouches === 0 && !screenSpaceEventHandler._isTouchHolding) {
      // releasing single touch, check for CLICK
      clickAction = screenSpaceEventHandler.getInputAction(
        ScreenSpaceEventType.LEFT_CLICK,
        modifier
      );

      if (defined(clickAction)) {
        const startPosition = screenSpaceEventHandler._primaryStartPosition;
        const endPosition =
          screenSpaceEventHandler._previousPositions.values[0];
        if (
          checkPixelTolerance(
            startPosition,
            endPosition,
            screenSpaceEventHandler._clickPixelTolerance
          )
        ) {
          Cartesian2.clone(
            screenSpaceEventHandler._primaryPosition,
            touchClickEvent.position
          );

          clickAction(touchClickEvent);
        }
      }
    }

    screenSpaceEventHandler._isTouchHolding = false;

    // Otherwise don't trigger CLICK, because we are adding more touches.
  }

  if (numberOfTouches === 0 && pinching) {
    // transitioning from pinch, trigger PINCH_END
    screenSpaceEventHandler._isPinching = false;

    action = screenSpaceEventHandler.getInputAction(
      ScreenSpaceEventType.PINCH_END,
      modifier
    );

    if (defined(action)) {
      action();
    }
  }

  if (numberOfTouches === 1 && !pinching) {
    // transitioning to single touch, trigger DOWN
    const position = positions.values[0];
    Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);
    Cartesian2.clone(position, screenSpaceEventHandler._primaryStartPosition);
    Cartesian2.clone(
      position,
      screenSpaceEventHandler._primaryPreviousPosition
    );

    screenSpaceEventHandler._buttonDown[MouseButton.LEFT] = true;

    action = screenSpaceEventHandler.getInputAction(
      ScreenSpaceEventType.LEFT_DOWN,
      modifier
    );

    if (defined(action)) {
      Cartesian2.clone(position, touchStartEvent.position);

      action(touchStartEvent);
    }

    screenSpaceEventHandler._touchHoldTimer = setTimeout(function () {
      if (!screenSpaceEventHandler.isDestroyed()) {
        screenSpaceEventHandler._touchHoldTimer = undefined;
        screenSpaceEventHandler._isTouchHolding = true;

        clickAction = screenSpaceEventHandler.getInputAction(
          ScreenSpaceEventType.RIGHT_CLICK,
          modifier
        );

        if (defined(clickAction)) {
          const startPosition = screenSpaceEventHandler._primaryStartPosition;
          const endPosition =
            screenSpaceEventHandler._previousPositions.values[0];
          if (
            checkPixelTolerance(
              startPosition,
              endPosition,
              screenSpaceEventHandler._holdPixelTolerance
            )
          ) {
            Cartesian2.clone(
              screenSpaceEventHandler._primaryPosition,
              touchHoldEvent.position
            );

            clickAction(touchHoldEvent);
          }
        }
      }
    }, ScreenSpaceEventHandler.touchHoldDelayMilliseconds);

    event.preventDefault();
  }

  if (numberOfTouches === 2 && !pinching) {
    // transitioning to pinch, trigger PINCH_START
    screenSpaceEventHandler._isPinching = true;

    action = screenSpaceEventHandler.getInputAction(
      ScreenSpaceEventType.PINCH_START,
      modifier
    );

    if (defined(action)) {
      Cartesian2.clone(positions.values[0], touch2StartEvent.position1);
      Cartesian2.clone(positions.values[1], touch2StartEvent.position2);

      action(touch2StartEvent);

      // Touch-enabled devices, in particular iOS can have many default behaviours for
      // "pinch" events, which can still be executed unless we prevent them here.
      event.preventDefault();
    }
  }
}

function handleTouchMove(screenSpaceEventHandler, event) {
  gotTouchEvent(screenSpaceEventHandler);

  const changedTouches = event.changedTouches;

  let i;
  const length = changedTouches.length;
  let touch;
  let identifier;
  const positions = screenSpaceEventHandler._positions;

  for (i = 0; i < length; ++i) {
    touch = changedTouches[i];
    identifier = touch.identifier;
    const position = positions.get(identifier);
    if (defined(position)) {
      getPosition(screenSpaceEventHandler, touch, position);
    }
  }

  fireTouchMoveEvents(screenSpaceEventHandler, event);

  const previousPositions = screenSpaceEventHandler._previousPositions;

  for (i = 0; i < length; ++i) {
    touch = changedTouches[i];
    identifier = touch.identifier;
    Cartesian2.clone(
      positions.get(identifier),
      previousPositions.get(identifier)
    );
  }
}

const touchMoveEvent = {
  startPosition: new Cartesian2(),
  endPosition: new Cartesian2(),
};
const touchPinchMovementEvent = {
  distance: {
    startPosition: new Cartesian2(),
    endPosition: new Cartesian2(),
  },
  angleAndHeight: {
    startPosition: new Cartesian2(),
    endPosition: new Cartesian2(),
  },
};

function fireTouchMoveEvents(screenSpaceEventHandler, event) {
  const modifier = getModifier(event);
  const positions = screenSpaceEventHandler._positions;
  const previousPositions = screenSpaceEventHandler._previousPositions;
  const numberOfTouches = positions.length;
  let action;

  if (
    numberOfTouches === 1 &&
    screenSpaceEventHandler._buttonDown[MouseButton.LEFT]
  ) {
    // moving single touch
    const position = positions.values[0];
    Cartesian2.clone(position, screenSpaceEventHandler._primaryPosition);

    const previousPosition = screenSpaceEventHandler._primaryPreviousPosition;

    action = screenSpaceEventHandler.getInputAction(
      ScreenSpaceEventType.MOUSE_MOVE,
      modifier
    );

    if (defined(action)) {
      Cartesian2.clone(previousPosition, touchMoveEvent.startPosition);
      Cartesian2.clone(position, touchMoveEvent.endPosition);

      action(touchMoveEvent);
    }

    Cartesian2.clone(position, previousPosition);

    event.preventDefault();
  } else if (numberOfTouches === 2 && screenSpaceEventHandler._isPinching) {
    // moving pinch

    action = screenSpaceEventHandler.getInputAction(
      ScreenSpaceEventType.PINCH_MOVE,
      modifier
    );
    if (defined(action)) {
      const position1 = positions.values[0];
      const position2 = positions.values[1];
      const previousPosition1 = previousPositions.values[0];
      const previousPosition2 = previousPositions.values[1];

      const dX = position2.x - position1.x;
      const dY = position2.y - position1.y;
      const dist = Math.sqrt(dX * dX + dY * dY) * 0.25;

      const prevDX = previousPosition2.x - previousPosition1.x;
      const prevDY = previousPosition2.y - previousPosition1.y;
      const prevDist = Math.sqrt(prevDX * prevDX + prevDY * prevDY) * 0.25;

      const cY = (position2.y + position1.y) * 0.125;
      const prevCY = (previousPosition2.y + previousPosition1.y) * 0.125;
      const angle = Math.atan2(dY, dX);
      const prevAngle = Math.atan2(prevDY, prevDX);

      Cartesian2.fromElements(
        0.0,
        prevDist,
        touchPinchMovementEvent.distance.startPosition
      );
      Cartesian2.fromElements(
        0.0,
        dist,
        touchPinchMovementEvent.distance.endPosition
      );

      Cartesian2.fromElements(
        prevAngle,
        prevCY,
        touchPinchMovementEvent.angleAndHeight.startPosition
      );
      Cartesian2.fromElements(
        angle,
        cY,
        touchPinchMovementEvent.angleAndHeight.endPosition
      );

      action(touchPinchMovementEvent);
    }
  }
}

function handlePointerDown(screenSpaceEventHandler, event) {
  event.target.setPointerCapture(event.pointerId);

  if (event.pointerType === "touch") {
    const positions = screenSpaceEventHandler._positions;

    const identifier = event.pointerId;
    positions.set(
      identifier,
      getPosition(screenSpaceEventHandler, event, new Cartesian2())
    );

    fireTouchEvents(screenSpaceEventHandler, event);

    const previousPositions = screenSpaceEventHandler._previousPositions;
    previousPositions.set(
      identifier,
      Cartesian2.clone(positions.get(identifier))
    );
  } else {
    handleMouseDown(screenSpaceEventHandler, event);
  }
}

function handlePointerUp(screenSpaceEventHandler, event) {
  if (event.pointerType === "touch") {
    const positions = screenSpaceEventHandler._positions;

    const identifier = event.pointerId;
    positions.remove(identifier);

    fireTouchEvents(screenSpaceEventHandler, event);

    const previousPositions = screenSpaceEventHandler._previousPositions;
    previousPositions.remove(identifier);
  } else {
    handleMouseUp(screenSpaceEventHandler, event);
  }
}

function handlePointerMove(screenSpaceEventHandler, event) {
  if (event.pointerType === "touch") {
    const positions = screenSpaceEventHandler._positions;

    const identifier = event.pointerId;
    const position = positions.get(identifier);
    if (!defined(position)) {
      return;
    }

    getPosition(screenSpaceEventHandler, event, position);
    fireTouchMoveEvents(screenSpaceEventHandler, event);

    const previousPositions = screenSpaceEventHandler._previousPositions;
    Cartesian2.clone(
      positions.get(identifier),
      previousPositions.get(identifier)
    );
  } else {
    handleMouseMove(screenSpaceEventHandler, event);
  }
}

/**
 * @typedef {object} ScreenSpaceEventHandler.PositionedEvent
 *
 * An Event that occurs at a single position on screen.
 *
 * @property {Cartesian2} position
 */

/**
 * @callback ScreenSpaceEventHandler.PositionedEventCallback
 *
 * The callback invoked when a positioned event triggers an event listener.
 *
 * @param {ScreenSpaceEventHandler.PositionedEvent} event The event which triggered the listener
 */

/**
 * @typedef {object} ScreenSpaceEventHandler.MotionEvent
 *
 * An Event that starts at one position and ends at another.
 *
 * @property {Cartesian2} startPosition
 * @property {Cartesian2} endPosition
 */

/**
 * @callback ScreenSpaceEventHandler.MotionEventCallback
 *
 * The callback invoked when a motion event triggers an event listener.
 *
 * @param {ScreenSpaceEventHandler.MotionEvent} event The event which triggered the listener
 */

/**
 * @typedef {object} ScreenSpaceEventHandler.TwoPointEvent
 *
 * An Event that occurs at a two positions on screen.
 *
 * @property {Cartesian2} position1
 * @property {Cartesian2} position2
 */

/**
 * @callback ScreenSpaceEventHandler.TwoPointEventCallback
 *
 * The callback invoked when a two-point event triggers an event listener.
 *
 * @param {ScreenSpaceEventHandler.TwoPointEvent} event The event which triggered the listener
 */

/**
 * @typedef {object} ScreenSpaceEventHandler.TwoPointMotionEvent
 *
 * An Event that starts at a two positions on screen and moves to two other positions.
 *
 * @property {Cartesian2} position1
 * @property {Cartesian2} position2
 * @property {Cartesian2} previousPosition1
 * @property {Cartesian2} previousPosition2
 */

/**
 * @callback ScreenSpaceEventHandler.TwoPointMotionEventCallback
 *
 * The callback invoked when a two-point motion event triggers an event listener.
 *
 * @param {ScreenSpaceEventHandler.TwoPointMotionEvent} event The event which triggered the listener
 */

/**
 * @callback ScreenSpaceEventHandler.WheelEventCallback
 *
 * The callback invoked when a mouse-wheel event triggers an event listener.
 *
 * @param {number} delta The amount that the mouse wheel moved
 */

/**
 * Handles user input events. Custom functions can be added to be executed on
 * when the user enters input.
 *
 * @alias ScreenSpaceEventHandler
 *
 * @param {HTMLCanvasElement} [element=document] The element to add events to.
 *
 * @constructor
 */
function ScreenSpaceEventHandler(element) {
  this._inputEvents = {};
  this._buttonDown = {
    [MouseButton.LEFT]: false,
    [MouseButton.MIDDLE]: false,
    [MouseButton.RIGHT]: false,
  };
  this._isPinching = false;
  this._isTouchHolding = false;
  this._lastSeenTouchEvent =
    -ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds;

  this._primaryStartPosition = new Cartesian2();
  this._primaryPosition = new Cartesian2();
  this._primaryPreviousPosition = new Cartesian2();

  this._positions = new AssociativeArray();
  this._previousPositions = new AssociativeArray();

  this._removalFunctions = [];

  this._touchHoldTimer = undefined;

  // TODO: Revisit when doing mobile development. May need to be configurable
  // or determined based on the platform?
  this._clickPixelTolerance = 5;
  this._holdPixelTolerance = 25;

  this._element = element ?? document;

  registerListeners(this);
}

/**
 * Set a function to be executed on an input event.
 *
 * @param {ScreenSpaceEventHandler.PositionedEventCallback|ScreenSpaceEventHandler.MotionEventCallback|ScreenSpaceEventHandler.WheelEventCallback|ScreenSpaceEventHandler.TwoPointEventCallback|ScreenSpaceEventHandler.TwoPointMotionEventCallback} action Function to be executed when the input event occurs.
 * @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
 * @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
 * event occurs.
 *
 * @see ScreenSpaceEventHandler#getInputAction
 * @see ScreenSpaceEventHandler#removeInputAction
 */
ScreenSpaceEventHandler.prototype.setInputAction = function (
  action,
  type,
  modifier
) {
  //>>includeStart('debug', pragmas.debug);
  if (!defined(action)) {
    throw new DeveloperError("action is required.");
  }
  if (!defined(type)) {
    throw new DeveloperError("type is required.");
  }
  //>>includeEnd('debug');

  const key = getInputEventKey(type, modifier);
  this._inputEvents[key] = action;
};

/**
 * Returns the function to be executed on an input event.
 *
 * @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
 * @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
 * event occurs.
 *
 * @returns {ScreenSpaceEventHandler.PositionedEventCallback|ScreenSpaceEventHandler.MotionEventCallback|ScreenSpaceEventHandler.WheelEventCallback|ScreenSpaceEventHandler.TwoPointEventCallback|ScreenSpaceEventHandler.TwoPointMotionEventCallback} The function to be executed on an input event.
 *
 * @see ScreenSpaceEventHandler#setInputAction
 * @see ScreenSpaceEventHandler#removeInputAction
 */
ScreenSpaceEventHandler.prototype.getInputAction = function (type, modifier) {
  //>>includeStart('debug', pragmas.debug);
  if (!defined(type)) {
    throw new DeveloperError("type is required.");
  }
  //>>includeEnd('debug');

  const key = getInputEventKey(type, modifier);
  return this._inputEvents[key];
};

/**
 * Removes the function to be executed on an input event.
 *
 * @param {ScreenSpaceEventType} type The ScreenSpaceEventType of input event.
 * @param {KeyboardEventModifier} [modifier] A KeyboardEventModifier key that is held when a <code>type</code>
 * event occurs.
 *
 * @see ScreenSpaceEventHandler#getInputAction
 * @see ScreenSpaceEventHandler#setInputAction
 */
ScreenSpaceEventHandler.prototype.removeInputAction = function (
  type,
  modifier
) {
  //>>includeStart('debug', pragmas.debug);
  if (!defined(type)) {
    throw new DeveloperError("type is required.");
  }
  //>>includeEnd('debug');

  const key = getInputEventKey(type, modifier);
  delete this._inputEvents[key];
};

/**
 * Returns true if this object was destroyed; otherwise, false.
 * <br /><br />
 * If this object was destroyed, it should not be used; calling any function other than
 * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
 *
 * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
 *
 * @see ScreenSpaceEventHandler#destroy
 */
ScreenSpaceEventHandler.prototype.isDestroyed = function () {
  return false;
};

/**
 * Removes listeners held by this object.
 * <br /><br />
 * Once an object is destroyed, it should not be used; calling any function other than
 * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.  Therefore,
 * assign the return value (<code>undefined</code>) to the object as done in the example.
 *
 * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
 *
 *
 * @example
 * handler = handler && handler.destroy();
 *
 * @see ScreenSpaceEventHandler#isDestroyed
 */
ScreenSpaceEventHandler.prototype.destroy = function () {
  unregisterListeners(this);

  return destroyObject(this);
};

/**
 * The amount of time, in milliseconds, that mouse events will be disabled after
 * receiving any touch events, such that any emulated mouse events will be ignored.
 * @type {number}
 * @default 800
 */
ScreenSpaceEventHandler.mouseEmulationIgnoreMilliseconds = 800;

/**
 * The amount of time, in milliseconds, before a touch on the screen becomes a
 * touch and hold.
 * @type {number}
 * @default 1500
 */
ScreenSpaceEventHandler.touchHoldDelayMilliseconds = 1500;
export default ScreenSpaceEventHandler;

CustomScreenSpaceEventHandler.js

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions