UNPKG

@geoapify/route-directions

Version:

Route Directions component for Routing API - choose waypoints, select route options, calculate the route

865 lines (864 loc) 50 kB
import { GeocoderAutocomplete } from "@geoapify/geocoder-autocomplete"; export class RouteDirections { container; apiKey; MIN_ADDRESS_LENGTH = 3; callbacksRouteCalculated = []; callbacksOptionsChanged = []; callbacksModeChanged = []; callbacksWaypointsChanged = []; routingUrl = "https://api.geoapify.com/v1/routing"; geocodingAddress = "https://api.geoapify.com/v1/geocode/search"; reverseGeocodingAddress = "https://api.geoapify.com/v1/geocode/reverse"; allowedModes = ['walk', 'hike', 'scooter', 'motorcycle', 'drive', 'light_truck', 'medium_truck', 'truck', 'bicycle', 'mountain_bike', 'road_bike', 'bus']; dragElement; labels = { "avoid": "Avoid:", "units": "Units:", "addDestination": "Add destination", "avoidTolls": "Tolls", "avoidFerries": "Ferries", "avoidHighways": "Highways", "unitsMiles": "Miles", "unitsKilometers": "Kilometers", "calculateButton": "Calculate", "noRouteFound": "No route found", "warning.min2Waypoints": "Choose at least two waypoints to calculate a route", 'walk': "Walk", 'hike': 'Hike', 'scooter': "Scooter", 'motorcycle': "Motocycle", 'drive': "drive", 'light_truck': "Van", 'medium_truck': "Delivery truck", 'truck': "Truck", 'bicycle': "Bicycle", 'mountain_bike': "Montain bicycle", 'road_bike': "Road bicycle", 'bus': "Bus" }; geocoderOptions = { skipDetails: true, skipIcons: true }; options = { mode: 'drive', avoidTolls: false, avoidFerries: false, avoidHighways: false, elevation: false, units: "metric", calculateRouteTrigger: 'auto', supportedModes: ['walk', 'bicycle', 'drive', 'medium_truck'], supportedOptions: [], debounceDelay: 2000 }; /* We set timeout before sending a request to avoid unnecessary calls */ currentTimeout; /* Active request promise reject function. To be able to cancel the promise when a new request comes */ currentPromiseReject; previousRoutingAPICallURL; constructor(container, apiKey, options, geocoderOptions) { this.container = container; this.apiKey = apiKey; this.options = options ? { ...this.options, ...options } : this.options; this.geocoderOptions = geocoderOptions ? { ...this.geocoderOptions, ...geocoderOptions } : this.geocoderOptions; this.labels = this.options.labels ? { ...this.labels, ...this.options.labels } : this.labels; this.checkAndfillWaypoints(); this.generateControls(); } calculate() { return new Promise((resolve, reject) => { const waypointsWithCoords = this.options.waypoints.filter(waypoint => waypoint.lat && waypoint.lon); if (waypointsWithCoords.length >= 2) { const waypoints = waypointsWithCoords.map(location => location.lat + ',' + location.lon).join('|'); let url = `${this.routingUrl}?waypoints=${waypoints}&mode=${this.options.mode}`; url += this.options.lang ? `&lang=${this.options.lang}` : ''; const avoids = []; if (this.options.avoidTolls) { avoids.push("tolls"); } if (this.options.avoidFerries) { avoids.push("ferries"); } if (this.options.avoidHighways) { avoids.push("highways"); } url += avoids.length ? `&avoid=${avoids.join("|")}` : ''; const details = ["instruction_details"]; if (this.options.elevation) { details.push("elevation"); } url += details.length ? `&details=${details.join(",")}` : ''; if (this.options.units === 'imperial') { url += "&units=imperial"; } url += `&apiKey=${this.apiKey}`; if (this.previousRoutingAPICallURL === url) { // Do nothing when the call is repeated return; } this.inProgress(true); fetch(url).then(response => response.json()).then(result => { this.previousRoutingAPICallURL = url; if (result?.features?.length) { this.onRouteCalculated(result.features[0]); resolve(result.features[0]); } else if (result.features && result.features?.length === 0) { this.showMessage(this.labels.noRouteFound); reject(this.labels.noRouteFound); } else if (result.error) { this.showMessage(result, true); reject(result.error); } this.inProgress(false); }, () => { this.inProgress(false); }); } else if (this.options.calculateRouteTrigger !== 'auto') { this.showMessage(this.labels["warning.min2Waypoints"], true); reject(this.labels["warning.min2Waypoints"]); } }); } on(operation, callback) { if (operation === 'modeChanged' && this.callbacksModeChanged.indexOf(callback) < 0) { this.callbacksModeChanged.push(callback); } if (operation === 'optionChanged' && this.callbacksOptionsChanged.indexOf(callback) < 0) { this.callbacksOptionsChanged.push(callback); } if (operation === 'waypointChanged' && this.callbacksWaypointsChanged.indexOf(callback) < 0) { this.callbacksWaypointsChanged.push(callback); } if (operation === 'routeCalculated' && this.callbacksRouteCalculated.indexOf(callback) < 0) { this.callbacksRouteCalculated.push(callback); } } off(operation, callback) { if (operation === 'modeChanged' && this.callbacksModeChanged.indexOf(callback) >= 0) { this.callbacksModeChanged.splice(this.callbacksModeChanged.indexOf(callback), 1); } if (operation === 'optionChanged' && this.callbacksOptionsChanged.indexOf(callback) >= 0) { this.callbacksOptionsChanged.splice(this.callbacksOptionsChanged.indexOf(callback), 1); } if (operation === 'waypointChanged' && this.callbacksWaypointsChanged.indexOf(callback) >= 0) { this.callbacksWaypointsChanged.splice(this.callbacksWaypointsChanged.indexOf(callback), 1); } if (operation === 'routeCalculated' && this.callbacksRouteCalculated.indexOf(callback) >= 0) { this.callbacksRouteCalculated.splice(this.callbacksRouteCalculated.indexOf(callback), 1); } } addLocation(lat, lon, address) { const waypoint = this.options.waypoints.find(waypoint => !waypoint.lon || !waypoint.lat); if (waypoint) { waypoint.lat = lat; waypoint.lon = lon; waypoint.address = address; this.onWaypointsChanged(waypoint, 'changed'); if (address && waypoint.geocoder) { waypoint.geocoder.setValue(address); } const url = `${this.reverseGeocodingAddress}?lat=${waypoint.lat}&lon=${waypoint.lon}&format=json&apiKey=${this.apiKey}`; fetch(url).then(result => result.json()).then(result => { if (result && result.results?.length) { waypoint.address = result.results[0].formatted; if (waypoint.geocoder) { waypoint.geocoder.setValue(result.results[0].formatted); } } else { // address is not found waypoint.address = `${waypoint.lat} ${waypoint.lon}`; if (waypoint.geocoder) { waypoint.geocoder.setValue(`${waypoint.lat} ${waypoint.lon}`); } } this.updateWaypointControls(); }, err => { this.showMessage(err, true); }); } } getOptions() { return this.options; } sendAutoRequest() { if (this.options.calculateRouteTrigger !== 'auto') { return; } // Cancel previous timeout if (this.currentTimeout) { window.clearTimeout(this.currentTimeout); this.currentTimeout = null; } // Cancel previous request if (this.currentPromiseReject) { this.currentPromiseReject({ canceled: true }); this.currentPromiseReject = null; this.previousRoutingAPICallURL = null; } this.currentTimeout = window.setTimeout(() => { /* Create a new promise and send geocoding request */ const promise = new Promise((resolve, reject) => { this.currentPromiseReject = reject; this.calculate().then(() => { this.currentPromiseReject = null; resolve(); }); }); }, this.options.debounceDelay); } inProgress(value) { if (value) { this.container.querySelector(".geoapify-route-directions-in-progress-container").classList.add("visible"); } else { this.container.querySelector(".geoapify-route-directions-in-progress-container").classList.remove("visible"); } } onWaypointsChanged(waypoint, reason) { this.callbacksWaypointsChanged.forEach(callback => callback(waypoint, reason)); this.showMessage(null); this.sendAutoRequest(); } onOptionsChanged(option) { this.callbacksOptionsChanged.forEach(callback => callback(option)); this.showMessage(null); this.sendAutoRequest(); } onModeChanged(mode) { this.callbacksModeChanged.forEach(callback => callback(mode)); this.showMessage(null); this.sendAutoRequest(); } onRouteCalculated(geojson) { this.callbacksRouteCalculated.forEach(callback => callback(geojson)); this.showMessage(""); } checkAndfillWaypoints() { if (this.options.waypoints && this.options.waypoints.length > 2 && this.options.noStopover) { this.options.waypoints.length = 2; } if (!this.options.waypoints || !this.options.waypoints.length) { this.options.waypoints = [{}, {}]; } if (this.options.waypoints.length === 1) { this.options.waypoints.push({}); } const promises = []; this.options.waypoints.forEach(waypoint => { if (waypoint.address && waypoint.address.length < this.MIN_ADDRESS_LENGTH && (!waypoint.lat || !waypoint.lon)) { // skip this address waypoint.address = ""; this.updateWaypointControls(); } else if (waypoint.address && (!waypoint.lat || !waypoint.lon)) { // geocode this address const url = `${this.geocodingAddress}?text=${encodeURIComponent(waypoint.address)}&format=json&apiKey=${this.apiKey}`; promises.push(new Promise((resolve) => { fetch(url).then(response => response.json()).then(result => { if (result && result.results && result.results.length && result.results[0].lat && result.results[0].lon) { waypoint.address = result.results[0].formatted; waypoint.lat = result.results[0].lat; waypoint.lon = result.results[0].lon; if (waypoint.geocoder) { waypoint.geocoder.setValue(result.results[0].formatted); } } else { // address is not found waypoint.address = ""; if (waypoint.geocoder) { waypoint.geocoder.setValue(""); } this.showMessage(result, true); } this.updateWaypointControls(); resolve(); }, err => { this.showMessage(err); resolve(); }); })); } else if (waypoint.lat && waypoint.lon && (!waypoint.address || waypoint.address.length < this.MIN_ADDRESS_LENGTH)) { const url = `${this.reverseGeocodingAddress}?lat=${waypoint.lat}&lon=${waypoint.lon}&format=json&apiKey=${this.apiKey}`; promises.push(new Promise(resolve => { fetch(url).then(result => result.json()).then(result => { if (result && result.results?.length) { waypoint.address = result.results[0].formatted; if (waypoint.geocoder) { waypoint.geocoder.setValue(result.results[0].formatted); } } else { // address is not found waypoint.address = `${waypoint.lat} ${waypoint.lon}`; if (waypoint.geocoder) { waypoint.geocoder.setValue(`${waypoint.lat} ${waypoint.lon}`); } } this.updateWaypointControls(); resolve(); }, err => { this.showMessage(err, true); resolve(); }); })); } }); Promise.all(promises).then(() => { this.sendAutoRequest(); }); } updateWaypointControls() { const allWaypointContainers = this.container.querySelectorAll('.geoapify-route-directions-waypoint'); const canDelete = allWaypointContainers.length > 2; // show switch button if (!canDelete) { this.container.querySelector('.geoapify-route-directions-switch-waypoints').classList.add("visible"); } else { this.container.querySelector('.geoapify-route-directions-switch-waypoints').classList.remove("visible"); } Array.from(allWaypointContainers).forEach((container, index) => { // allow delete buttons if (canDelete) { container.classList.add('removable'); } else { container.classList.remove('removable'); } // icons if (index === allWaypointContainers.length - 1) { container.querySelector(".geoapify-route-directions-waypoint-icon").classList.add('last'); } else { container.querySelector(".geoapify-route-directions-waypoint-icon").classList.remove('last'); } }); } updateModeButtons() { const buttonElements = this.container.querySelectorAll('.geoapify-route-directions-mode-button'); Array.from(buttonElements).forEach((buttonElem) => { let mode; buttonElem.classList.forEach(className => { if (className.startsWith("mode__")) { mode = className.replace("mode__", ""); } }); if (mode === this.options.mode) { buttonElem.classList.add('active'); } else { buttonElem.classList.remove('active'); } }); } generateControls() { if (this.container.style.position !== 'absolute') { this.container.style.position = "relative"; } // isProgress container const inProgressContainer = document.createElement("div"); inProgressContainer.classList.add("geoapify-route-directions-in-progress-container"); inProgressContainer.innerHTML = `<div class="lds-facebook"><div></div><div></div><div></div></div>`; this.container.appendChild(inProgressContainer); // generate modes const checkedModes = this.options.supportedModes.filter(mode => this.allowedModes.indexOf(mode) >= 0); const modesContainer = document.createElement("div"); modesContainer.classList.add("geoapify-route-directions-mode-container"); checkedModes.forEach(mode => { const modeButton = document.createElement("div"); modeButton.classList.add("geoapify-route-directions-mode-button"); modeButton.classList.add(`mode__${mode}`); modeButton.title = this.labels[mode]; this.addIcon(modeButton, mode); modeButton.addEventListener("click", () => { this.options.mode = mode; this.updateModeButtons(); this.onModeChanged(mode); }); modesContainer.appendChild(modeButton); }); this.container.appendChild(modesContainer); // generate waypoint inputs const waypointsContainerAndSwapButtonConrainer = document.createElement("div"); waypointsContainerAndSwapButtonConrainer.classList.add("geoapify-route-directions-waypoint-and-swap-button-container"); this.container.appendChild(waypointsContainerAndSwapButtonConrainer); const waypointsContainer = document.createElement("div"); waypointsContainer.classList.add("geoapify-route-directions-waypoint-container"); waypointsContainerAndSwapButtonConrainer.appendChild(waypointsContainer); this.options.waypoints.forEach((wayPointData, index) => { this.generateWaypoint(waypointsContainer, wayPointData); }); // swap button const switchButton = document.createElement("div"); switchButton.classList.add("geoapify-route-directions-switch-waypoints"); this.addIcon(switchButton, 'swap-vert'); switchButton.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); this.swapWaypoints(); }); waypointsContainerAndSwapButtonConrainer.appendChild(switchButton); // add destination button if (!this.options.noStopover) { const addDestination = document.createElement("div"); addDestination.classList.add("geoapify-route-directions-add-destination"); const addDestinationIcon = document.createElement("span"); addDestinationIcon.classList.add("icon"); this.addIcon(addDestinationIcon, 'plus-circle'); addDestination.appendChild(addDestinationIcon); const addDestinationText = document.createElement("span"); addDestinationText.classList.add("text"); addDestinationText.textContent = this.labels["addDestination"]; addDestination.appendChild(addDestinationText); addDestination.addEventListener('click', (e) => { this.addWaypoint(); }); this.container.appendChild(addDestination); } // options const optionsContainer = document.createElement("div"); optionsContainer.classList.add("geoapify-route-directions-options-container"); this.generateOptions(optionsContainer); this.container.appendChild(optionsContainer); if (this.options.calculateRouteTrigger === 'buttonClick') { const generateRouteButtonContainer = document.createElement("div"); generateRouteButtonContainer.classList.add("geoapify-route-directions-generate-button-container"); const button = document.createElement("button"); button.classList.add("geoapify-route-directions-generate-button"); button.textContent = this.labels.calculateButton; button.addEventListener('click', () => { this.calculate(); }); generateRouteButtonContainer.appendChild(button); this.container.appendChild(generateRouteButtonContainer); } // generate message container const messageContainer = document.createElement("div"); messageContainer.classList.add("geoapify-route-directions-message"); this.container.appendChild(messageContainer); this.updateWaypointControls(); this.updateModeButtons(); } generateOptions(container) { const optionsLine1 = document.createElement("div"); optionsLine1.classList.add("geoapify-route-directions-options-line"); if (this.options.supportedOptions.indexOf("highways") >= 0) { const highwayCheckbox = document.createElement('label'); highwayCheckbox.classList.add("geoapify-route-directions-options-checkbox"); const inputElement = document.createElement("input"); inputElement.setAttribute("type", "checkbox"); inputElement.checked = this.options.avoidHighways || false; inputElement.addEventListener("change", (event) => { this.options.avoidHighways = event.currentTarget.checked; this.onOptionsChanged("highways"); }); highwayCheckbox.appendChild(inputElement); const textElement = document.createElement("span"); textElement.classList.add("text"); textElement.textContent = this.labels.avoidHighways; highwayCheckbox.appendChild(textElement); optionsLine1.appendChild(highwayCheckbox); } if (this.options.supportedOptions.indexOf("tolls") >= 0) { const checkbox = document.createElement('label'); checkbox.classList.add("geoapify-route-directions-options-checkbox"); const inputElement = document.createElement("input"); inputElement.setAttribute("type", "checkbox"); inputElement.checked = this.options.avoidTolls || false; inputElement.addEventListener("change", (event) => { this.options.avoidTolls = event.currentTarget.checked; this.onOptionsChanged("tolls"); }); checkbox.appendChild(inputElement); const textElement = document.createElement("span"); textElement.classList.add("text"); textElement.textContent = this.labels.avoidTolls; checkbox.appendChild(textElement); optionsLine1.appendChild(checkbox); } if (this.options.supportedOptions.indexOf("ferries") >= 0) { const checkbox = document.createElement('label'); checkbox.classList.add("geoapify-route-directions-options-checkbox"); const inputElement = document.createElement("input"); inputElement.setAttribute("type", "checkbox"); inputElement.checked = this.options.avoidFerries || false; inputElement.addEventListener("change", (event) => { this.options.avoidFerries = event.currentTarget.checked; this.onOptionsChanged("ferries"); }); checkbox.appendChild(inputElement); const textElement = document.createElement("span"); textElement.classList.add("text"); textElement.textContent = this.labels.avoidFerries; checkbox.appendChild(textElement); optionsLine1.appendChild(checkbox); } if (optionsLine1.hasChildNodes()) { const avoidLabel = document.createElement("span"); avoidLabel.classList.add("geoapify-route-directions-options-label"); avoidLabel.textContent = this.labels.avoid; optionsLine1.insertBefore(avoidLabel, optionsLine1.firstChild); container.appendChild(optionsLine1); } const optionsLine2 = document.createElement("div"); optionsLine2.classList.add("geoapify-route-directions-options-line"); if (this.options.supportedOptions.indexOf("units") >= 0) { const radiobutton1 = document.createElement('label'); radiobutton1.classList.add("geoapify-route-directions-options-radiobutton"); const inputElement1 = document.createElement("input"); inputElement1.setAttribute("type", "radio"); inputElement1.setAttribute("name", "units"); inputElement1.setAttribute("value", "imperial"); inputElement1.checked = this.options.units === 'imperial'; inputElement1.addEventListener("change", (event) => { this.options.units = event.currentTarget.checked ? 'imperial' : 'metric'; this.onOptionsChanged("units"); }); radiobutton1.appendChild(inputElement1); const textElement1 = document.createElement("span"); textElement1.classList.add("text"); textElement1.textContent = this.labels.unitsMiles; radiobutton1.appendChild(textElement1); const radiobutton2 = document.createElement('label'); radiobutton1.classList.add("geoapify-route-directions-options-radiobutton"); const inputElement2 = document.createElement("input"); inputElement2.setAttribute("type", "radio"); inputElement2.setAttribute("name", "units"); inputElement2.setAttribute("value", "metric"); inputElement2.addEventListener("change", (event) => { this.options.units = event.currentTarget.checked ? 'metric' : 'imperial'; this.onOptionsChanged("units"); }); inputElement2.checked = this.options.units !== 'imperial'; radiobutton2.appendChild(inputElement2); radiobutton2.classList.add("geoapify-route-directions-options-radiobutton"); const textElement2 = document.createElement("span"); textElement2.classList.add("text"); textElement2.textContent = this.labels.unitsKilometers; radiobutton2.appendChild(textElement2); optionsLine2.appendChild(radiobutton1); optionsLine2.appendChild(radiobutton2); } if (optionsLine2.hasChildNodes()) { const unitsLabel = document.createElement("span"); unitsLabel.classList.add("geoapify-route-directions-options-label"); unitsLabel.textContent = this.labels.units; optionsLine2.insertBefore(unitsLabel, optionsLine2.firstChild); container.appendChild(optionsLine2); } } generateWaypoint(container, waypointData) { const waypoint = document.createElement("div"); waypoint.classList.add("geoapify-route-directions-waypoint"); waypoint.setAttribute("draggable", "true"); const waypointIcon = document.createElement("div"); waypointIcon.classList.add("geoapify-route-directions-waypoint-icon"); const waypointIconLine = document.createElement("span"); waypointIconLine.classList.add("geoapify-route-directions-waypoint-icon-line"); waypointIcon.appendChild(waypointIconLine); const waypointIconIcon = document.createElement("span"); waypointIconIcon.classList.add("geoapify-route-directions-waypoint-icon-icon"); waypointIcon.appendChild(waypointIconIcon); waypoint.appendChild(waypointIcon); const geocoderElement = document.createElement("div"); geocoderElement.classList.add("geoapify-route-directions-waypoint-address-input"); geocoderElement.setAttribute("draggable", "true"); geocoderElement.addEventListener("dragstart", (event) => { event.preventDefault(); event.stopPropagation(); }); const geocoder = new GeocoderAutocomplete(geocoderElement, this.apiKey, this.geocoderOptions); geocoder.setValue(waypointData.address || ''); waypointData.geocoder = geocoder; geocoder.on('select', (location) => { if (location) { waypointData.address = location.properties.formatted; waypointData.lon = location.properties.lon; waypointData.lat = location.properties.lat; if (geocoder.isOpen()) { geocoder.once('close', () => { this.onWaypointsChanged(waypointData, 'changed'); }); } else { this.onWaypointsChanged(waypointData, 'changed'); } } else { delete waypointData.address; delete waypointData.lon; delete waypointData.lat; this.onWaypointsChanged(waypointData, 'changed'); } }); waypoint.appendChild(geocoderElement); const removeButtonElement = document.createElement("div"); removeButtonElement.classList.add("geoapify-route-directions-waypoint-remove-button-container"); const removeButton = document.createElement("div"); removeButton.classList.add("geoapify-route-directions-waypoint-remove-button"); removeButton.setAttribute("draggable", "true"); removeButton.addEventListener("dragstart", (event) => { event.preventDefault(); event.stopPropagation(); }); this.addIcon(removeButton, 'close-circle'); removeButtonElement.appendChild(removeButton); removeButtonElement.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); this.removeWaypoint(waypointData, waypoint); }); waypoint.appendChild(removeButtonElement); container.appendChild(waypoint); this.addDradAndDropFunctionality(waypoint); } showMessage(error, isError) { const textContainer = this.container.querySelector('.geoapify-route-directions-message'); if (!error) { textContainer.textContent = ''; } else if (error.error && error.message) { // this error comes from an API call textContainer.textContent = `${error.error}: ${error.message}`; } else { textContainer.textContent = error; } if (isError) { textContainer.classList.add("error"); } else { textContainer.classList.remove("error"); } } /* Drag events */ handleDragStart(e) { this.dragElement = e.target; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setDragImage(this.dragElement.querySelector(".geoapify-route-directions-waypoint-address-input"), 0, 0); this.dragElement.classList.add('dragElem'); } handleDragEnd(e) { const element = e.target; if (element) { element.classList.remove('dragElem'); } this.container.querySelectorAll(".geoapify-route-directions-waypoint").forEach(element => element.classList.remove("over")); } handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; const element = e.target.closest(".geoapify-route-directions-waypoint"); if (element) { const isBottom = ((element.nextSibling !== this.dragElement) && (e.clientY - element.getBoundingClientRect().top) > element.getBoundingClientRect().height / 2) || element.previousSibling === this.dragElement; if (isBottom) { element.classList.add("bottom"); } else { element.classList.remove("bottom"); } } return false; } getAllSiblings(itemElement) { var result = [], node = itemElement.parentNode.firstChild; while (node) { if (node !== itemElement && node.nodeType === Node.ELEMENT_NODE) result.push(node); node = node.nextSibling; } return result; } handleDragEnter(e) { e.preventDefault(); const itemElement = e.target.closest(".geoapify-route-directions-waypoint"); if (itemElement !== this.dragElement && itemElement) { itemElement.classList.add("over"); this.getAllSiblings(itemElement).forEach(element => element.classList.remove("over")); } } handleDragLeave(e) { const itemElement = e.target.closest(".geoapify-route-directions-waypoint"); if (itemElement) { const withinElement = e.clientY - itemElement.getBoundingClientRect().top > 0 && e.clientY - itemElement.getBoundingClientRect().top < itemElement.getBoundingClientRect().height; if (!withinElement) { itemElement.classList.remove("over"); } } } handleDrop(e) { e.stopPropagation(); this.dragElement.classList.remove('dragElem'); let element = e.target.closest(".geoapify-route-directions-waypoint"); // Don't do anything if dropping the same column we're dragging. if (this.dragElement !== element) { if (element.classList.contains("bottom")) { this.moveWaypoints(this.getElementIndex(this.dragElement), this.getElementIndex(element) + 1); element.parentNode.insertBefore(this.dragElement, element.nextElementSibling); } else { this.moveWaypoints(this.getElementIndex(this.dragElement), this.getElementIndex(element)); element.parentNode.insertBefore(this.dragElement, element); } this.updateWaypointControls(); } element.classList.remove('over'); return false; } addDradAndDropFunctionality(wayPointContainer) { wayPointContainer.addEventListener('dragstart', this.handleDragStart.bind(this), false); wayPointContainer.addEventListener('dragenter', this.handleDragEnter.bind(this), false); wayPointContainer.addEventListener('dragover', this.handleDragOver.bind(this), false); wayPointContainer.addEventListener('dragleave', this.handleDragLeave.bind(this), false); wayPointContainer.addEventListener('drop', this.handleDrop.bind(this), false); wayPointContainer.addEventListener('dragend', this.handleDragEnd.bind(this), false); } getElementIndex(element) { if (!element) { return -1; } for (var i = 0; i < element.parentNode.children.length; i++) { if (element.parentNode.children[i] === element) { return i; } } return -1; } moveWaypoints(fromIndex, toIndex) { var waypoint = this.options.waypoints[fromIndex]; if (fromIndex < toIndex) { this.options.waypoints.splice(toIndex, 0, waypoint); this.options.waypoints.splice(fromIndex, 1); } else { this.options.waypoints.splice(fromIndex, 1); this.options.waypoints.splice(toIndex, 0, waypoint); } this.onWaypointsChanged(waypoint, 'moved'); } removeWaypoint(waypoint, waypointElement) { const index = this.options.waypoints.indexOf(waypoint); this.options.waypoints.splice(index, 1); waypointElement.remove(); this.updateWaypointControls(); this.onWaypointsChanged(waypoint, 'removed'); } swapWaypoints() { if (this.options.waypoints.length !== 2) { return; } const container = this.container.querySelector(".geoapify-route-directions-waypoint-container"); container.insertBefore(container.lastElementChild, container.firstElementChild); const waypoint = this.options.waypoints[1]; this.options.waypoints[1] = this.options.waypoints[0]; this.options.waypoints[0] = waypoint; this.updateWaypointControls(); this.onWaypointsChanged(this.options.waypoints[0], 'moved'); this.onWaypointsChanged(this.options.waypoints[1], 'moved'); } addWaypoint() { const waypoint = {}; this.options.waypoints.push(waypoint); const container = this.container.querySelector(".geoapify-route-directions-waypoint-container"); this.generateWaypoint(container, waypoint); this.updateWaypointControls(); this.onWaypointsChanged(waypoint, 'added'); } addIcon(element, icon) { // Material Icons: https://fonts.google.com/icons?selected=Material+Icons const icons = { "close": { path: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z", viewbox: "0 0 24 24" }, "close-circle": { path: "M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z", viewbox: "0 0 24 24" }, "swap-vert": { path: "M16 17.01V10h-2v7.01h-3L15 21l4-3.99h-3zM9 3L5 6.99h3V14h2V6.99h3L9 3z", viewbox: "0 0 24 24" }, "plus-circle": { path: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z", viewbox: "0 0 24 24" }, 'walk': { path: "M13.5 5.5c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zM9.8 8.9L7 23h2.1l1.8-8 2.1 2v6h2v-7.5l-2.1-2 .6-3C14.8 12 16.8 13 19 13v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-.4-.6-1-1-1.7-1-.3 0-.5.1-.8.1L6 8.3V13h2V9.6l1.8-.7", viewbox: "0 0 24 24" }, 'hike': { path: "M13.5,5.5c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S12.4,5.5,13.5,5.5z M17.5,10.78c-1.23-0.37-2.22-1.17-2.8-2.18l-1-1.6 c-0.41-0.65-1.11-1-1.84-1c-0.78,0-1.59,0.5-1.78,1.44S7,23,7,23h2.1l1.8-8l2.1,2v6h2v-7.5l-2.1-2l0.6-3c1,1.15,2.41,2.01,4,2.34V23 H19V9h-1.5L17.5,10.78z M7.43,13.13l-2.12-0.41c-0.54-0.11-0.9-0.63-0.79-1.17l0.76-3.93c0.21-1.08,1.26-1.79,2.34-1.58l1.16,0.23 L7.43,13.13z", viewbox: "0 0 24 24" }, 'scooter': { g: '<path d="M19,7c0-1.1-0.9-2-2-2h-3v2h3v2.65L13.52,14H10V9H6c-2.21,0-4,1.79-4,4v3h2c0,1.66,1.34,3,3,3s3-1.34,3-3h4.48L19,10.35V7 z M7,17c-0.55,0-1-0.45-1-1h2C8,16.55,7.55,17,7,17z"/><rect height="2" width="5" x="5" y="6"/><path d="M19,13c-1.66,0-3,1.34-3,3s1.34,3,3,3s3-1.34,3-3S20.66,13,19,13z M19,17c-0.55,0-1-0.45-1-1s0.45-1,1-1s1,0.45,1,1 S19.55,17,19,17z"/>', viewbox: "0 0 24 24" }, 'motorcycle': { path: "M20,11c-0.18,0-0.36,0.03-0.53,0.05L17.41,9H20V6l-3.72,1.86L13.41,5H9v2h3.59l2,2H11l-4,2L5,9H0v2h4c-2.21,0-4,1.79-4,4 c0,2.21,1.79,4,4,4c2.21,0,4-1.79,4-4l2,2h3l3.49-6.1l1.01,1.01C16.59,12.64,16,13.75,16,15c0,2.21,1.79,4,4,4c2.21,0,4-1.79,4-4 C24,12.79,22.21,11,20,11z M4,17c-1.1,0-2-0.9-2-2c0-1.1,0.9-2,2-2c1.1,0,2,0.9,2,2C6,16.1,5.1,17,4,17z M20,17c-1.1,0-2-0.9-2-2 c0-1.1,0.9-2,2-2s2,0.9,2,2C22,16.1,21.1,17,20,17z", viewbox: "0 0 24 24" }, 'drive': { path: "M18.92 5.01C18.72 4.42 18.16 4 17.5 4h-11c-.66 0-1.21.42-1.42 1.01L3 11v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 15c-.83 0-1.5-.67-1.5-1.5S5.67 12 6.5 12s1.5.67 1.5 1.5S7.33 15 6.5 15zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 10l1.5-4.5h11L19 10H5z", viewbox: "0 0 24 24" }, 'light_truck': { path: "M17 5H3c-1.1 0-2 .89-2 2v9h2c0 1.65 1.34 3 3 3s3-1.35 3-3h5.5c0 1.65 1.34 3 3 3s3-1.35 3-3H23v-5l-6-6zM3 11V7h4v4H3zm3 6.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7-6.5H9V7h4v4zm4.5 6.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM15 11V7h1l4 4h-5z", viewbox: "0 0 24 24" }, 'medium_truck': { path: "M20 8h-3V4H3c-1.1 0-2 .9-2 2v11h2c0 1.66 1.34 3 3 3s3-1.34 3-3h6c0 1.66 1.34 3 3 3s3-1.34 3-3h2v-5l-3-4zM6 18.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5-9l1.96 2.5H17V9.5h2.5zm-1.5 9c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z", viewbox: "0 0 24 24" }, 'truck': { path: "M 2.5054151,6.1751188 C 1.5907946,6.1468929 0.91892251,7.0614706 1.0491949,7.9290257 c 0,2.5705083 0,5.1410173 0,7.7115253 0.4854067,0 0.9708135,0 1.4562202,0 -0.05145,1.377013 1.4107818,2.50715 2.730993,2.115703 0.9433711,-0.226161 1.655859,-1.145352 1.6376679,-2.115703 0.067577,0.572742 0.2975838,1.295861 0.8294942,1.686275 1.0525842,0.934273 2.9037738,0.481796 3.4033858,-0.835069 0.170193,-0.471967 0.05658,-1.040934 0.717092,-0.851206 1.767291,0 3.534583,0 5.301875,0 -0.05145,1.377013 1.410782,2.50715 2.730994,2.115703 0.943371,-0.226161 1.655858,-1.145352 1.637667,-2.115703 0.485407,0 0.970814,0 1.456221,0 0,-1.213517 0,-2.427034 0,-3.640551 -0.72811,-0.970814 -1.456221,-1.941627 -2.184331,-2.9124408 -0.72811,0 -1.45622,0 -2.18433,0 0,-0.9708135 0,-1.9416269 0,-2.9124404 -5.35891,0 -10.7178193,0 -16.0767289,0 z M 18.582144,10.179725 c 0.606758,0 1.213517,0 1.820275,0 0.475926,0.606758 0.951853,1.213517 1.427779,1.820275 -1.082685,0 -2.165369,0 -3.248054,0 0,-0.606758 0,-1.213517 0,-1.820275 z m -13.8923984,4.36866 c 0.9361805,-0.05191 1.4690175,1.239354 0.7727069,1.864873 -0.6255189,0.69631 -1.9167813,0.163473 -1.8648722,-0.772707 -0.012979,-0.587727 0.5040101,-1.105082 1.0921653,-1.092166 z m 4.404213,0 c 0.9361804,-0.05191 1.4690174,1.239353 0.7727072,1.864873 -0.625519,0.69631 -1.9167815,0.163474 -1.8648724,-0.772707 -0.012979,-0.587727 0.50401,-1.105082 1.0921652,-1.092166 z m 10.2162954,0 c 0.93618,-0.05191 1.469017,1.239354 0.772707,1.864873 -0.625519,0.69631 -1.916781,0.163473 -1.864872,-0.772707 -0.01298,-0.587727 0.50401,-1.105082 1.092165,-1.092166 z", viewbox: "0 0 24 24" }, 'bicycle': { path: "M18.18,10l-1.7-4.68C16.19,4.53,15.44,4,14.6,4H12v2h2.6l1.46,4h-4.81l-0.36-1H12V7H7v2h1.75l1.82,5H9.9 c-0.44-2.23-2.31-3.88-4.65-3.99C2.45,9.87,0,12.2,0,15c0,2.8,2.2,5,5,5c2.46,0,4.45-1.69,4.9-4h4.2c0.44,2.23,2.31,3.88,4.65,3.99 c2.8,0.13,5.25-2.19,5.25-5c0-2.8-2.2-5-5-5H18.18z M7.82,16c-0.4,1.17-1.49,2-2.82,2c-1.68,0-3-1.32-3-3s1.32-3,3-3 c1.33,0,2.42,0.83,2.82,2H5v2H7.82z M14.1,14h-1.4l-0.73-2H15C14.56,12.58,14.24,13.25,14.1,14z M19,18c-1.68,0-3-1.32-3-3 c0-0.93,0.41-1.73,1.05-2.28l0.96,2.64l1.88-0.68l-0.97-2.67c0.03,0,0.06-0.01,0.09-0.01c1.68,0,3,1.32,3,3S20.68,18,19,18z", viewbox: "0 0 24 24" }, 'mountain_bike': { path: "m 0,24 c 1.0483746,-0.970116 2.0967492,-1.940233 3.1451238,-2.910349 0.687195,0.188901 1.3743899,0.377803 2.0615849,0.566704 0.074467,0.252081 -0.1708548,1.106337 0.1515681,0.891506 0.9271663,-0.631226 1.8543325,-1.262452 2.7814987,-1.893678 0.1250469,0.362293 0.2500939,0.724585 0.3751408,1.086878 1.3501999,-0.776293 2.7003997,-1.552587 4.0505997,-2.32888 0.642873,0.19549 1.285745,0.39098 1.928618,0.58647 -0.127277,0.283555 -0.645555,0.868237 -0.01401,0.567389 2.239459,-0.59089 4.478919,-1.181779 6.718378,-1.772668 0.935096,1.376813 1.870193,2.753627 2.805289,4.13044 C 23.957138,23.257878 24.089456,23.771551 23.937553,24 15.958369,24 7.9791844,24 0,24 Z M 17.368208,9.4088866 C 16.495762,7.8768704 15.676923,6.3113665 14.769415,4.8012452 14.192639,3.9996558 13.13896,3.9319833 12.259493,4.157355 c -0.650058,0.1051652 -1.300117,0.2103305 -1.950176,0.3154957 0.106468,0.6581102 0.212935,1.3162205 0.319403,1.9743307 0.855543,-0.1384081 1.711087,-0.2768162 2.56663,-0.4152243 0.693356,1.238499 1.386712,2.476998 2.080068,3.715497 C 13.692663,10.003509 12.109907,10.259564 10.527152,10.515619 10.413979,10.220851 10.030833,9.7949273 10.073716,9.5759734 10.373468,9.451476 11.0236,9.5797778 11.087879,9.2853869 10.98806,8.6683735 10.88824,8.0513601 10.788421,7.4343467 9.1431456,7.7005161 7.4978702,7.9666855 5.8525948,8.2328549 5.9590626,8.8909653 6.0655303,9.5490756 6.1719981,10.207186 6.7478445,10.114027 7.323691,10.020867 7.8995374,9.9277077 8.7645873,11.476097 9.6296371,13.024487 10.494687,14.572877 9.5435276,15.028627 9.5970731,13.715609 9.0103071,13.244131 7.3360567,11.091839 3.8504884,10.850732 1.8756862,12.714557 c -1.89349434,1.592804 -2.27771979,4.608077 -0.8265331,6.612325 1.5012812,2.304528 4.9596557,2.801693 7.1134351,1.138433 1.2031446,-0.875523 1.9541548,-2.325049 1.9901008,-3.811107 1.382031,-0.223582 2.764063,-0.447165 4.146094,-0.670747 0.833906,2.46827 3.796455,3.856612 6.233864,2.970299 2.561793,-0.818455 4.120479,-3.884211 3.158947,-6.419742 C 22.94954,10.186324 20.304907,8.7594829 17.929506,9.307951 17.747649,9.3441724 17.530468,9.358979 17.368208,9.4088866 Z M 8.0993851,16.986388 C 7.8473918,18.935926 5.4464176,20.058984 3.7591603,19.117694 1.9703775,18.262883 1.5961278,15.561802 3.0917798,14.257334 4.4269623,12.93814 6.9479287,13.293533 7.7799819,15.012057 6.8520465,15.162176 5.924111,15.312296 4.9961756,15.462415 c 0.1064678,0.65811 0.2129355,1.316221 0.3194033,1.974331 0.9279354,-0.150119 1.8558708,-0.300239 2.7838062,-0.450358 z M 13.97938,14.009131 c -0.460677,0.07453 -0.921354,0.149055 -1.382031,0.223582 -0.346678,-0.61925 -0.693356,-1.238499 -1.040034,-1.857749 0.997037,-0.161299 1.994074,-0.322597 2.991111,-0.483896 -0.347528,0.650456 -0.552895,1.379325 -0.569046,2.118063 z m 5.475917,3.166123 c -1.770228,0.361248 -3.560535,-1.192079 -3.480313,-2.991203 -0.04759,-0.474995 0.465112,-2.350537 0.894752,-1.582458 0.395551,0.70855 0.791103,1.417099 1.186655,2.125649 0.582425,-0.323837 1.164849,-0.647674 1.747274,-0.971511 -0.461318,-0.82694 -0.922636,-1.653881 -1.383954,-2.480821 1.770368,-0.437427 3.622872,1.107999 3.567846,2.919897 0.04377,1.455879 -1.093956,2.779037 -2.53226,2.980447 z", viewbox: "0 0 24 24" }, 'road_bike': { path: "M 13.556641 5.2011719 L 13.556641 6.9003906 L 15.765625 6.9003906 L 17.005859 10.300781 L 12.917969 10.300781 L 12.613281 9.4511719 L 13.556641 9.4511719 L 13.556641 7.75 L 9.3066406 7.75 L 9.3066406 9.4511719 L 10.792969 9.4511719 L 12.339844 13.699219 L 11.771484 13.699219 C 11.39752 11.803901 9.8071682 10.402085 7.8183594 10.308594 C 5.4385874 10.189605 3.3574219 12.169056 3.3574219 14.548828 C 3.3574219 16.928599 5.2276499 18.798828 7.6074219 18.798828 C 9.6982213 18.798828 11.389021 17.363703 11.771484 15.400391 L 15.341797 15.400391 C 15.715761 17.295709 17.304159 18.697525 19.292969 18.791016 C 21.672741 18.901506 23.755859 16.929287 23.755859 14.541016 C 23.755859 12.161244 21.885631 10.291016 19.505859 10.291016 L 18.808594 10.291016 L 18.808594 10.300781 L 17.363281 6.3222656 C 17.116805 5.6508299 16.479556 5.2011719 15.765625 5.2011719 L 13.556641 5.2011719 z M 3.6972656 5.2324219 L 3.6972656 6.3886719 L 10.015625 6.3886719 L 10.015625 5.2324219 L 3.6972656 5.2324219 z M 0.13476562 6.8574219 L 0.13476562 8.0136719 L 6.453125 8.0136719 L 6.453125 6.8574219 L 0.13476562 6.8574219 z M 1.7011719 8.5410156 L 1.7011719 9.6972656 L 8.0195312 9.6972656 L 8.0195312 8.5410156 L 1.7011719 8.5410156 z M 7.6074219 12 C 8.7378135 12 9.6639385 12.704814 10.003906 13.699219 L 7.6074219 13.699219 L 7.6074219 15.400391 L 10.003906 15.400391 C 9.6639386 16.394794 8.7378135 17.099609 7.6074219 17.099609 C 6.1795587 17.099609 5.0566406 15.976691 5.0566406 14.548828 C 5.0566406 13.120965 6.1795587 12 7.6074219 12 z M 13.53125 12 L 16.105469 12 C 15.731505 12.492953 15.460786 13.06178 15.341797 13.699219 L 14.150391 13.699219 L 13.53125 12 z M 19.513672 12 C 20.941536 12 22.064453 13.120965 22.064453 14.548828 C 22.064453 15.976691 20.933722 17.099609 19.505859 17.099609 C 18.077996 17.099609 16.955078 15.976691 16.955078 14.548828 C 16.955078 13.758404 17.303708 13.078783 17.847656 12.611328 L 18.664062 14.855469 L 20.261719 14.277344 L 19.4375 12.007812 C 19.463 12.007812 19.488182 12 19.513672 12 z", viewbox: "0 0 24 24" }, 'bus': { path: "M4 16c0 .88.39 1.67 1 2.22V20c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h8v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1.78c.61-.55 1-1.34 1-2.22V6c0-3.5-3.58-4-8-4s-8 .5-8 4v10zm3.5 1c-.83 0-1.5-.67-1.5-1.5S6.67 14 7.5 14s1.5.67 1.5 1.5S8.33 17 7.5 17zm9 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm1.5-6H6V6h12v5z", viewbox: "0 0 24 24" } }; var svgElement = document.createElementNS("http://www.w3.org/2000/svg", 'svg'); svgElement.setAttribute('viewBox', icons[icon].viewbox); svgElement.setAttribute('height', "24"); if (icons[icon].path) { var iconElement = document.createElementNS("http://www.w3.org/2000/svg", 'path'); iconElement.setAttribute("d", icons[icon].path); iconElement.setAttribute('fill', 'currentColor'); svgElement.appendChild(iconElement); } else if (icons[icon].g) { var gElement = document.createElementNS("http://www.w3.org/2000/svg", 'g'); gElement.innerHTML = icons[icon].g; gElement.setAttribute('fill', 'currentColor'); svgElement.appendChild(gElement); } element.appendChild(svgElement); } }