Hitting a point in a circle

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>
        <script crossorigin src="https://cdn.jsdelivr.net/npm/@turf/turf@7.1.0/turf.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 {
                ACTIVE_MARKER_COLOR,
                CIRCLE_CENTER,
                CIRCLE_STYLE,
                HIDDEN_CIRCLE_STYLE,
                INNER_CIRCLE_RADIUS,
                INNER_CIRCLE_STYLE,
                LOCATION,
                MARKERS_COORDINATES,
                SLIDER_COLOR
            } from '../variables';
            import {
                CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT,
                CIRCLE_RADIUS,
                DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT,
                getBooleanPointInPolygon,
                getCenterOfCircle,
                getCircleGeoJSON,
                getDistanceBetweenPoints,
                getSliderPercent,
                getSliderValueHeight
            } from './common';
            import type {DomEventHandler, LngLat} from '@mappable-world/mappable-types';
            import type {MMapFeatureEventHandler} from '@mappable-world/mappable-types/imperative/MMapFeature/types';
            
            window.map = null;
            
            interface InfoMessageProps {
                text: string;
            }
            
            main();
            
            async function main() {
                // Waiting for all api elements to be loaded
                await mappable.ready;
                const {
                    MMap,
                    MMapDefaultSchemeLayer,
                    MMapDefaultFeaturesLayer,
                    MMapFeature,
                    MMapListener,
                    MMapControl,
                    MMapControls
                } = mappable;
                const {MMapDefaultMarker} = await mappable.import('@mappable-world/mappable-default-ui-theme');
            
                const markers = [];
                let showCircle = null;
                let innerCircle = null;
                let hiddenCircleChangeSize = null;
                let hiddenCircleDraggable = null;
                let currentMouseCoordinates: LngLat = CIRCLE_CENTER;
                let circleRadius = CIRCLE_RADIUS;
                let circleCenter = CIRCLE_CENTER;
            
                class InfoMessageClass extends mappable.MMapComplexEntity<InfoMessageProps> {
                    private _element!: HTMLDivElement;
            
                    private _detachDom!: () => void;
            
                    // Method for create a DOM control element
                    _createElement(props: InfoMessageProps) {
                        // Create a root element
                        const infoWindow = document.createElement('div');
                        infoWindow.classList.add('info-window');
                        infoWindow.innerHTML = props.text;
            
                        return infoWindow;
                    }
            
                    // 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 = undefined;
                        this._element = undefined;
                    }
                }
            
                class RadiusControlClass extends mappable.MMapComplexEntity<{}> {
                    private _element!: HTMLDivElement;
            
                    private _detachDom!: () => void;
            
                    // Method for create a DOM control element
                    _createElement() {
                        // Create a root element
                        const container = document.createElement('div');
                        container.classList.add('container');
            
                        const innerContainer = document.createElement('div');
            
                        const upperControlText = document.createElement('span');
                        upperControlText.classList.add('control-text');
                        upperControlText.innerText = '10 km';
            
                        const inputControl = document.createElement('input');
                        inputControl.type = 'range';
                        inputControl.oninput = onSliderChange;
                        inputControl.min = '10';
                        inputControl.max = '100';
                        inputControl.value = (circleRadius * 10).toString();
                        inputControl.classList.add('slider');
                        inputControl.id = 'slider';
            
                        const downControlText = document.createElement('span');
                        downControlText.classList.add('control-text');
                        downControlText.innerText = '1 km';
            
                        innerContainer.appendChild(upperControlText);
                        innerContainer.appendChild(inputControl);
                        innerContainer.appendChild(downControlText);
            
                        const innerContainerSliderValue = document.createElement('div');
                        const sliderValue = document.createElement('span');
                        sliderValue.innerText = circleRadius.toFixed() + ' km';
                        sliderValue.classList.add('slider__value');
                        sliderValue.id = 'slider-value';
                        innerContainerSliderValue.appendChild(sliderValue);
            
                        container.appendChild(innerContainer);
                        container.appendChild(innerContainerSliderValue);
                        return container;
                    }
            
                    // Method for attaching the control to the map
                    _onAttach() {
                        this._element = this._createElement();
                        this._detachDom = mappable.useDomContext(this, this._element, this._element);
                    }
            
                    // Method for detaching control from the map
                    _onDetach() {
                        this._detachDom();
                        this._detachDom = undefined;
                        this._element = undefined;
                    }
                }
            
                // 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},
                    // Add a map scheme layer
                    [new MMapDefaultSchemeLayer({}), new MMapDefaultFeaturesLayer({})]
                );
            
                const updateGeometries = () => {
                    const circleGeometry = getCircleGeoJSON(circleCenter, circleRadius);
                    const innerCircleGeometry = getCircleGeoJSON(circleCenter, INNER_CIRCLE_RADIUS);
                    const hiddenCircleChangeSizeGeometry = getCircleGeoJSON(
                        circleCenter,
                        circleRadius * CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT
                    );
                    const hiddenCircleDraggableGeometry = getCircleGeoJSON(
                        circleCenter,
                        circleRadius * DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT
                    );
                    showCircle.update({geometry: circleGeometry});
                    innerCircle.update({geometry: innerCircleGeometry});
                    hiddenCircleChangeSize.update({geometry: hiddenCircleChangeSizeGeometry});
                    hiddenCircleDraggable.update({geometry: hiddenCircleDraggableGeometry});
                    return circleGeometry;
                };
            
                const updateMarkers = (circleCoordinates: LngLat[][]) => {
                    markers.forEach((marker) => {
                        const inPolygon = getBooleanPointInPolygon(circleCoordinates, marker.coordinates);
                        marker.update({color: inPolygon ? {day: ACTIVE_MARKER_COLOR, night: ACTIVE_MARKER_COLOR} : undefined});
                    });
                };
            
                const updateSlider = () => {
                    const slider = document.getElementById('slider');
                    const sliderValue = document.getElementById('slider-value');
                    if (slider) {
                        (slider as HTMLInputElement).value = (circleRadius * 10).toString();
                        sliderValue.innerText = circleRadius.toFixed() + ' km';
                        const sliderHeight = slider.offsetHeight;
                        const percent = getSliderPercent(circleRadius, 10, 1);
                        const offset = getSliderValueHeight(sliderHeight, 16, percent);
            
                        sliderValue.style.top = `${offset}px`;
                        slider.style.background = `linear-gradient(to top, ${SLIDER_COLOR} ${percent}%, rgba(246, 246, 246, 1) ${percent}%)`;
                    }
                };
            
                const onHiddenCircleChangeSizeDragMove: MMapFeatureEventHandler = () => {
                    const edgePoint = getDistanceBetweenPoints(circleCenter, currentMouseCoordinates);
                    if (edgePoint <= 10 && edgePoint >= 1) {
                        circleRadius = edgePoint;
                        const showCircleCoordinates = updateGeometries();
                        updateMarkers(showCircleCoordinates.coordinates);
                        updateSlider();
                    }
                    return false;
                };
            
                const onHiddenCircleDraggableDragMove: MMapFeatureEventHandler = (coordinates: LngLat[][]) => {
                    circleCenter = getCenterOfCircle(coordinates);
                    const showCircleCoordinates = updateGeometries();
                    updateMarkers(showCircleCoordinates.coordinates);
                    return false;
                };
            
                const onMouseMove: DomEventHandler = (_, event) => {
                    currentMouseCoordinates = event.coordinates;
                };
            
                const onSliderChange = (event) => {
                    circleRadius = parseInt(event.target.value) / 10;
                    const circleCoordinates = updateGeometries();
                    updateMarkers(circleCoordinates.coordinates);
                    updateSlider();
                };
            
                const showCircleGeometry = getCircleGeoJSON(circleCenter, circleRadius);
            
                showCircle = new MMapFeature({
                    geometry: showCircleGeometry,
                    style: CIRCLE_STYLE
                });
                map.addChild(showCircle);
            
                innerCircle = new MMapFeature({
                    geometry: getCircleGeoJSON(circleCenter, INNER_CIRCLE_RADIUS),
                    style: INNER_CIRCLE_STYLE
                });
                map.addChild(innerCircle);
            
                hiddenCircleChangeSize = new MMapFeature({
                    geometry: getCircleGeoJSON(circleCenter, circleRadius * CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT),
                    style: {...HIDDEN_CIRCLE_STYLE, zIndex: 10, cursor: 'pointer'},
                    draggable: true,
                    onDragMove: onHiddenCircleChangeSizeDragMove
                });
                map.addChild(hiddenCircleChangeSize);
            
                hiddenCircleDraggable = new MMapFeature({
                    geometry: getCircleGeoJSON(circleCenter, circleRadius * DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT),
                    style: {...HIDDEN_CIRCLE_STYLE, zIndex: 11, cursor: 'move'},
                    draggable: true,
                    onDragMove: onHiddenCircleDraggableDragMove
                });
                map.addChild(hiddenCircleDraggable);
            
                map.addChild(
                    new MMapListener({
                        onMouseMove
                    })
                );
            
                MARKERS_COORDINATES.forEach((markerProps) => {
                    const inPolygon = getBooleanPointInPolygon(showCircleGeometry.coordinates, markerProps.coordinates);
                    const marker = new MMapDefaultMarker({
                        coordinates: markerProps.coordinates,
                        iconName: 'fallback',
                        color: inPolygon ? {day: ACTIVE_MARKER_COLOR, night: ACTIVE_MARKER_COLOR} : undefined,
                        size: 'normal'
                    });
                    markers.push(marker);
                    map.addChild(marker);
                });
            
                const control = new MMapControl({}).addChild(new RadiusControlClass({}));
                map.addChild(new MMapControls({position: 'right'}, [control]));
            
                map.addChild(
                    new MMapControls({position: 'top left'}, [
                        new InfoMessageClass({text: 'Move a circle and change radius of it'})
                    ])
                );
            }
        </script>

        <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>
        <script crossorigin src="https://cdn.jsdelivr.net/npm/@turf/turf@7.1.0/turf.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 type {MarkerColorProps} from '@mappable-world/mappable-default-ui-theme';
            import type {DomEventHandler, LngLat, PolygonGeometry} from '@mappable-world/mappable-types';
            import type {ChangeEvent} from 'react';
            import {
                CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT,
                CIRCLE_RADIUS,
                DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT,
                getBooleanPointInPolygon,
                getCenterOfCircle,
                getCircleGeoJSON,
                getDistanceBetweenPoints,
                getSliderPercent,
                getSliderValueHeight
            } from './common';
            import {
                ACTIVE_MARKER_COLOR,
                CIRCLE_CENTER,
                CIRCLE_STYLE,
                HIDDEN_CIRCLE_STYLE,
                INNER_CIRCLE_RADIUS,
                INNER_CIRCLE_STYLE,
                LOCATION,
                MARKERS_COORDINATES,
                SLIDER_COLOR
            } from '../variables';
            
            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,
                    MMapDefaultFeaturesLayer,
                    MMapFeature,
                    MMapListener,
                    MMapControls,
                    MMapControl
                } = reactify.module(mappable);
                const {MMapDefaultMarker} = reactify.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));
                const {useMemo, useState, useCallback, useEffect} = React;
            
                function App() {
                    const [currentMouseCoordinates, setCurrentMouseCoordinates] = useState<LngLat>();
                    const [circleRadius, setCircleRadius] = useState(CIRCLE_RADIUS);
                    const [circleCenter, setCircleCenter] = useState(CIRCLE_CENTER);
            
                    const circleGeometry = useMemo(
                        () => getCircleGeoJSON(circleCenter, circleRadius),
                        [circleCenter, circleRadius]
                    );
                    const hiddenCircleChangeSizeGeometry = useMemo(
                        () => getCircleGeoJSON(circleCenter, circleRadius * CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT),
                        [circleCenter, circleRadius]
                    );
                    const innerCircleGeometry = useMemo(
                        () => getCircleGeoJSON(circleCenter, INNER_CIRCLE_RADIUS),
                        [circleCenter, circleRadius]
                    );
                    const hiddenCircleDraggableGeometry = useMemo(
                        () => getCircleGeoJSON(circleCenter, circleRadius * DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT),
                        [circleCenter, circleRadius]
                    );
            
                    const onHiddenCircleChangeSizeDragMove = useCallback(() => {
                        const edgePoint = getDistanceBetweenPoints(circleCenter, currentMouseCoordinates);
                        if (edgePoint <= 10 && edgePoint >= 1) {
                            setCircleRadius(edgePoint);
                        }
                        return false;
                    }, [circleCenter, currentMouseCoordinates]);
            
                    const onHiddenCircleDraggableDragMove = useCallback((coordinates: LngLat[][]) => {
                        const center = getCenterOfCircle(coordinates);
                        setCircleCenter(center);
                        return false;
                    }, []);
            
                    const onMouseMove: DomEventHandler = useCallback((_, event) => {
                        setCurrentMouseCoordinates(event.coordinates);
                    }, []);
            
                    const onSliderChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
                        setCircleRadius(parseInt(event.target.value) / 10);
                    }, []);
            
                    const getMarkerColor = useCallback(
                        (coordinates: LngLat): MarkerColorProps => {
                            const inPolygon = getBooleanPointInPolygon(circleGeometry.coordinates, coordinates);
                            return inPolygon ? {day: ACTIVE_MARKER_COLOR, night: ACTIVE_MARKER_COLOR} : undefined;
                        },
                        [circleGeometry]
                    );
            
                    useEffect(() => {
                        const slider = document.getElementById('slider');
                        const sliderValue = document.getElementById('slider-value');
                        if (slider) {
                            const sliderHeight = slider.offsetHeight;
                            const percent = getSliderPercent(circleRadius, 10, 1);
                            const offset = getSliderValueHeight(sliderHeight, 16, percent);
            
                            sliderValue.style.top = `${offset}px`;
                            slider.style.background = `linear-gradient(to top, ${SLIDER_COLOR} ${percent}%, rgba(246, 246, 246, 1) ${percent}%)`;
                        }
                    }, [circleRadius]);
            
                    return (
                        // Initialize the map and pass initialization parameters
                        <MMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
                            {/* Add a map scheme layer */}
                            <MMapDefaultSchemeLayer />
                            <MMapDefaultFeaturesLayer />
                            <MMapFeature geometry={circleGeometry as PolygonGeometry} style={CIRCLE_STYLE} />
                            <MMapFeature geometry={innerCircleGeometry as PolygonGeometry} style={INNER_CIRCLE_STYLE} />
            
                            <MMapFeature
                                draggable
                                onDragMove={onHiddenCircleChangeSizeDragMove}
                                geometry={hiddenCircleChangeSizeGeometry as PolygonGeometry}
                                style={{...HIDDEN_CIRCLE_STYLE, zIndex: 10, cursor: 'pointer'}}
                            />
            
                            <MMapFeature
                                draggable
                                onDragMove={onHiddenCircleDraggableDragMove}
                                geometry={hiddenCircleDraggableGeometry as PolygonGeometry}
                                style={{...HIDDEN_CIRCLE_STYLE, zIndex: 11, cursor: 'move'}}
                            />
            
                            {MARKERS_COORDINATES.map((marker) => (
                                <MMapDefaultMarker
                                    coordinates={marker.coordinates}
                                    size="normal"
                                    iconName="fallback"
                                    color={getMarkerColor(marker.coordinates)}
                                />
                            ))}
            
                            <MMapControls position="right">
                                <MMapControl>
                                    <div className="container">
                                        <div>
                                            <span className="control-text">10 km</span>
                                            <input
                                                type="range"
                                                min="10"
                                                onChange={onSliderChange}
                                                max="100"
                                                value={circleRadius * 10}
                                                className="slider"
                                                id="slider"
                                            />
                                            <span className="control-text">1 km</span>
                                        </div>
                                        <span className="slider__value" id="slider-value">
                                            {circleRadius.toFixed()} km
                                        </span>
                                    </div>
                                </MMapControl>
                            </MMapControls>
            
                            <MMapControls position="top left">
                                <MMapControl transparent>
                                    <div className="info-window">Move a circle and change radius of it</div>
                                </MMapControl>
                            </MMapControls>
            
                            <MMapListener onMouseMove={onMouseMove} />
                        </MMap>
                    );
                }
            
                ReactDOM.render(
                    <React.StrictMode>
                        <App />
                    </React.StrictMode>,
                    document.getElementById('app')
                );
            }
        </script>

        <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>
        <script crossorigin src="https://cdn.jsdelivr.net/npm/@turf/turf@7.1.0/turf.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 {
                ACTIVE_MARKER_COLOR,
                CIRCLE_CENTER,
                CIRCLE_STYLE,
                HIDDEN_CIRCLE_STYLE,
                INNER_CIRCLE_RADIUS,
                INNER_CIRCLE_STYLE,
                LOCATION,
                MARKERS_COORDINATES,
                SLIDER_COLOR
            } from '../variables';
            import {
                CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT,
                CIRCLE_RADIUS,
                DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT,
                getBooleanPointInPolygon,
                getCenterOfCircle,
                getCircleGeoJSON,
                getDistanceBetweenPoints,
                getSliderPercent,
                getSliderValueHeight
            } from './common';
            import type {DomEventHandler, LngLat} from '@mappable-world/mappable-types';
            import type {ChangeEvent} from 'react';
            import type {MarkerColorProps} from '@mappable-world/mappable-default-ui-theme';
            
            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,
                    MMapDefaultFeaturesLayer,
                    MMapFeature,
                    MMapControls,
                    MMapControl,
                    MMapListener
                } = vuefy.module(mappable);
                const {MMapDefaultMarker} = vuefy.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));
            
                const app = Vue.createApp({
                    components: {
                        MMap,
                        MMapDefaultSchemeLayer,
                        MMapDefaultFeaturesLayer,
                        MMapFeature,
                        MMapDefaultMarker,
                        MMapControls,
                        MMapControl,
                        MMapListener
                    },
                    setup() {
                        const refMap = (ref) => {
                            window.map = ref?.entity;
                        };
                        const currentMouseCoordinates = Vue.ref();
                        const circleRadius = Vue.ref(CIRCLE_RADIUS);
                        const circleCenter = Vue.ref<LngLat>(CIRCLE_CENTER);
            
                        const circleRadiusFixed = Vue.computed(() => circleRadius.value.toFixed());
            
                        const circleGeometry = Vue.computed(() => getCircleGeoJSON(circleCenter.value, circleRadius.value));
                        const innerCircleGeometry = Vue.computed(() => getCircleGeoJSON(circleCenter.value, INNER_CIRCLE_RADIUS));
                        const hiddenCircleChangeSizeGeometry = Vue.computed(() =>
                            getCircleGeoJSON(circleCenter.value, circleRadius.value * CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT)
                        );
                        const hiddenCircleDraggableGeometry = Vue.computed(() =>
                            getCircleGeoJSON(circleCenter.value, circleRadius.value * DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT)
                        );
            
                        const onHiddenCircleChangeSizeDragMove = () => {
                            const edgePoint = getDistanceBetweenPoints(circleCenter.value, currentMouseCoordinates.value);
                            if (edgePoint <= 10 && edgePoint >= 1) {
                                circleRadius.value = edgePoint;
                            }
                            return false;
                        };
            
                        const onHiddenCircleDraggableDragMove = (coordinates: LngLat[][]) => {
                            const center = getCenterOfCircle(coordinates);
                            circleCenter.value = center;
                            return false;
                        };
            
                        const onMouseMove: DomEventHandler = (_, event) => {
                            currentMouseCoordinates.value = event.coordinates;
                        };
            
                        const onSliderChange = (event: ChangeEvent<HTMLInputElement>) => {
                            circleRadius.value = parseInt(event.target.value) / 10;
                        };
            
                        const getMarkerColor = (coordinates: LngLat): MarkerColorProps => {
                            const inPolygon = getBooleanPointInPolygon(circleGeometry.value.coordinates, coordinates);
                            return inPolygon ? {day: ACTIVE_MARKER_COLOR, night: ACTIVE_MARKER_COLOR} : undefined;
                        };
            
                        Vue.watch(
                            circleRadius,
                            () => {
                                const slider = document.getElementById('slider');
                                const sliderValue = document.getElementById('slider-value');
                                if (slider) {
                                    const sliderHeight = slider.offsetHeight;
                                    const percent = getSliderPercent(circleRadius.value, 10, 1);
                                    const offset = getSliderValueHeight(sliderHeight, 16, percent);
            
                                    sliderValue.style.top = `${offset}px`;
                                    slider.style.background = `linear-gradient(to top, ${SLIDER_COLOR} ${percent}%, rgba(246, 246, 246, 1) ${percent}%)`;
                                }
                            },
                            {immediate: true}
                        );
            
                        return {
                            LOCATION,
                            CIRCLE_STYLE,
                            INNER_CIRCLE_STYLE,
                            HIDDEN_CIRCLE_STYLE,
                            MARKERS_COORDINATES,
                            refMap,
                            circleRadius,
                            circleRadiusFixed,
                            circleGeometry,
                            innerCircleGeometry,
                            hiddenCircleChangeSizeGeometry,
                            hiddenCircleDraggableGeometry,
                            onHiddenCircleChangeSizeDragMove,
                            onHiddenCircleDraggableDragMove,
                            onMouseMove,
                            onSliderChange,
                            getMarkerColor
                        };
                    },
                    template: `
                  <!-- Initialize the map and pass initialization parameters -->
                  <MMap
                    :location="LOCATION"
                    :showScaleInCopyrights="true"
                    :ref="refMap"
                  >
                    <!-- Add a map scheme layer -->
                    <MMapDefaultSchemeLayer />
                    <MMapDefaultFeaturesLayer />
            
                    <MMapFeature :geometry="circleGeometry" :style="CIRCLE_STYLE" />
                    <MMapFeature :geometry="innerCircleGeometry" :style="INNER_CIRCLE_STYLE" />
            
                    <MMapFeature
                      :draggable="true"
                      :onDragMove="onHiddenCircleChangeSizeDragMove"
                      :geometry="hiddenCircleChangeSizeGeometry"
                      :style="{ ...HIDDEN_CIRCLE_STYLE, zIndex: 10, cursor: 'pointer' }"
                    />
            
                    <MMapFeature
                      :draggable="true"
                      :onDragMove="onHiddenCircleDraggableDragMove"
                      :geometry="hiddenCircleDraggableGeometry"
                      :style="{ ...HIDDEN_CIRCLE_STYLE, zIndex: 11, cursor: 'move' }"
                    />
            
                    <template v-for="markerProp of MARKERS_COORDINATES">
                      <MMapDefaultMarker
                        :coordinates="markerProp.coordinates"
                        size="normal"
                        iconName="fallback"
                        :color="getMarkerColor(markerProp.coordinates)"
                      />
                    </template>
            
            
                    <MMapControls position="right">
                      <MMapControl>
                        <div class="container">
                          <div>
                            <span class="control-text">10 km</span>
                            <input
                              type="range"
                              class="slider"
                              id="slider"
                              min="10"
                              @input="onSliderChange"
                              :value="circleRadius * 10"
                              max="100"
                            />
                            <span class="control-text">1 km</span>
                          </div>
                          <span class="slider__value" id="slider-value">{{ circleRadiusFixed }} km</span>
                        </div>
                      </MMapControl>
                    </MMapControls>
            
                    <MMapControls position="top left">
                      <MMapControl :transparent="true">
                        <div class="info-window">
                          Move a circle and change radius of it
                        </div>
                      </MMapControl>
                    </MMapControls>
            
                    <MMapListener :onMouseMove="onMouseMove" />
                  </MMap>
                `
                });
                app.mount('#app');
            }
            
            main();
        </script>

        <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>
