Change camera position

Open in CodeSandbox

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {LOCATION} from '../variables';
      import {DEG_TO_RAD} from './common';

      window.map = null;

      main();

      async function main() {
          // Waiting for all api elements to be loaded
          await mappable.ready;
          const {MMap, MMapDefaultSchemeLayer, MMapListener, MMapControls, MMapControl} = mappable;

          // Initialize the map
          map = new MMap(
              // Pass the link to the HTMLElement of the container
              document.getElementById('app'),
              // Pass the map initialization parameters
              {location: LOCATION, showScaleInCopyrights: true, camera: {tilt: 40 * DEG_TO_RAD}},
              // Add a map scheme layer
              [new MMapDefaultSchemeLayer({})]
          );

          let hasAutoRotate = true;
          let frame = 0;

          function onActionStartHandler() {
              // Turn off the auto-rotation of the map flag at any event
              hasAutoRotate = false;
              const switchInput = document.getElementById('switch');
              (switchInput as HTMLInputElement).checked = false;
          }

          // Creating a listener object
          const mapListener = new MMapListener({
              layer: 'any',
              // Adding an onActionStart handler to the listener
              onActionStart: onActionStartHandler
          });
          map.addChild(mapListener);

          // Add a shared container for MMapControlButton's and add it to the map
          const controls = new MMapControls({position: 'top right'});
          map.addChild(controls);

          // Automatically rotate the camera
          function startAutoRotationCamera() {
              if (hasAutoRotate) {
                  //  Divide degrees by 100 to slow rotation to ~10 degrees / sec
                  map.update({camera: {azimuth: map.azimuth + (10 * DEG_TO_RAD) / 120}});
                  // Request the next frame of the animation
                  frame = requestAnimationFrame(startAutoRotationCamera);
              } else {
                  // If the automatic rotation mode is stopped then cancel the request for the next animation frame
                  cancelAnimationFrame(frame);
              }
          }

          startAutoRotationCamera();

          // Add MMapControlButton's that will change the camera position when clicked
          function autoRotateBtnHandler() {
              hasAutoRotate = !hasAutoRotate;
              startAutoRotationCamera();
          }

          function rotateLeftBtnHandler() {
              onActionStartHandler();
              // Rotate the camera 30 degrees to the left (degrees need to be converted to radians)
              map.update({camera: {azimuth: map.azimuth - 30 * DEG_TO_RAD, duration: 250}});
          }

          function rotateRightBtnHandler() {
              onActionStartHandler();
              // Rotate the camera 30 degrees to the right (degrees need to be converted to radians)
              map.update({camera: {azimuth: map.azimuth + 30 * DEG_TO_RAD, duration: 250}});
          }

          function upTiltBtnHandler() {
              onActionStartHandler();
              // Tilt the camera up 10 degrees (degrees need to be converted to radians)
              map.update({camera: {tilt: map.tilt - 10 * DEG_TO_RAD, duration: 250}});
          }

          function downTiltBtnHandler() {
              onActionStartHandler();
              // Tilt the camera down 10 degrees (degrees need to be converted to radians)
              map.update({camera: {tilt: map.tilt + 10 * DEG_TO_RAD, duration: 250}});
          }

          // Create a custom control class for map camera position management
          interface CameraPositionControlProps {
              downTiltBtnHandler: () => void;

              rotateRightBtnHandler: () => void;

              upTiltBtnHandler: () => void;

              rotateLeftBtnHandler: () => void;

              autoRotateBtnHandler: () => void;
          }

          class CameraPositionControl extends mappable.MMapComplexEntity<CameraPositionControlProps> {
              private _element: HTMLDivElement;

              private _detachDom: () => void;

              // Method for create a DOM control element
              _createElement(props: CameraPositionControlProps) {
                  const {
                      downTiltBtnHandler,
                      rotateRightBtnHandler,
                      upTiltBtnHandler,
                      rotateLeftBtnHandler,
                      autoRotateBtnHandler
                  } = props;

                  const controlContainer = document.createElement('div');
                  controlContainer.classList.add('control');

                  const controlRotateElement = document.createElement('div');
                  controlRotateElement.classList.add('control__auto-rotate');

                  const controlRotateTextElement = document.createElement('p');
                  controlRotateTextElement.classList.add('auto-rotate__text');
                  controlRotateTextElement.innerText = 'Auto Rotate';

                  const controlRotateSwitchElement = document.createElement('label');
                  controlRotateSwitchElement.classList.add('switch');

                  const controlRotateSwitchInput = document.createElement('input');
                  controlRotateSwitchInput.type = 'checkbox';
                  controlRotateSwitchInput.onchange = autoRotateBtnHandler;
                  controlRotateSwitchInput.checked = true;
                  controlRotateSwitchInput.id = 'switch';

                  const controlRotateSwitchInputSpan = document.createElement('span');
                  controlRotateSwitchInputSpan.classList.add('slider');

                  controlRotateSwitchElement.appendChild(controlRotateSwitchInput);
                  controlRotateSwitchElement.appendChild(controlRotateSwitchInputSpan);

                  controlRotateElement.appendChild(controlRotateTextElement);
                  controlRotateElement.appendChild(controlRotateSwitchElement);

                  const controlDividerElement = document.createElement('div');
                  controlDividerElement.classList.add('control__divider');

                  const controlButtonsElement = document.createElement('div');
                  controlButtonsElement.classList.add('control__buttons');

                  const controlButtonsBlockFirst = document.createElement('div');
                  controlButtonsBlockFirst.classList.add('buttons__block');

                  const controlButtonTiltForward = document.createElement('button');
                  controlButtonTiltForward.classList.add('button');
                  controlButtonTiltForward.classList.add('tilt-forward');
                  controlButtonTiltForward.onclick = upTiltBtnHandler;
                  controlButtonTiltForward.innerText = 'tilt forward';

                  controlButtonsBlockFirst.appendChild(controlButtonTiltForward);

                  const controlButtonsBlockSecond = document.createElement('div');
                  controlButtonsBlockSecond.classList.add('buttons__block');

                  const controlButtonRotateLeft = document.createElement('button');
                  controlButtonRotateLeft.classList.add('button');
                  controlButtonRotateLeft.classList.add('rotate-left');
                  controlButtonRotateLeft.onclick = rotateLeftBtnHandler;
                  controlButtonRotateLeft.innerText = 'rotate left';

                  const controlButtonTiltBack = document.createElement('button');
                  controlButtonTiltBack.classList.add('button');
                  controlButtonTiltBack.classList.add('tilt-back');
                  controlButtonTiltBack.onclick = downTiltBtnHandler;
                  controlButtonTiltBack.innerText = 'tilt back';

                  const controlButtonRotateRight = document.createElement('button');
                  controlButtonRotateRight.classList.add('button');
                  controlButtonRotateRight.classList.add('rotate-right');
                  controlButtonRotateRight.onclick = rotateRightBtnHandler;
                  controlButtonRotateRight.innerText = 'rotate right';

                  controlButtonsBlockSecond.appendChild(controlButtonRotateLeft);
                  controlButtonsBlockSecond.appendChild(controlButtonTiltBack);
                  controlButtonsBlockSecond.appendChild(controlButtonRotateRight);

                  controlButtonsElement.appendChild(controlButtonsBlockFirst);
                  controlButtonsElement.appendChild(controlButtonsBlockSecond);

                  controlContainer.appendChild(controlRotateElement);
                  controlContainer.appendChild(controlDividerElement);
                  controlContainer.appendChild(controlButtonsElement);

                  return controlContainer;
              }

              // Method for attaching the control to the map
              _onAttach() {
                  this._element = this._createElement(this._props);
                  this._detachDom = mappable.useDomContext(this, this._element, this._element);
              }

              // Method for detaching control from the map
              _onDetach() {
                  this._detachDom();
                  this._detachDom = null;
                  this._element = null;
              }
          }

          const control = new MMapControl();

          control.addChild(
              new CameraPositionControl({
                  downTiltBtnHandler,
                  autoRotateBtnHandler,
                  rotateLeftBtnHandler,
                  rotateRightBtnHandler,
                  upTiltBtnHandler
              })
          );

          controls.addChild(control);
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import {LOCATION} from '../variables';
      import {DEG_TO_RAD} from './common';

      window.map = null;

      main();

      async function main() {
        // For each object in the JS API, there is a React counterpart
        // To use the React version of the API, include the module @mappable-world/mappable-reactify
        const [mappableReact] = await Promise.all([
          mappable.import('@mappable-world/mappable-reactify'),
          mappable.ready
        ]);
        const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
        const {MMap, MMapDefaultSchemeLayer, MMapListener, MMapControls, MMapControl} = reactify.module(mappable);
        const {useState, useEffect, useRef, useCallback} = React;

        function App() {
          const [hasAutoRotate, setAutoRotate] = useState(true);
          const [mapAzimuth, setMapAzimuth] = useState(0);
          const [mapTilt, setMapTilt] = useState(40 * DEG_TO_RAD);
          const frame = useRef < number > null;

          // Automatically rotate the camera
          const startAutoRotationCamera = useCallback(() => {
            if (hasAutoRotate) {
              //  Divide degrees by 100 to slow rotation to ~20 degrees / sec
              setMapAzimuth(map.azimuth + (10 * DEG_TO_RAD) / 100);
              // Request the next frame of the animation
              frame.current = requestAnimationFrame(startAutoRotationCamera);
            } else {
              // If the automatic rotation mode is stopped then cancel the request for the next animation frame
              cancelAnimationFrame(frame.current);
            }
          }, [hasAutoRotate]);

          useEffect(() => {
            // Update ref to new animation frame ID
            frame.current = requestAnimationFrame(startAutoRotationCamera);

            // Kill animation cycle on component unmount
            return () => cancelAnimationFrame(frame.current);
          }, [startAutoRotationCamera]);

          const onActionStartHandler = useCallback(() => {
            // Turn off the auto-rotation of the map flag at any event
            setAutoRotate(false);
          }, []);

          const autoRotateBtnHandler = useCallback(() => {
            setAutoRotate((prevAutoRotate) => !prevAutoRotate);
          }, []);

          const rotateLeftBtnHandler = useCallback(() => {
            onActionStartHandler();
            // Rotate the camera 30 degrees to the left (degrees need to be converted to radians)
            setMapAzimuth(map.azimuth - 30 * DEG_TO_RAD);
          }, []);

          const rotateRightBtnHandler = useCallback(() => {
            onActionStartHandler();
            // Rotate the camera 30 degrees to the right (degrees need to be converted to radians)
            setMapAzimuth(map.azimuth + 30 * DEG_TO_RAD);
          }, []);

          const tiltUpBtnHandler = useCallback(() => {
            onActionStartHandler();
            // Tilt the camera up 10 degrees (degrees need to be converted to radians)
            setMapTilt(map.tilt - 10 * DEG_TO_RAD);
          }, []);

          const tiltDownBtnHandler = useCallback(() => {
            onActionStartHandler();
            // Tilt the camera down 10 degrees (degrees need to be converted to radians)
            setMapTilt(map.tilt + 10 * DEG_TO_RAD);
          }, []);

          return (
            // Initialize the map and pass initialization parameters
            <MMap
              location={LOCATION}
              showScaleInCopyrights={true}
              camera={{azimuth: mapAzimuth, tilt: mapTilt, duration: hasAutoRotate ? 0 : 250}}
              ref={(x) => (map = x)}
            >
              {/* Add a map scheme layer */}
              <MMapDefaultSchemeLayer />

              {/* Creating a listener component and adding an onActionStart handler to it */}
              <MMapListener onActionStart={onActionStartHandler} />

              {/* Add a shared container for MMapControlButton's */}
              <MMapControls position="top right" orientation="vertical">
                {/* Add MMapControlButton's that will change the camera position when clicked */}
                <MMapControl>
                  <div className="control">
                    <div className="control__auto-rotate">
                      <p className="auto-rotate__text">Auto Rotate</p>
                      <label className="switch">
                        <input type="checkbox" checked={hasAutoRotate} onChange={autoRotateBtnHandler} />
                        <span className="slider" />
                      </label>
                    </div>

                    <div className="control__divider" />

                    <div className="control__buttons">
                      <div className="buttons__block">
                        <button className="button tilt-forward" onClick={tiltUpBtnHandler}>
                          tilt forward
                        </button>
                      </div>
                      <div className="buttons__block">
                        <button className="button rotate-left" onClick={rotateLeftBtnHandler}>
                          rotate left
                        </button>
                        <button className="button tilt-back" onClick={tiltDownBtnHandler}>
                          tilt back
                        </button>
                        <button className="button rotate-right" onClick={rotateRightBtnHandler}>
                          rotate right
                        </button>
                      </div>
                    </div>
                  </div>
                </MMapControl>
              </MMapControls>
            </MMap>
          );
        }

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('app')
        );
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {LOCATION} from '../variables';
      import {DEG_TO_RAD} from './common';

      window.map = null;

      async function main() {
        // For each object in the JS API, there is a Vue counterpart
        // To use the Vue version of the API, include the module @mappable-world/mappable-vuefy
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {MMap, MMapDefaultSchemeLayer, MMapListener, MMapControls, MMapControl, MMapControlButton} =
          vuefy.module(mappable);

        const app = Vue.createApp({
          setup() {
            const location = Vue.ref(LOCATION);
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            const hasAutoRotate = Vue.ref(true);
            const mapAzimuth = Vue.ref(0);
            const mapTilt = Vue.ref(40 * DEG_TO_RAD);
            let frameId;

            const autoRotateCamera = () => {
              if (hasAutoRotate.value) {
                mapAzimuth.value += (10 * DEG_TO_RAD) / 100;
                frameId = requestAnimationFrame(autoRotateCamera);
              }
            };

            Vue.onMounted(() => {
              frameId = requestAnimationFrame(autoRotateCamera);
            });

            Vue.onUnmounted(() => {
              cancelAnimationFrame(frameId);
            });

            const onActionStartHandler = () => {
              hasAutoRotate.value = false;
            };

            const autoRotateBtnHandler = () => {
              hasAutoRotate.value = !hasAutoRotate.value;
              frameId = requestAnimationFrame(autoRotateCamera);
            };

            const rotateLeftBtnHandler = () => {
              onActionStartHandler();
              mapAzimuth.value = map.azimuth - 30 * DEG_TO_RAD;
            };

            const rotateRightBtnHandler = () => {
              onActionStartHandler();
              mapAzimuth.value = map.azimuth + 30 * DEG_TO_RAD;
            };

            const tiltUpBtnHandler = () => {
              onActionStartHandler();
              mapTilt.value = map.tilt - 10 * DEG_TO_RAD;
            };

            const tiltDownBtnHandler = () => {
              onActionStartHandler();
              mapTilt.value = map.tilt + 10 * DEG_TO_RAD;
            };

            return {
              location,
              refMap,
              hasAutoRotate,
              mapAzimuth,
              mapTilt,
              onActionStartHandler,
              autoRotateBtnHandler,
              rotateLeftBtnHandler,
              rotateRightBtnHandler,
              tiltUpBtnHandler,
              tiltDownBtnHandler
            };
          },
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapListener,
            MMapControls,
            MMapControl,
            MMapControlButton
          },
          template: `
      <MMap
        :location="location"
        :camera="{ azimuth: mapAzimuth, tilt: mapTilt, duration: hasAutoRotate ? 0 : 250 }"
        :ref="refMap"
      >
        <MMapDefaultSchemeLayer/>
        <MMapListener @action-start="onActionStartHandler"/>

        <MMapControls position="top right">
          <MMapControl>
            <div class="control">
              <div class="control__auto-rotate">
                <p class="auto-rotate__text">Auto Rotate</p>
                <label class="switch">
                  <input type="checkbox" :checked="hasAutoRotate" @change="autoRotateBtnHandler"/>
                  <span class="slider"/>
                </label>
              </div>

              <div class="control__divider"/>

              <div class="control__buttons">
                <div class="buttons__block">
                  <button class="button tilt-forward" @click="tiltUpBtnHandler">
                    tilt forward
                  </button>
                </div>
                <div class="buttons__block">
                  <button class="button rotate-left" @click="rotateLeftBtnHandler">
                    rotate left
                  </button>
                  <button class="button tilt-back" @click="tiltDownBtnHandler">
                    tilt back
                  </button>
                  <button class="button rotate-right" @click="rotateRightBtnHandler">
                    rotate right
                  </button>
                </div>
              </div>
            </div>
          </MMapControl>
        </MMapControls>
      </MMap>
    `
        });

        app.mount('#app');
      }

      main();
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.control {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.control__auto-rotate {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
}

.auto-rotate__text {
  font-size: 16px;
  color: #050d33;
  font-weight: 500;
}

.control__divider {
  width: 100%;
  height: 1px;
  background-color: #5c5e661a;
  margin: 8px 0;
  border: none;
}

.control__buttons {
  display: flex;
  flex-direction: column;
}

.buttons__block {
  display: flex;
  flex-direction: row;
  justify-content: center;
}

.button {
  color: #050d33;
  font-size: 12px;
  margin: 2px;
  display: flex;
  width: 84px;
  height: 68px;
  padding: 34px 8px 10px 8px;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 6px;
  border-radius: 12px;
  background-color: rgba(92, 94, 102, 0.06);
  border: none;
  cursor: pointer;
  background-repeat: no-repeat;
  background-position: center 30%;
}

.button.tilt-forward {
  background-image: url('./tilt-forward.svg?inline');
}
.button.tilt-back {
  background-image: url('./tilt-back.svg?inline');
}
.button.rotate-left {
  background-image: url('./rotate-left.svg?inline');
}
.button.rotate-right {
  background-image: url('./rotate-right.svg?inline');
}

/* The switch - the box around the slider */
.switch {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 20px;
}

/* Hide default HTML checkbox */
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

/* The slider */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: 0.4s;
  transition: 0.4s;
  border-radius: 34px;
}

.slider:before {
  position: absolute;
  content: '';
  height: 16px;
  width: 16px;
  left: 2px;
  bottom: 2px;
  background-color: white;
  -webkit-transition: 0.4s;
  transition: 0.4s;
  border-radius: 50%;
}

input:checked + .slider {
  background-color: var(--interact-action);
}

input:focus + .slider {
  box-shadow: 0 0 1px var(--interact-action);
}

input:checked + .slider:before {
  -webkit-transform: translateX(14px);
  -ms-transform: translateX(14px);
  transform: translateX(14px);
}
export const DEG_TO_RAD = Math.PI / 180;
:root {
  --interact-action: #122db2;
}
import type {MMapLocationRequest} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [55.274, 25.197], // starting position [lng, lat]
  zoom: 16.5 // starting zoom
};