Change camera position
vanilla.html
react.html
vue.html
common.css
common.ts
variables.css
variables.ts
<!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"></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
};