How to optimize waypoints in fastest order?

Hello,

I managed to create a map where I can add multiple waypoints. See https://www.snelsteroute.nl/html/waypoints.html
Question: How can order 10 or more multiple waypoints in a fastest route?

Thanks for your answers!

Fred

Hi Fred,

Basically you will want to make a normal GET route request and be sure to have the computeBestOrder param set.

You can find the documentation here: https://developer.tomtom.com/routing-api/routing-api-documentation-routing/calculate-route

Just do a ctrl-f of computeBestOrder.

I’m not sure what the Location Limit is on TomTom - 10 seems like a lot. If you are doing something massive you’ll likely have to set up your own Routing Server. Typically this problem is called the “Traveling Salesman Problem” to help your research/googling.

Side note - I don’t work for TomTom or anything - just a helpful friendly dev at Truck Driver Power, Inc.

Like Dan mentioned - computeBestOrder. It can be added as an option for calculateRoute method.

Hello Dan and Przemek,

Thnxs both for your comments.
Hopefully one of you can help me further? Unfortunately there is no ready script yet for optimizing waypoints so I need some help.

Question: How en where can I add an extra button in the following waypoints script which will execute the ComputeBestOrder function? Https://www.snelsteroute.nl/html/waypoints.html

I created the request URL: https://api.tomtom.com/routing/1/calculateRoute/52.50931%2C13.42936%3A52.50274%2C13.43872/json?computeBestOrder=true&routeType=shortest&avoid=unpavedRoads&travelMode=car&key=*****
But how to implement this into the waypoints script with a button?

I have already tried to put computeBestOrder=true into the script (maybe at the wrong place?)
However the script didn’t optimize the given waypoints/adresses.

Hope one of you will help me out in case?

Thnxs Fred