import type {DrawingStyle, LngLat, MMapLocationRequest} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
    center: [55.2811, 25.2239], // starting position [lng, lat]
    zoom: 12.6 // starting zoom
};
export const INNER_CIRCLE_RADIUS = 0.1;
export const ACTIVE_MARKER_COLOR = '#2E4CE5';
export const SLIDER_COLOR = '#122db2';
export const CIRCLE_CENTER: LngLat = [55.2811, 25.2239];
export const CIRCLE_STYLE: DrawingStyle = {
    simplificationRate: 0,
    stroke: [{color: '#2E4CE5', width: 3, opacity: 0.6}],
    fill: '#122DB2',
    fillOpacity: 0.1
};
export const HIDDEN_CIRCLE_STYLE: DrawingStyle = {
    simplificationRate: 0,
    stroke: [],
    fill: '#000000',
    fillOpacity: 0
};

export const INNER_CIRCLE_STYLE: DrawingStyle = {
    simplificationRate: 0,
    stroke: [],
    fill: '#2E4CE5',
    fillOpacity: 0.6
};
export const MARKERS_COORDINATES: Array<{coordinates: LngLat}> = [
    {
        coordinates: [55.2701, 25.2368]
    },
    {
        coordinates: [55.2487, 25.2086]
    },
    {
        coordinates: [55.2907, 25.1982]
    },
    {
        coordinates: [55.3126, 25.2278]
    },
    {
        coordinates: [55.3236, 25.2247]
    }
];
:root {
    --slider-color-track: #122db2;
}
import type {LngLat, PolygonGeometry} from '@mappable-world/mappable-types';

