Create a marker clusterer

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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="react, typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {LngLat} from '@mappable-world/mappable-types';
      import type {Feature} from '@mappable-world/mappable-clusterer';
      import {COMMON_LOCATION_PARAMS, getBounds, getRandomPoints, MARGIN} from './common';
      import {BOUNDS, LOCATION} from '../variables';

      window.map = null;

      main();
      async function main() {
          // Waiting for all api elements to be loaded
          await mappable.ready;
          interface ClustererChangeControlProps {
              toggleClusterer: () => void;
              changePointsCount: (count: number) => void;
              updatePoints: () => void;
          }

          class ClustererChangeControl extends mappable.MMapComplexEntity<ClustererChangeControlProps> {
              private _element: HTMLDivElement;
              private _detachDom: () => void;

              // Method for create a DOM control element
              _createElement(props: ClustererChangeControlProps) {
                  const {toggleClusterer, changePointsCount, updatePoints} = props;

                  const clustererChange = document.createElement('div');
                  clustererChange.classList.add('clusterer-change');

                  const inputSection = document.createElement('div');
                  inputSection.classList.add('clusterer-change__section');

                  const inputLabel = document.createElement('div');
                  inputLabel.classList.add('clusterer-change__input__label');
                  inputLabel.textContent = 'Point count:';
                  inputSection.appendChild(inputLabel);

                  const inputField = document.createElement('input');
                  inputField.type = 'number';
                  inputField.classList.add('clusterer-change__input');
                  inputField.value = '100';
                  inputField.addEventListener('input', (e: Event) => {
                      const target = e.target as HTMLInputElement;
                      changePointsCount(Number(target.value));
                  });
                  inputSection.appendChild(inputField);

                  const btnSection = document.createElement('div');
                  btnSection.classList.add('clusterer-change__buttons');

                  const updatePointsBtn = document.createElement('button');
                  updatePointsBtn.type = 'button';
                  updatePointsBtn.classList.add('clusterer-change__btn');
                  updatePointsBtn.textContent = 'Update points';
                  updatePointsBtn.addEventListener('click', updatePoints);
                  btnSection.appendChild(updatePointsBtn);

                  const toggleClustererBtn = document.createElement('button');
                  toggleClustererBtn.type = 'button';
                  toggleClustererBtn.id = 'toggleBtn';
                  toggleClustererBtn.classList.add('clusterer-change__btn');
                  toggleClustererBtn.textContent = 'Disable cluster mode';
                  toggleClustererBtn.addEventListener('click', toggleClusterer);
                  btnSection.appendChild(toggleClustererBtn);

                  const dividerElement = document.createElement('hr');
                  dividerElement.classList.add('divider');

                  clustererChange.appendChild(inputSection);
                  clustererChange.appendChild(dividerElement);
                  clustererChange.appendChild(btnSection);

                  return clustererChange;
              }

              // 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 {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker, MMapControls, MMapControl} = mappable;

          // Load the package with the cluster, extract the classes for creating clusterer objects and the clustering method
          const {MMapClusterer, clusterByGrid} = await mappable.import('@mappable-world/mappable-clusterer');
          const {MMapDefaultMarker} = await mappable.import('@mappable-world/mappable-default-ui-theme');

          // Declare number of points in the clusterer
          let pointsCount = 100;
          let points = getRandomPoints(pointsCount, BOUNDS);
          let markers = [];

          map = new MMap(document.getElementById('app'), {location: LOCATION, showScaleInCopyrights: true, margin: MARGIN}, [
              new MMapDefaultSchemeLayer({}),
              new MMapDefaultFeaturesLayer({})
          ]);

          /* We declare the function for rendering ordinary markers, we will submit it to the clusterer settings.
        Note that the function must return any Entity element. In the example, this is MMapDefaultMarker. */
          const marker = (feature: Feature) =>
              new MMapDefaultMarker({
                  coordinates: feature.geometry.coordinates,
                  iconName: 'landmark'
              });

          // As for ordinary markers, we declare a cluster rendering function that also returns an Entity element.
          const cluster = (coordinates: LngLat, features: Feature[]) =>
              new MMapMarker(
                  {
                      coordinates,
                      onClick() {
                          const bounds = getBounds(features.map((feature: Feature) => feature.geometry.coordinates));
                          map.update({location: {bounds, ...COMMON_LOCATION_PARAMS}});
                      }
                  },
                  circle(features.length).cloneNode(true) as HTMLElement
              );

          function circle(count: number) {
              const circle = document.createElement('div');
              circle.classList.add('circle');
              circle.innerHTML = `
                        <div class="circle-content">
                            <span class="circle-text">${count}</span>
                        </div>
                    `;
              return circle;
          }

          /* We create a clusterer object and add it to the map object.
        As parameters, we pass the clustering method, an array of features, the functions for rendering markers and clusters.
        For the clustering method, we will pass the size of the grid division in pixels. */
          const clusterer = new MMapClusterer({
              method: clusterByGrid({gridSize: 64}),
              features: points,
              marker,
              cluster
          });
          map.addChild(clusterer);

          // Creating handler functions for changing the clusterer. We will use these functions in a custom control
          // THe handler function for changing the number of clusterer points
          function changePointsCount(count: number) {
              pointsCount = count;
          }
          // The handler function for updating coordinates of clusterer points
          function updatePoints() {
              points = getRandomPoints(pointsCount, map.bounds);
              clusterer.update({features: points});
              if (!clusterer.parent) {
                  markers.forEach((markerEl) => {
                      map.removeChild(markerEl);
                  });
                  markers = [];
                  points.forEach((feature) => {
                      const markerElement = marker(feature);
                      map.addChild(markerElement);
                      markers.push(markerElement);
                  });
              }
          }
          // The handler function for attach/detach the clusterer
          function toggleClusterer() {
              const button = document.getElementById('toggleBtn');
              if (clusterer.parent) {
                  map.removeChild(clusterer);
                  points.forEach((feature) => {
                      const markerElement = marker(feature);
                      map.addChild(markerElement);
                      markers.push(markerElement);
                  });
                  button.innerText = 'Enable cluster mode';
              } else {
                  map.addChild(clusterer);
                  markers.forEach((markerEl) => {
                      map.removeChild(markerEl);
                  });
                  markers = [];
                  button.innerText = 'Disable cluster mode';
              }
          }

          // Creating and adding a custom clusterer change element to the map
          map.addChild(
              new MMapControls({position: 'top right'}).addChild(
                  new MMapControl().addChild(new ClustererChangeControl({toggleClusterer, changePointsCount, updatePoints}))
              )
          );
      }
    </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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="react, typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import type {Feature} from '@mappable-world/mappable-clusterer';
      import type {LngLat} from '@mappable-world/mappable-types';
      import type TReact from 'react';
      import {COMMON_LOCATION_PARAMS, getBounds, getRandomPoints, MARGIN} from './common';
      import {BOUNDS, LOCATION} 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, MMapMarker, MMapControls, MMapControl} =
          reactify.module(mappable);

        // Load the package with the cluster, extract the classes for creating clusterer objects and the clustering method
        const {MMapClusterer, clusterByGrid} = reactify.module(
          await mappable.import('@mappable-world/mappable-clusterer')
        );
        const {MMapDefaultMarker} = reactify.module(
          await mappable.import('@mappable-world/mappable-default-ui-theme')
        );

        const {useState, useMemo, useCallback} = React;

        function App() {
          // We declare the initial states of the clusterer
          // Clusterer visibility
          const [isClusterer, setIsClusterer] = useState(true);

          const [location, setLocation] = useState(LOCATION);
          // Number of points in the clusterer
          const [pointsCount, setPointsCount] = useState(100);
          // Array with parameters for each clusterer point
          const [points, setPoints] = useState(getRandomPoints(pointsCount, BOUNDS));

          // We declare a render function. For the clustering method, we pass and store the size of one grid division in pixels
          const gridSizedMethod = useMemo(() => clusterByGrid({gridSize: 64}), []);

          const onClusterClick = useCallback(
            (features: Feature[]) => {
              const bounds = getBounds(features.map((feature: Feature) => feature.geometry.coordinates));
              setLocation((prevLocation) => ({...prevLocation, bounds, ...COMMON_LOCATION_PARAMS}));
            },
            [location]
          );

          // We declare a function for rendering markers. Note that the function must return any Entity element. In the example, this is MMapDefaultMarker
          const marker = (feature: Feature) => (
            <MMapDefaultMarker iconName="landmark" coordinates={feature.geometry.coordinates} />
          );

          // We declare a cluster rendering function that also returns an Entity element. We will transfer the marker and cluster rendering functions to the clusterer settings
          const cluster = (coordinates: LngLat, features: Feature[]) => (
            <MMapMarker coordinates={coordinates}>
              <div className="circle" onClick={() => onClusterClick(features)}>
                <div className="circle-content">
                  <span className="circle-text">{features.length}</span>
                </div>
              </div>
            </MMapMarker>
          );

          // Creating handler functions for changing the clusterer. We will use these functions in a custom control
          // THe handler function for changing the number of clusterer points
          const changePointsCount = useCallback(
            (event: TReact.ChangeEvent<HTMLInputElement>) => setPointsCount(Number(event.target.value)),
            []
          );
          // The handler function for updating coordinates of clusterer points
          const updatePoints = useCallback(() => setPoints(getRandomPoints(pointsCount, map.bounds)), [pointsCount]);
          // The handler function for attach/detach the clusterer
          const toggleClusterer = useCallback(() => setIsClusterer((prevValue) => !prevValue), []);

          return (
            // Initialize the map and pass initialization parameters
            <MMap margin={MARGIN} location={location} showScaleInCopyrights={true} ref={(x) => (map = x)}>
              {/* Add a map scheme layer */}
              <MMapDefaultSchemeLayer />
              {/* Add default feature layer */}
              <MMapDefaultFeaturesLayer />
              {/* In the clusterer props, we pass the previously declared functions for rendering markers and clusters,
                the clustering method, and an array of features */}
              {isClusterer ? (
                <MMapClusterer marker={marker} cluster={cluster} method={gridSizedMethod} features={points} />
              ) : (
                points.map((feature) => marker(feature))
              )}

              {/* Add a custom clusterer change element to the map */}
              <MMapControls position="top right">
                <MMapControl>
                  <div className="clusterer-change">
                    <div className="clusterer-change__section">
                      <div className="clusterer-change__input__label">Point count:</div>
                      <input
                        type="number"
                        className="clusterer-change__input"
                        value={pointsCount}
                        onChange={changePointsCount}
                      />
                    </div>
                    <hr className="divider" />
                    <div className="clusterer-change__buttons">
                      <button type="button" className="clusterer-change__btn" onClick={updatePoints}>
                        Update points
                      </button>
                      <button type="button" className="clusterer-change__btn" onClick={toggleClusterer}>
                        {isClusterer ? 'Disable cluster mode' : 'Enable cluster mode'}
                      </button>
                    </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>

    <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="react, typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="react, typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {getRandomPoints, MARGIN} from './common';
      import {BOUNDS, LOCATION} from '../variables';
      import {COMMON_LOCATION_PARAMS, getBounds} from '../../[objects]marker-custom-icon/common';
      import {Feature} from '@mappable-world/mappable-types';

      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, MMapMarker, MMapControls, MMapControl} =
          vuefy.module(mappable);

        const {MMapClusterer, clusterByGrid} = vuefy.module(
          await mappable.import('@mappable-world/mappable-clusterer')
        );
        const {MMapDefaultMarker} = vuefy.module(await mappable.import('@mappable-world/mappable-default-ui-theme'));

        const Marker = Vue.defineComponent({
          props: ['feature'],
          components: {
            MMapDefaultMarker
          },
          template: `
            <MMapDefaultMarker iconName="landmark" :coordinates="feature.geometry.coordinates" />
        `
        });

        const Cluster = Vue.defineComponent({
          props: {
            coordinates: Array,
            features: Array,
            onClick: Function
          },
          components: {
            MMapMarker
          },
          template: `
      <MMapMarker
        :coordinates="coordinates"
        :onClick="onClick"
      >
        <div class="circle">
          <div class="circle-content">
            <span class="circle-text">{{ features.length }}</span>
          </div>
        </div>
      </MMapMarker>
    `
        });

        const App = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapDefaultFeaturesLayer,
            MMapClusterer,
            MMapControls,
            MMapControl,
            Cluster,
            Marker
          },
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            const location = Vue.ref(LOCATION);

            const isClusterer = Vue.ref(true);
            const pointsCount = Vue.ref(100);
            const points = Vue.ref(getRandomPoints(pointsCount.value, BOUNDS));

            const changePointsCount = (event) => {
              pointsCount.value = Number(event.target.value);
            };

            const updatePoints = () => {
              points.value = getRandomPoints(pointsCount.value, map.bounds);
            };

            const toggleClusterer = () => {
              isClusterer.value = !isClusterer.value;
            };
            const gridSizedMethod = clusterByGrid({gridSize: 64});

            const onClusterClick = (features: Feature[]) => {
              const bounds = getBounds(features.map((feature: Feature) => feature.geometry.coordinates));
              location.value = {bounds, ...COMMON_LOCATION_PARAMS};
            };

            Vue.watch(pointsCount, () => {
              updatePoints();
            });

            return {
              MARGIN,
              refMap,
              location,
              isClusterer,
              points,
              pointsCount,
              changePointsCount,
              updatePoints,
              toggleClusterer,
              onClusterClick,
              Marker,
              Cluster,
              gridSizedMethod
            };
          },
          template: `
      <MMap :location="location" :showScaleInCopyrights="true" :ref="refMap" :margin="MARGIN">
        <!-- Add a map scheme layer -->
        <MMapDefaultSchemeLayer />
        <!-- Add default features layer -->
        <MMapDefaultFeaturesLayer />
        <!-- In the clusterer props, we pass the previously declared functions for rendering markers and clusters,
        the clustering method, and an array of features -->

        <template v-if="isClusterer">
          <MMapClusterer :method="gridSizedMethod" :features="points">
            <template #marker="{feature}">
              <Marker :feature="feature" />
            </template>
            <template #cluster="{coordinates, features}">
              <Cluster :onClick="() => onClusterClick(features)" :coordinates="coordinates" :features="features" />
            </template>
          </MMapClusterer>
        </template>
        <template v-else>
          <template v-for="feature in points">
            <Marker :feature="feature" />
          </template>
        </template>

        <!-- Add a custom clusterer change element to the map -->
        <MMapControls position="top right">
          <MMapControl>
            <div class="clusterer-change">
              <div class="clusterer-change__section">
                <div class="clusterer-change__input__label">Point count:</div>
                <input
                  type="number"
                  class="clusterer-change__input"
                  v-model="pointsCount"
                />
              </div>
              <hr class="divider">
              <div class="clusterer-change__buttons">
                <button type="button" class="clusterer-change__btn" @click="updatePoints">
                  Update points
                </button>
                <button type="button" class="clusterer-change__btn" @click="toggleClusterer">
                  {{ isClusterer ? "Disable cluster mode" : "Enable cluster mode" }}
                </button>
              </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>
.clusterer-change {
  padding: 8px 16px;
  border-radius: 12px;

  display: flex;
  flex-direction: column;
  align-items: center;
}

.clusterer-change__section {
  display: flex;
  align-items: center;
  width: 100%;
  justify-content: space-between;
  gap: 10px;
  height: 48px;
}

.clusterer-change__buttons {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 0;
}

.clusterer-change__input__label {
  font-size: 14px;
}

.clusterer-change__input {
  max-width: 60px;
  height: 32px;
  font-size: 16px;
  text-align: center;
  border: none;
  border-radius: 8px;
  background: rgba(92, 94, 102, 0.06);
  font-size: 16px;
  outline: none;
  transition: border-color 0.2s ease;
}

.clusterer-change__btn {
  border: none;
  cursor: pointer;

  width: 172px;
  padding: 8px 16px;

  color: #050d33;
  font-size: 14px;
  font-weight: 15;
  background-color: rgba(92, 94, 102, 0.06);
  border-radius: 8px;
  transition: background-color 0.2s;
}

.clusterer-change__btn:hover {
  background-color: rgba(92, 94, 102, 0.06);
}

.clusterer-change__btn:active {
  background-color: rgba(92, 94, 102, 0.06);
}

.circle {
  position: absolute;

  width: 40px;
  height: 40px;

  color: var(--interact-action);
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.7);
  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.2);
  transform: translate(-50%, -50%);
}

.circle:hover {
  color: #2e4ce5;
  cursor: pointer;
}

.circle-content {
  position: absolute;
  top: 50%;
  left: 50%;

  display: flex;
  justify-content: center;
  align-items: center;

  width: 90%;
  height: 90%;

  border-radius: 50%;
  background-color: currentColor;

  transform: translate3d(-50%, -50%, 0);
}

.circle-text {
  font-size: 16px;
  font-weight: 500;
  line-height: 20px;
  color: #fff;
}

.pin {
  transform: translate(-50%, -100%);
}

.divider {
  width: 100%;
  border: none;
  border-top: 1px solid rgba(92, 94, 102, 0.1);
  margin: 8px 0;
}
import type {LngLatBounds, LngLat, MMapLocationRequest, Margin} from '@mappable-world/mappable-types';
import type {Feature} from '@mappable-world/mappable-clusterer';

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

// Function for generating a pseudorandom number
const seed = (s: number) => () => {
  s = Math.sin(s) * 10000;
  return s - Math.floor(s);
};

const rnd = seed(10000); // () => Math.random()

// Generating random coordinates of a point [lng, lat] in a given boundary
const getRandomPointCoordinates = (bounds: LngLatBounds): LngLat => [
  bounds[0][0] + (bounds[1][0] - bounds[0][0]) * rnd(),
  bounds[1][1] + (bounds[0][1] - bounds[1][1]) * rnd()
];

// A function that creates an array with parameters for each clusterer random point
export const getRandomPoints = (count: number, bounds: LngLatBounds): Feature[] => {
  return Array.from({length: count}, (_, index) => ({
    type: 'Feature',
    id: index.toString(),
    geometry: {type: 'Point', coordinates: getRandomPointCoordinates(bounds)}
  }));
};

export const COMMON_LOCATION_PARAMS: Partial<MMapLocationRequest> = {easing: 'ease-in-out', duration: 2000};

export function getBounds(coordinates: number[][]): LngLatBounds {
  let minLat = Infinity,
    minLng = Infinity;
  let maxLat = -Infinity,
    maxLng = -Infinity;

  for (const coords of coordinates) {
    const lat = coords[1];
    const lng = coords[0];

    if (lat < minLat) minLat = lat;
    if (lat > maxLat) maxLat = lat;
    if (lng < minLng) minLng = lng;
    if (lng > maxLng) maxLng = lng;
  }

  return [
    [minLng, minLat],
    [maxLng, maxLat]
  ] as LngLatBounds;
}

export const MARGIN: Margin = [100, 100, 100, 100];
:root {
  --interact-action: #313133;
}
import type {LngLatBounds, MMapLocationRequest} from '@mappable-world/mappable-types';

/* Rectangle bounded by bottom-left and top-right coordinates
Inside it, we generate the first bundle of clusterer points */
export const BOUNDS: LngLatBounds = [
  [55.2825, 25.2317],
  [55.4132, 25.1753]
];

export const LOCATION: MMapLocationRequest = {
  zoom: 11.6,
  center: [55.3948, 25.1947]
};