Waypoints #foldable { width: 320px; } #form { margin-top: 10px; } .tt-icon-size { height: 18px !important; width: 18px !important; } .icon-spacing-right { margin-right: 12px; margin-top: 22px; } .icon-spacing-left { margin-left: 12px; margin-top: 24px; } .route-input-container { display: flex; } button.add-stop-btn { margin-left: 30px; margin-top: 24px; } .remove-btn { background-color: transparent; border: 0; } .remove-btn.hidden { visibility: hidden; } .waypoint-marker { align-items: center; background-color: #4a90e2; border: solid 3px #2faaff; border-radius: 50%; color: #ffffff; display: flex; font-size: 14px; font-weight: bold; height: 32px !important; justify-content: center; text-align: center; width: 32px !important; will-change: unset !important; } .tt-icon-number { align-items: center; background: #ffffff; background-color: #8dc3eb; border-radius: 50%; color: #ffffff; display: flex; justify-content: center; }
var MAX_ROUTING_INPUTS = 7; var Utrecht = [5.91595, 52.36729]; var map = tt.map({ key: '', container: 'map', style: 'tomtom://vector/1/basic-main', center: Utrecht, zoom: 7, dragPan: !isMobileOrTablet() }); map.addControl(new tt.FullscreenControl()); map.addControl(new tt.NavigationControl()); new Foldable('#foldable', 'top-right'); var routingInputs = []; var route; var markers = []; var infoHint = new InfoHint('error', 'bottom-center', 5000).addTo(document.getElementById('map')); var loadingHint = new InfoHint('info', 'bottom-center').addTo(document.getElementById('map'));
    function RoutingService() {}

    RoutingService.prototype.calculateRoute = function() {
        this.clearMarkers();
        if (route) {
            map.removeLayer('route');
            map.removeSource('route');
            route = undefined;
        }
        var locations = this.getLocations();
        this.drawMarkers(locations.arr);
        if (locations.count < 2) {
            return;
        }
        loadingHint.setMessage('Loading...');
        tt.services.calculateRoute({
            key: '',
            traffic: false,
            locations: locations.str
        })
            .go()
            .then(function(response) {
                loadingHint.hide();
                this.clearMarkers();
                var geojson = response.toGeoJson();
                route = map.addLayer({
                    'id': 'route',
                    'type': 'line',
                    'source': {
                        'type': 'geojson',
                        'data': geojson
                    },
                    'paint': {
                        'line-color': '#2faaff',
                        'line-width': 6
                    }
                }, this.findFirstBuildingLayerId());

                this.drawMarkers(locations.arr);
            }.bind(this))
            .catch(function(error) {
                loadingHint.hide();
                infoHint.setErrorMessage(error);
            });
    };

    RoutingService.prototype.clearMarkers = function() {
        markers.forEach(function(marker) {
            marker.remove();
        });
    };

    RoutingService.prototype.drawMarkers = function(locations) {
        var bounds = new tt.LngLatBounds();
        var maxIndex = routingInputs.length - 1;
        if (locations.length === 0) {
            return;
        }

        routingInputs.forEach(function(input, index) {
            if (input.position) {
                var marker = new tt.Marker(this.waypointMarker(index, maxIndex)).setLngLat(input.position).addTo(map);
                markers.push(marker);
                bounds.extend(tt.LngLat.convert(input.position));
            }
        }, this);
        map.fitBounds(bounds, { duration: 0, padding: 100 });
    };

    RoutingService.prototype.findFirstBuildingLayerId = function() {
        var layers = map.getStyle().layers;
        for (var index in layers) {
            if (layers[index].type === 'fill-extrusion') {
                return layers[index].id;
            }
        }

        throw new Error('Map style does not contain any layer with fill-extrusion type.');
    };

    RoutingService.prototype.getLocations = function() {
        var resultStr = '';
        var resultArr = [];
        var count = 0;
        routingInputs.forEach(function(routingInput) {
            if (routingInput.position) {
                count += 1;
                resultArr.push(routingInput.position);
                resultStr += routingInput.position.lng + ',' + routingInput.position.lat + ':';
            }
        });
        resultStr = resultStr.substring(0, resultStr.length - 1);
        return {
            str: resultStr,
            count: count,
            arr: resultArr
        };
    };

    RoutingService.prototype.waypointMarker = function(index, total) {
        var container = document.createElement('div');
        container.className = 'waypoint-marker';
        if (index === 0) {
            container.className += ' tt-icon -start -white';
        } else if (index === total) {
            container.className += ' tt-icon -finish -white';
        } else {
            var number = document.createElement('div');
            number.innerText = index;
            container.appendChild(number);
        }
        return container;
    };

    function RoutingInput(options) {
        this.index = options.index;
        this.routingService = options.routingService;
        this.onRemoveBtnClick = options.onRemove.bind(this);
        this.container = this.createContainer();
        this.searchBox = this.createSearchBox();
        this.icon = this.createIconContainer();
        this.removeButton = this.createRemoveButton();
        this.container.appendChild(this.icon);
        this.container.appendChild(this.searchBox);
        this.container.appendChild(this.removeButton);
    }

    RoutingInput.prototype.createContainer = function() {
        var container = document.createElement('div');
        container.className = 'route-input-container';
        return container;
    };

    RoutingInput.prototype.createSearchBox = function() {
        var searchBox = new tt.plugins.SearchBox(tt.services, {
            showSearchButton: false,
            searchOptions: {
                key: ''
            },
            placeholder: 'Adres invullen...'
        });
        var htmlSearchBox = searchBox.getSearchBoxHTML();
        document.getElementById('searchBoxesPlaceholder').appendChild(htmlSearchBox);
        searchBox.on('tomtom.searchbox.resultselected', this.onResultSelected.bind(this));
        searchBox.on('tomtom.searchbox.resultscleared', this.onResultCleared.bind(this));
        return htmlSearchBox;
    };

    RoutingInput.prototype.getIconType = function() {
        var lastIdx = routingInputs.length - 1;
        switch (this.index) {
        case 0:
            return 'start';
        case lastIdx:
            return 'finish';
        default:
            return 'number';
        }
    };

    RoutingInput.prototype.getIconClassName = function(iconType) {
        switch (iconType) {
        case 'start':
            return 'tt-icon tt-icon-size icon-spacing-right -start';
        case 'finish':
            return 'tt-icon tt-icon-size icon-spacing-right -finish';
        case 'number':
            return 'tt-icon-number tt-icon-size icon-spacing-right icon-number';
        }
    };

    RoutingInput.prototype.createRemoveButton = function() {
        var button = document.createElement('button');
        button.className = 'tt-icon icon-spacing-left remove-btn -trash';
        button.onclick = function(event) {
            event.preventDefault();
            this.container.parentNode.removeChild(this.container);
            routingInputs.splice(this.index, 1);
            this.onRemoveBtnClick();
        }.bind(this);
        return button;
    };

    RoutingInput.prototype.createIconContainer = function() {
        var icon = document.createElement('div');
        return icon;
    };

    RoutingInput.prototype.updateIcons = function() {
        var icon = document.createElement('div');
        var iconType = this.getIconType();
        icon.className = this.getIconClassName(iconType);

        if (iconType === 'number') {
            var number = document.createElement('div');
            number.innerText = this.index;
            icon.appendChild(number);
        }

        this.container.replaceChild(icon, this.icon);
        this.icon = icon;
        if (routingInputs.length <= 2) {
            this.removeButton.classList.add('hidden');
        } else {
            this.removeButton.classList.remove('hidden');
        }
    };

    RoutingInput.prototype.onResultCleared = function() {
        this.position = undefined;
        this.routingService.calculateRoute();
    };

    RoutingInput.prototype.onResultSelected = function(result) {
        this.position = result.data.result.position;
        this.routingService.calculateRoute();
    };

    function Panel(routingService) {
        this.routingService = routingService;
        this.container = document.getElementById('form');
        this.createInput();
        this.createInput();
        this.createAddButton();
    }

    Panel.prototype.createWaypoint = function() {
        var length = routingInputs.length;
        if (length === MAX_ROUTING_INPUTS) {
            infoHint.setMessage('You cannot add more waypoints in this example, but ' +
            'the Routing service supports up to 150 waypoints.');
            return;
        }
        var index = length - 1;
        var routingInput = this.createRoutingInput(index);
        this.container.insertBefore(routingInput.container, routingInputs[length - 1].container);
        routingInputs.splice(index, 0, routingInput);
        this.updateRoutingInputIndexes();
        this.updateRoutingInputIcons();
    };

    Panel.prototype.createRoutingInput = function(index) {
        return new RoutingInput({
            index: index,
            onRemove: this.onRemoveBtnClick.bind(this),
            routingService: this.routingService
        });
    };

    Panel.prototype.createInput = function() {
        var index = routingInputs.length;
        var routingInput = this.createRoutingInput(index);
        this.container.appendChild(routingInput.container);
        routingInputs.push(routingInput);
        routingInput.updateIcons();
    };

    Panel.prototype.createAddButton = function() {
        var button = document.createElement('button');
        button.appendChild(document.createTextNode('Extra adres toevoegen'));
        button.className = 'tt-button -primary add-stop-btn';
        button.onclick = function(event) {
            event.preventDefault();
            this.createWaypoint();
        }.bind(this);
        this.container.appendChild(button);
    };

        Panel.prototype.onRemoveBtnClick = function() {
        this.updateRoutingInputIndexes();
        this.updateRoutingInputIcons();
        this.routingService.calculateRoute();
    };

    Panel.prototype.updateRoutingInputIndexes = function() {
        routingInputs.forEach(function(routingInput, index) {
            routingInput.index = index;
        });
    };

    Panel.prototype.updateRoutingInputIcons = function() {
        routingInputs.forEach(function(routingInput) {
            routingInput.updateIcons();
        });
    };

    var routingService = new RoutingService();
    map.on('load', function() {
        routingService.calculateRoute();
    });

    new Panel(routingService);
</script>
</body>