declare global {
    const turf: typeof import('@turf/turf');
}

mappable.ready.then(() => {
    mappable.import.registerCdn('https://cdn.jsdelivr.net/npm/{package}', '@mappable-world/mappable-default-ui-theme@0.0');
});

export const CIRCLE_RADIUS = 3;
export const CHANGE_SIZE_CIRCLE_RADIUS_COEFFICIENT = 1.2;
export const DRAGGABLE_CIRCLE_RADIUS_COEFFICIENT = 0.8;

export const getCircleGeoJSON = (center: LngLat, radiusKilometers: number): PolygonGeometry => {
    const {geometry} = turf.circle(center, radiusKilometers, {units: 'kilometers'});
    return geometry as PolygonGeometry;
};

export const getDistanceBetweenPoints = (circleCenter: LngLat, currentMouseCoordinates: LngLat): number => {
    if (!currentMouseCoordinates || !circleCenter) return CIRCLE_RADIUS;
    const from = turf.point(circleCenter);
    const to = turf.point(currentMouseCoordinates);
    return turf.distance(from, to, {units: 'kilometers'});
};

export const getCenterOfCircle = (coordinates: LngLat[][]): LngLat => {
    const polygon = turf.polygon(coordinates);
    const center = turf.center(polygon);
    return center.geometry.coordinates as LngLat;
};

