What is it?

This application finds a near-optimal route through popular Swiss trailheads by minimizing the total distance between them. The problem is modeled as a graph: trailheads are nodes, and the distances between trailheads are weighted edges.

Under the hood, this is a Traveling Salesman Problem. We solve it using Ant Colony Optimization, a nature-inspired algorithm where artificial ants construct possible routes, leave pheromone on promising paths, and gradually improve the best route found. In our implementation, you can visualize the ants creating their solutions step by step, until the best path is found.

Map showing all 359 Swiss trailheads and the best route found after 100 iterations
All 359 trailheads, best path found after 100 iterations
Source control?
GitHub repo: github.com/aaliyahfiala42/SwissTrails
Core Concepts

Traveling Salesman Problem

The Traveling Salesman Problem asks: given a set of locations, what is the shortest possible route that visits each location once and returns to the start?

In this project, the “salesman” is replaced by a route through Swiss trailheads. Since the number of possible route combinations grows extremely quickly as more trailheads are added, this becomes a combinatorial optimization problem.

Ant Colony Optimization

Ant Colony Optimization is inspired by the way real ants find efficient paths to food. Ants explore different routes and leave pheromone behind. Shorter and better routes receive more pheromone, making them more attractive to future ants.

Over many iterations, the colony balances exploration of new routes with exploitation of strong existing routes, which helps it find a good solution to the Traveling Salesman Problem.

Algorithm

The algorithm initializes pheromone trails, repeatedly constructs ant solutions, stores the best solution found so far, optionally performs daemon actions, and then updates the pheromone values.

procedure ACO is
    Initialize step: set parameters and initialize pheromone trail

    while stopping condition not met do
        constructAntSolutions()
        store best solution so far
        daemonActions()      // optional
        pheromoneUpdate()
    repeat

    return best solution
end procedure
Data Source: Swiss trailhead data

The trailhead data comes from SchweizMobil. A CSV file was downloaded containing the trail name, latitude, and longitude. This file is visible in the project’s data folder.

CSV structure

The data file contains three main columns:

  • trail name
  • latitude
  • longitude

When the user limits the number of trails, the application currently selects the first n trailheads from this list.

Haversine Distance

The Haversine formula calculates the shortest distance between two latitude and longitude points while accounting for the curvature of the Earth. This is more accurate than treating coordinates as if they were points on a flat plane.

Distance between two trailheads

function haversine(p1, p2) {
    const R = 6371;

    const phi1 = p1.latitude * Math.PI / 180;
    const phi2 = p2.latitude * Math.PI / 180;

    const dphi = (p2.latitude - p1.latitude) * Math.PI / 180;
    const dlambda = (p2.longitude - p1.longitude) * Math.PI / 180;

    const a = Math.sin(dphi / 2) ** 2 +
              Math.cos(phi1) * Math.cos(phi2) *
              Math.sin(dlambda / 2) ** 2;

    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    return R * c;
}
Ant Route Choice

Each ant scores possible next trailheads using both pheromone and distance. A path with more pheromone becomes more attractive, while a shorter distance is also preferred.

const tau = pheromone[current][j] ** alpha;
            const eta = (1 / distanceMatrix[current][j]) ** beta;
            const score = tau * eta;
Performance Enhancement: Parallelized ant updates

Initially, the implementation followed the original algorithm closely: each ant built one complete solution after another. This worked, but it was slow and made the map visualization feel frozen because only one ant appeared to move at a time.

Since each ant’s route construction is independent before the pheromone update step, the application now uses an asynchronous Promise-based approach. All ants build each step together and return their solutions in an array. This makes the algorithm faster and makes the visualization much clearer because all ants move together.

Building ant routes together

const antSolutions = await Promise.all(
                        Array.from({ length: m }, (_, a) =>
                            buildAntRoutes(
                                a,
                                iter,
                                nodes,
                                pheromone,
                                distanceMatrix,
                                alpha,
                                beta,
                                seed
                            )
                        )
                    );
Parameters

What the controls mean

The algorithm is controlled by several parameters. Changing them affects the balance between exploration, convergence speed, and final route quality.

m: number of artificial ants

The number of ants building routes each iteration. In practice, m is often set equal to n for the Traveling Salesman Problem. Setting m lower than n is possible, but it usually decreases performance.

Range: 1-1000
n: number of selected trails

The number of trailheads included in the route. In the current implementation, the first n trails are selected from the data list.

Range: 1-359
NC: number of iterations

The number counter. This determines how many iterations the algorithm runs before stopping and returning the best solution found.

Range: 1-100
Q: pheromone strength

A constant that affects how much pheromone each ant leaves as a fraction of the total distance of that ant’s solution. Since Q is constant for all ants, changing it often has little visible impact on performance.

Range: 1-1000
ρ: evaporation rate

A pivotal parameter that controls how much pheromone remains after each iteration. If it is too high, ants may converge too quickly. If it is too low, routes may stay too random. A generally safe value found during testing was 0.5.

Range: 0-1
α: pheromone influence

Controls how strongly ants follow existing pheromone trails. A higher alpha makes previous successful paths more influential.

β: distance influence

Controls how strongly ants prefer closer trailheads. A higher beta makes the ants more distance-focused when choosing the next point.

Tools Used

Node.js

The application runs as a Node.js web application.

EJS

EJS templates are used to render the web pages.

JavaScript

The Ant Colony Optimization algorithm is written in JavaScript.

Leaflet

Leaflet is used to visualize the trailheads, ant movement, and best route on the map.

Future Improvements
  • Use complete trail geometry: Instead of using only trailheads, the application could account for full trail lengths, including trail starts and trail ends.

  • Allow custom trail selection: Users could manually choose the trails they want to include and generate a custom complete path.

  • Export the best solution: The best route found could be exported for later use, sharing, or navigation.

  • Visualize pheromone trails: The map could show pheromone intensity directly, making it easier to see how the colony learns over time.

  • Compare algorithm performance: The ACO implementation could be compared with other popular optimization algorithms to evaluate speed, consistency, and route quality.