Building a route to interesting places near a specified point

Open in CodeSandbox

Demonstration of three API’s work: JavaScript API, Distance Matrix and Retrieving Route Details. When you choose a start point system sends the request and get all places for a tourism, chooses 10 the closest and builds the best optimized route on foot.

<!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="../variables.ts"
    ></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">
      import {LOCATION, SEARCH_PARAMS} from '../variables';
      import {
        BUILDING_ROUTE_ERROR_TEXT,
        BUILDING_ROUTE_TEXT,
        CLEAR_ROUTE_BUTTON_TEXT,
        ClearButton,
        CUSTOMIZATION,
        getNearbyPlaces,
        getOptimalRoute,
        INFO_MESSAGE_TEXT,
        InfoMessage,
        LINE_STRING_STYLE,
        ROUTE_IS_BUILT_TEXT
      } from './common';
      import {DomEvent} from '@mappable-world/mappable-types';

      window.map = null; // Initialize the map variable
      let marker = null; // Variable to hold the current marker
      let nearbyPlaces = []; // Array to hold nearby places
      let routeElement = null; // Variable to hold the route element
      let infoMessage = null; // Variable to hold the info message element
      let topRightControls = null; // Variable to hold the top right controls

      main(); // Call the main function to start the application

      async function main() {
        // Waiting for all API elements to be loaded
        await mappable.ready;

        const {
          MMap,
          MMapDefaultSchemeLayer,
          MMapDefaultFeaturesLayer,
          MMapListener,
          MMapControls,
          MMapControl,
          MMapFeature
        } = mappable; // Destructure necessary components from mappable

        // Import the package to add a default marker and zoom control
        const {MMapDefaultMarker, MMapZoomControl} = await mappable.import(
          '@mappable-world/mappable-default-ui-theme'
        );

        // Handle map click events to build a route
        const handleMapClick = async (object, event: DomEvent) => {
          clearAll(); // Clear previous markers, routes, and nearby places
          addMarker(event.coordinates); // Add a new marker at the clicked location
          updateInfoMessage(BUILDING_ROUTE_TEXT); // Update info message to indicate route building
          try {
            // Fetch nearby places based on clicked coordinates
            const places = await getNearbyPlaces(event.coordinates, map.bounds, SEARCH_PARAMS);
            // Get the optimal route including the clicked location and nearby places
            const response = await getOptimalRoute([
              event.coordinates,
              ...places.map((place) => place.geometry.coordinates)
            ]);
            updateRoute(response.geometry.coordinates); // Update the route with the fetched coordinates
            updateNearbyPlaces(places); // Update nearby places on the map
            updateInfoMessage(ROUTE_IS_BUILT_TEXT); // Update info message to indicate route has been built
            addClearButton(); // Add a button to clear the route
          } catch (error) {
            // Handle errors during fetching
            console.error('Error fetching nearby places or optimal route:', error);
            clearAll(); // Clear any markers or routes in case of an error
            updateInfoMessage(BUILDING_ROUTE_ERROR_TEXT); // Update info message to indicate an error occurred
          }
        };

        const clearAll = () => {
          if (topRightControls) {
            map.removeChild(topRightControls); // Remove the clear button from the map
          }
          clearNearByPlaces(); // Clear nearby places from the map
          clearRoute(); // Clear the route from the map
          clearMarker(); // Clear the marker from the map
        };

        const addMarker = (coordinates) => {
          // Create and add a new marker
          marker = new MMapDefaultMarker({
            coordinates,
            size: 'normal',
            iconName: 'fallback'
          });
          map.addChild(marker); // Add the marker to the map
        };

        const addClearButton = () => {
          // Create and add a shared container for controls to the map
          if (!topRightControls) {
            topRightControls = new MMapControls({position: 'top right'}).addChild(
              new MMapControl({}).addChild(
                new ClearButton({
                  text: CLEAR_ROUTE_BUTTON_TEXT, // Set button text
                  onClick: handleClearClick // Set click handler for the clear button
                })
              )
            );
          }
          map.addChild(topRightControls); // Add the clear button to the map
        };

        const handleClearClick = () => {
          // Clear the map markers and route
          clearAll(); // Clear all markers, routes, and nearby places
          updateInfoMessage(INFO_MESSAGE_TEXT); // Reset info message
        };

        const clearNearByPlaces = () => {
          // Clear all nearby places from the map
          nearbyPlaces.forEach((place) => map.removeChild(place)); // Remove each place marker from the map
          nearbyPlaces = []; // Reset the nearby places array
        };

        const clearRoute = () => {
          // Clear the route from the map
          if (routeElement) {
            map.removeChild(routeElement); // Remove the route element from the map
            routeElement = null; // Reset the route element variable
          }
        };

        const clearMarker = () => {
          // Clear the marker from the map
          if (marker) {
            map.removeChild(marker); // Remove the marker from the map
            marker = null; // Reset the marker variable
          }
        };

        const updateRoute = (routeCoordinates) => {
          // Update the route on the map
          clearRoute(); // Clear any existing route
          routeElement = new MMapFeature({
            geometry: {
              type: 'LineString', // Define the geometry type
              coordinates: routeCoordinates // Set the route coordinates
            },
            style: LINE_STRING_STYLE
          });
          map.addChild(routeElement); // Add the route to the map
        };

        const updateNearbyPlaces = (places) => {
          // Update nearby places on the map
          clearNearByPlaces(); // Clear existing nearby places
          places.forEach((place) => {
            const newMarker = new MMapDefaultMarker({
              coordinates: place.geometry.coordinates, // Set coordinates for the new marker
              size: 'small', // Set marker size
              iconName: place.iconName // Set marker icon
            });
            nearbyPlaces.push(newMarker); // Add the new marker to the nearby places array
            map.addChild(newMarker); // Add the new marker to the map
          });
        };

        const updateInfoMessage = (text) => {
          // Update the info message displayed on the map
          infoMessage.update({text});
        };

        // Initialize the map
        map = new MMap(
          document.getElementById('app'), // Reference to the HTML container
          {location: LOCATION, showScaleInCopyrights: true, mode: 'vector'}, // Map options
          [
            new MMapDefaultSchemeLayer({
              customization: CUSTOMIZATION // Set customization options for the scheme layer
            }),
            new MMapDefaultFeaturesLayer({}), // Add a layer for geo objects
            new MMapListener({
              onClick: handleMapClick // Set the click event handler for the map
            })
          ]
        );

        const infoWindow = new MMapControls({position: 'top left'});
        infoMessage = new InfoMessage({text: INFO_MESSAGE_TEXT});
        infoWindow.addChild(infoMessage);
        map.addChild(infoWindow);

        map.addChild(
          // Here we place the control on the right
          new MMapControls({position: 'right'})
            // Add the first zoom control to the map
            .addChild(new MMapZoomControl({}))
        );
      }
    </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" />
  </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="../variables.ts"
    ></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">
      import {LOCATION, SEARCH_PARAMS} from '../variables';

      import {
          BUILDING_ROUTE_ERROR_TEXT,
          BUILDING_ROUTE_TEXT,
          CLEAR_ROUTE_BUTTON_TEXT,
          CUSTOMIZATION,
          CustomMarker,
          getNearbyPlaces,
          getOptimalRoute,
          INFO_MESSAGE_TEXT,
          InfoMessage,
          LINE_STRING_STYLE,
          ROUTE_IS_BUILT_TEXT
      } from './common';
      import {DomEvent, LngLat} from '@mappable-world/mappable-types';

      window.map = null;

      async function main() {
          const mappableReact = await mappable.import('@mappable-world/mappable-reactify');
          await mappable.ready;

          const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
          const {
              MMapListener,
              MMap,
              MMapDefaultSchemeLayer,
              MMapDefaultFeaturesLayer,
              MMapControls,
              MMapControl,
              MMapFeature
          } = reactify.module(mappable);

          // Import the package to add a default marker and zoom control
          const {MMapDefaultMarker, MMapPopupMarker, MMapZoomControl} = await reactify.module(
              await mappable.import('@mappable-world/mappable-default-ui-theme')
          );

          const {InfoMessage: InfoMessageR} = reactify.module({InfoMessage});
          const {useState} = React;

          function App() {
              const [markerCoordinates, setMarkerCoordinates] = useState<LngLat | []>([]);
              const [nearbyPlaces, setNearbyPlaces] = useState<CustomMarker[]>([]);
              const [routeCoordinates, setRouteCoordinates] = useState<LngLat[]>([]);
              const [infoMessage, setInfoMessage] = useState(INFO_MESSAGE_TEXT);

              // Handle map click events to build a route
              const handleMapClick = async (object, event: DomEvent) => {
                  setInfoMessage(BUILDING_ROUTE_TEXT); // Update info message
                  setNearbyPlaces([]); // Clear previous nearby places
                  setRouteCoordinates([]); // Clear previous route coordinates
                  setMarkerCoordinates(event.coordinates); // Set new marker coordinates

                  try {
                      // Fetch nearby places based on clicked coordinates
                      const places = await getNearbyPlaces(event.coordinates, map.bounds, SEARCH_PARAMS);

                      // Get the optimal route including the clicked location and nearby places
                      const response = await getOptimalRoute([
                          event.coordinates,
                          ...places.map((place) => place.geometry.coordinates)
                      ]);

                      // Update state with fetched data
                      setNearbyPlaces(places);
                      setRouteCoordinates(response.geometry.coordinates);
                      setInfoMessage(ROUTE_IS_BUILT_TEXT); // Update info message on successful route build
                  } catch (error) {
                      // Handle errors during fetching
                      console.error('Error fetching nearby places or optimal route:', error);
                      setNearbyPlaces([]);
                      setRouteCoordinates([]);
                      setMarkerCoordinates([]);
                      setInfoMessage(BUILDING_ROUTE_ERROR_TEXT); // Update info message on error
                  }
              };

              // Clear the map markers and route
              const handleClearClick = () => {
                  setNearbyPlaces([]);
                  setRouteCoordinates([]);
                  setMarkerCoordinates([]);
                  setInfoMessage(INFO_MESSAGE_TEXT); // Reset info message
              };

              return (
                  <MMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)} mode="vector">
                      <MMapDefaultSchemeLayer customization={CUSTOMIZATION} />
                      <MMapDefaultFeaturesLayer />

                      {/* Render marker and popup if coordinates are available */}
                      {markerCoordinates.length > 0 && (
                          <>
                              <MMapDefaultMarker
                                  coordinates={markerCoordinates as LngLat}
                                  size="normal"
                                  iconName="fallback"
                              />
                              <MMapPopupMarker
                                  coordinates={markerCoordinates as LngLat}
                                  content="I’m here"
                                  position="right"
                              />
                          </>
                      )}

                      {/* Render nearby places markers */}
                      {nearbyPlaces.map((place, index) => (
                          <MMapDefaultMarker
                              key={index}
                              coordinates={place.geometry.coordinates}
                              size="small"
                              iconName={place.iconName}
                          />
                      ))}

                      {/* Render route if coordinates are available */}
                      {routeCoordinates.length > 0 && (
                          <MMapFeature
                              geometry={{
                                  type: 'LineString',
                                  coordinates: routeCoordinates
                              }}
                              style={LINE_STRING_STYLE}
                          />
                      )}

                      {/* Render controls for the map */}
                      <MMapControls position="top left">
                          <InfoMessageR text={infoMessage} />
                      </MMapControls>

                      {/* Render clear button if route exists */}
                      {!!routeCoordinates.length && (
                          <MMapControls position="top right">
                              <MMapControl>
                                  <button className="clear-button" onClick={handleClearClick}>
                                      {CLEAR_ROUTE_BUTTON_TEXT}
                                  </button>
                              </MMapControl>
                          </MMapControls>
                      )}

                      {/* Render controls for the map */}
                      <MMapControls position="right" orientation="vertical">
                          <MMapZoomControl />
                      </MMapControls>

                      {/* Listener for map click events */}
                      <MMapListener onClick={handleMapClick} />
                  </MMap>
              );
          }

          // Render the App component into the DOM
          ReactDOM.render(
              <React.StrictMode>
                  <App />
              </React.StrictMode>,
              document.getElementById('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" />
  </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="../variables.ts"
    ></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="typescript" type="text/babel">
      import {LOCATION, SEARCH_PARAMS} from '../variables';
      import {
        BUILDING_ROUTE_ERROR_TEXT,
        BUILDING_ROUTE_TEXT,
        CLEAR_ROUTE_BUTTON_TEXT,
        CUSTOMIZATION,
        getNearbyPlaces,
        getOptimalRoute,
        INFO_MESSAGE_TEXT,
        InfoMessage,
        LINE_STRING_STYLE,
        ROUTE_IS_BUILT_TEXT
      } from './common';
      import {DomEvent} from '@mappable-world/mappable-types';

      window.map = null;

      async function main() {
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {
          MMap,
          MMapDefaultSchemeLayer,
          MMapDefaultFeaturesLayer,
          MMapControls,
          MMapControl,
          MMapFeature,
          MMapListener
        } = vuefy.module(mappable);

        // Import the package to add a default marker and zoom control
        const {MMapDefaultMarker, MMapPopupMarker, MMapZoomControl} = vuefy.module(
          await mappable.import('@mappable-world/mappable-default-ui-theme')
        );

        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapDefaultFeaturesLayer,
            MMapControls,
            MMapControl,
            MMapFeature,
            MMapListener,
            MMapPopupMarker,
            MMapZoomControl,
            MMapDefaultMarker
          },
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            const markerCoordinates = Vue.ref([]); // Array to hold marker coordinates
            const nearbyPlaces = Vue.ref([]); // Array to hold nearby places
            const routeCoordinates = Vue.ref([]); // Array to hold route coordinates
            const infoMessage = Vue.ref(INFO_MESSAGE_TEXT); // Info message state

            // Handle map click events to build a route
            const handleMapClick = async (object, event: DomEvent) => {
              infoMessage.value = BUILDING_ROUTE_TEXT; // Update info message
              nearbyPlaces.value = []; // Clear previous nearby places
              routeCoordinates.value = []; // Clear previous route coordinates
              markerCoordinates.value = event.coordinates; // Set new marker coordinates
              try {
                // Fetch nearby places based on clicked coordinates
                const places = await getNearbyPlaces(event.coordinates, map.bounds, SEARCH_PARAMS);
                // Get the optimal route including the clicked location and nearby places
                const response = await getOptimalRoute([
                  event.coordinates,
                  ...places.map((place) => place.geometry.coordinates)
                ]);
                // Update state with fetched data
                nearbyPlaces.value = places;
                routeCoordinates.value = response.geometry.coordinates;
                infoMessage.value = ROUTE_IS_BUILT_TEXT; // Update info message on successful route build
              } catch (error) {
                // Handle errors during fetching
                console.error('Error fetching nearby places or optimal route:', error);
                nearbyPlaces.value = [];
                routeCoordinates.value = [];
                markerCoordinates.value = [];
                infoMessage.value = BUILDING_ROUTE_ERROR_TEXT; // Update info message on error
              }
            };

            // Clear the map markers and route
            const handleClearClick = () => {
              nearbyPlaces.value = [];
              routeCoordinates.value = [];
              markerCoordinates.value = [];
              infoMessage.value = INFO_MESSAGE_TEXT; // Reset info message
            };

            return {
              LOCATION,
              CUSTOMIZATION,
              markerCoordinates,
              nearbyPlaces,
              routeCoordinates,
              infoMessage,
              InfoMessage,
              refMap,
              handleMapClick,
              handleClearClick,
              CLEAR_ROUTE_BUTTON_TEXT,
              LINE_STRING_STYLE
            };
          },
          template: `
      <MMap
        :location="LOCATION"
        showScaleInCopyrights
        :ref="refMap"
        mode="vector"
      >
        <MMapDefaultSchemeLayer :customization="CUSTOMIZATION" />
        <MMapDefaultFeaturesLayer />
        <!-- Render marker and popup if coordinates are available -->
        <MMapDefaultMarker
          v-if="markerCoordinates.length > 0"
          :coordinates="markerCoordinates"
          size="normal"
          iconName="fallback"
        />
        <MMapPopupMarker
          v-if="markerCoordinates.length > 0"
          :coordinates="markerCoordinates"
          content="I’m here"
          position="right"
        />
        <!-- Render nearby places markers -->
        <MMapDefaultMarker
          v-for="place in nearbyPlaces"
          :key="place.id"
          :coordinates="place.geometry.coordinates"
          size="small"
          :iconName="place.iconName"
        />
        <!-- Render route if coordinates are available -->
        <MMapFeature
          v-if="routeCoordinates.length > 0"
          :geometry="{ type: 'LineString', coordinates: routeCoordinates }"
          :style="LINE_STRING_STYLE"
        />
        <!-- Render controls for the map -->
        <MMapControls position="top left">
          <MMapControl>
            <div class="info-window">{{infoMessage}}</div>
          </MMapControl>
        </MMapControls>
        <!-- Render clear button if route exists -->
        <MMapControls position="top right" v-if="routeCoordinates.length">
          <MMapControl>
            <button class="clear-button" @click="handleClearClick">
              {{ CLEAR_ROUTE_BUTTON_TEXT }}
            </button>
          </MMapControl>
        </MMapControls>
         <!--Using MMapControls you can change the position of the control
        Here we place the control on the right-->
        <MMapControls position="right">
          <!--Add the first zoom control to the map-->
          <MMapZoomControl />
        </MMapControls>
        <!-- Listener for map click events -->
        <MMapListener @click="handleMapClick" />
      </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" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.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;
}

.clear-button {
  font-size: 14px;
  line-height: 20px;
  padding: 8px 12px;
  border-radius: 12px;
  background-color: #fff;
  border-color: unset;
  cursor: pointer;
  box-shadow: 0px 2px 4px 0px #5f698333;
  border: unset;
}
import type {LngLat, LngLatBounds, Feature, VectorCustomization, RouteOptions} from '@mappable-world/mappable-types';
import {IconName} from '@mappable-world/mappable-default-ui-theme/dist/types/icons/icon-name.generated';
import {DISTANCE_MATRIX_URL, DISTANCE_MATRIX_API_KEY} from './variables';

// Wait for the Mappable Maps API to load before accessing the map configuration
mappable.ready.then(() => {});

export const INFO_MESSAGE_TEXT = 'Click/tap on the map where you want to start your trip in the city';
export const BUILDING_ROUTE_TEXT = 'Building route...';
export const ROUTE_IS_BUILT_TEXT = 'Optimal route is built';
export const BUILDING_ROUTE_ERROR_TEXT = 'Error building route from selected point, please click/tap on another one';
export const CLEAR_ROUTE_BUTTON_TEXT = 'Clear route';

export const LINE_STRING_STYLE = {
  stroke: [{color: '#7373E6', width: 4, dash: [5, 10]}]
};

// Define custom styling for map elements
export const CUSTOMIZATION: VectorCustomization = [
  {
    tags: {
      any: ['poi'] // Point of Interest
    },
    stylers: [{visibility: 'off'}] // Hide POIs
  }
];

export type CustomMarker = Feature & {
  iconName: IconName;
};

// Create a custom information message control
export let InfoMessage = null;
export let ClearButton = null;

interface InfoMessageProps {
  text: string;
}

interface ClearButtonProps {
  text: string;
  onClick: () => void;
}

// Wait for the Mappable Maps API to load before accessing the entity system
mappable.ready.then(() => {
  class InfoMessageClass extends mappable.MMapComplexEntity<InfoMessageProps> {
    private _element!: HTMLDivElement;
    private _detachDom!: () => void;

    _createElement(props: InfoMessageProps) {
      const infoWindow = document.createElement('div');
      infoWindow.classList.add('info-window');
      infoWindow.innerText = props.text;
      return infoWindow;
    }

    // Method that is used when updating the parameters of control
    _onUpdate() {
      // If the element already exists, just update its innerText
      if (this._element) {
        this._element.innerText = this._props.text;
      } else {
        // If it doesn't exist, create it
        this._element = this._createElement(this._props);
        this._detachDom = mappable.useDomContext(this, this._element, this._element);
      }
    }

    _onAttach() {
      this._element = this._createElement(this._props); // Create the element
      this._detachDom = mappable.useDomContext(this, this._element, this._element);
    }

    _onDetach() {
      this._detachDom();
      this._detachDom = undefined;
      this._element = undefined;
    }
  }
  class ClearButtonClass extends mappable.MMapComplexEntity<ClearButtonProps> {
    private _element;
    private _detachDom;

    // Method to create a DOM control element for the button
    _createElement(props) {
      const clearButton = document.createElement('button');
      clearButton.className = 'clear-button';
      clearButton.innerText = props.text;
      clearButton.onclick = props.onClick;

      return clearButton;
    }

    // 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;
    }
  }
  ClearButton = ClearButtonClass; // Export the ClearButton class
  InfoMessage = InfoMessageClass; // Export the InfoMessage class
});

// Fetch nearby places based on origin coordinates and bounds
export const getNearbyPlaces = async (origins: LngLat, bounds: LngLatBounds, searchParams) => {
  try {
    // Perform searches for each type of place defined in searchParams
    const searchPromises = searchParams.map((parameter) => mappable.search({text: parameter.text, bounds}));

    const searchResponses = await Promise.all(searchPromises);

    // Map the search results to include associated icons
    const placesOfInterest = searchResponses.flatMap((response, index) =>
      response.map((object) => ({
        ...object,
        iconName: searchParams[index].iconName
      }))
    );

    // Extract coordinates for destinations
    const destinations = placesOfInterest.map((destination) => destination.geometry.coordinates);

    // Prepare parameters for the distance matrix API
    const originsParam = origins.join(',');
    const destinationsParam = destinations.map((destination) => destination.join(',')).join('|');

    // Fetch distance matrix data from the Mappable API
    const distanceMatrixUrl = `${DISTANCE_MATRIX_URL}?origins=${originsParam}&destinations=${destinationsParam}&apikey=${DISTANCE_MATRIX_API_KEY}`;
    const response = await fetch(distanceMatrixUrl);

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();

    // Filter valid data and check if any valid data exists
    const validPlaces = data.rows[0].elements
      .map((element, index) => ({
        ...placesOfInterest[index],
        ...element
      }))
      .filter((place) => place.status === 'OK');
    if (validPlaces.length === 0) {
      throw new Error('No valid data found');
    }

    // Sort the places by distance and limit to the closest 10
    const sortedPlaces = validPlaces.sort((a, b) => a.distance.value - b.distance.value).slice(0, 10);

    return sortedPlaces;
  } catch (error) {
    console.error('Error fetching data:', error);
    return []; // Return an empty array in case of an error
  }
};

// Fetch the optimal route based on given coordinates
export const getOptimalRoute = async (coordinates) => {
  try {
    // Request a route from the Mappable Maps API
    const response = await mappable.route({
      points: coordinates,
      optimize: true, // Enable route optimization. This parameter is not described in documentation at this moment, but it works
      type: 'walking', // Specify the mode of transport
      bounds: true // Enable bounds
    } as RouteOptions);

    // Check if a route was found
    if (!response[0]) return;

    // Convert the received route to a RouteFeature object
    const route = response[0].toRoute();
    // Check if the route has coordinates
    if (route.geometry.coordinates.length === 0) return;

    return route;
  } catch (error) {
    console.error('Error fetching data:', error); // Log any errors to the console
  }
};

mappable.import.registerCdn(
  'https://cdn.jsdelivr.net/npm/{package}',
  '@mappable-world/mappable-default-ui-theme@0.0'
);
import type {MMapLocationRequest} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [55.169014, 25.114102], // starting position [lng, lat]
  zoom: 13 // starting zoom
};

// Define search parameters for different types of places
export const SEARCH_PARAMS = [
  {text: 'gardens and parks', iconName: 'park'},
  {text: 'churches', iconName: 'catholic_church'},
  {text: 'museums', iconName: 'museum'},
  {text: 'zoos', iconName: 'zoo'},
  {text: 'monuments and memorials', iconName: 'monument'},
  {text: 'historical architecture', iconName: 'building'},
  {text: 'viewpoints', iconName: 'viewpoint'},
  {text: 'mosques', iconName: 'mosque'},
  {text: 'theatres', iconName: 'theatre'},
  {text: 'historical sites', iconName: 'memorable_event'}
];

export const DISTANCE_MATRIX_API_KEY = '<YOUR_APIKEY>';
export const DISTANCE_MATRIX_URL = 'https://distancematrix.api.mappable.world/v2';