export const getBooleanPointInPolygon = (polygon: LngLat[][], point: LngLat): boolean => {
    const pt = turf.point(point);
    const poly = turf.polygon(polygon);
    return turf.booleanPointInPolygon(pt, poly);
};

export const getSliderPercent = (currentValue: number, max: number, min: number) => {
    const percent = ((currentValue - min) / (max - min)) * 100;
    return percent;
};

export const getSliderValueHeight = (sliderHeight: number, thumbHeight: number, percent: number) => {
    const offset =
        sliderHeight - ((percent / 100) * (sliderHeight - thumbHeight) + thumbHeight / 2) - sliderHeight - 58;
    return offset;
};
.info-window {
    padding: 8px 12px 8px 40px;
    border-radius: 12px;
    background-color: #313133;
    background-image: url('./info-icon.svg');
    background-position: 10px 8px;
    background-repeat: no-repeat;
    color: #f2f5fa;
    font-size: 14px;
    line-height: 20px;
    min-width: max-content;
}

.container {
    height: 396px;
    padding: 8px 4px;
    width: 40px;
    text-align: center;
    box-sizing: border-box;
}

.control-text {
    font-size: 10px;
    color: #c8c9cc;
}

.slider__value {
    position: relative;
    background-color: #ffffff;
    font-size: 10px;
    top: -137px;
}

input[type='range'] {
    height: 332px;
    width: 2px;
    -webkit-appearance: none;
    writing-mode: vertical-lr;
    direction: rtl;
    margin: 4px 16px;
    background: linear-gradient(to top, var(--slider-color-track) 25%, #f6f6f6 25%);
}

input[type='range']:focus {
    outline: none;
}

input[type='range']::-webkit-slider-runnable-track {
    width: 100%;
    height: 2px;
    cursor: pointer;
    background: transparent;
    border-radius: 50px;
}

input[type='range']::-webkit-slider-thumb {
    border: 2px solid var(--slider-color-track);
    height: 16px;
    width: 16px;
    border-radius: 25px;
    background: #ffffff;
    cursor: pointer;
    -webkit-appearance: none;
    position: relative;
    left: -7px;
}

input[type='range']:focus::-webkit-slider-runnable-track {
    background: transparent;
}

input[type='range']::-moz-range-track {
    width: 100%;
    height: 2px;
    cursor: pointer;
    background: transparent;
    border-radius: 50px;
}

input[type='range']::-moz-range-thumb {
    border: 2px solid var(--slider-color-track);
    height: 16px;
    width: 16px;
    border-radius: 25px;
    background: #ffffff;
    cursor: pointer;
    position: relative;
    left: -7px;
}

input[type='range']::-ms-track {
    width: 100%;
    height: 2px;
    cursor: pointer;
    background: transparent;
    border-color: transparent;
    color: transparent;
}

input[type='range']::-ms-thumb {
    margin-top: 1px;
    border: 2px solid var(--slider-color-track);
    height: 16px;
    width: 16px;
    border-radius: 25px;
    background: #ffffff;
    cursor: pointer;
    position: relative;
    left: -7px;
}