## ============================================================
##  Coca-Cola Denmark Sales Route Optimiser
##  Method : TSP via Minimum Spanning Tree (MST) + nearest-
##           neighbour 2-opt crossing removal
##  CRS    : UTM Zone 32N  (EPSG:32632)
##  Output : RStudio Plots panel  +  A4 Portrait PDF
## ============================================================

## ---- 0. Install / load packages ----------------------------
pkgs <- c("sf", "ggplot2", "rnaturalearth", "rnaturalearthdata",
          "igraph", "dplyr", "ggspatial")

for (p in pkgs) {
  if (!requireNamespace(p, quietly = TRUE)) install.packages(p)
}
library(sf)
## Warning: package 'sf' was built under R version 4.3.3
## Linking to GEOS 3.11.2, GDAL 3.8.2, PROJ 9.3.1; sf_use_s2() is TRUE
library(ggplot2)
library(rnaturalearth)
library(rnaturalearthdata)
## Warning: package 'rnaturalearthdata' was built under R version 4.3.3
## 
## Attaching package: 'rnaturalearthdata'
## The following object is masked from 'package:rnaturalearth':
## 
##     countries110
library(igraph)
## Warning: package 'igraph' was built under R version 4.3.3
## 
## Attaching package: 'igraph'
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## The following object is masked from 'package:base':
## 
##     union
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.3.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:igraph':
## 
##     as_data_frame, groups, union
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggspatial)

set.seed(42)

## ---- 1. Denmark boundary -----------------------------------
denmark_wgs <- ne_countries(country     = "Denmark",
                            scale       = "medium",
                            returnclass = "sf") |> st_geometry()
utm32       <- st_crs(32632)
denmark_utm <- st_transform(denmark_wgs, utm32)

## ---- 2. Copenhagen depot -----------------------------------
cph_wgs <- st_sfc(st_point(c(12.5683, 55.6761)), crs = 4326)
cph_utm <- st_transform(cph_wgs, utm32) |> st_coordinates()

depot <- data.frame(name  = "Copenhagen",
                    X     = cph_utm[1, "X"],
                    Y     = cph_utm[1, "Y"],
                    depot = TRUE)

## ---- 3. 100 towns inside Denmark (rejection sampling) ------
bbox  <- st_bbox(denmark_utm)
towns <- data.frame()

while (nrow(towns) < 100) {
  xs  <- runif(3000, bbox["xmin"], bbox["xmax"])
  ys  <- runif(3000, bbox["ymin"], bbox["ymax"])
  pts <- st_as_sf(data.frame(X = xs, Y = ys),
                  coords = c("X", "Y"), crs = utm32)
  ok  <- st_within(pts, st_union(denmark_utm), sparse = FALSE)[, 1]
  cr  <- st_coordinates(pts[ok, ])
  towns <- rbind(towns, data.frame(X = cr[, "X"], Y = cr[, "Y"]))
}
towns        <- towns[1:100, ]
towns$name   <- paste0("T", sprintf("%03d", 1:100))
towns$depot  <- FALSE

## ---- 4. All nodes ------------------------------------------
all_nodes           <- bind_rows(depot, towns)
rownames(all_nodes) <- NULL
n_nodes             <- nrow(all_nodes)   # 101

## ---- 5. Distance matrix ------------------------------------
coords_mat <- as.matrix(all_nodes[, c("X", "Y")])
dist_mat   <- as.matrix(dist(coords_mat))

## ---- 6. MST -> DFS tour ------------------------------------
g   <- graph_from_adjacency_matrix(dist_mat, mode = "undirected",
                                   weighted = TRUE, diag = FALSE)
mst_g        <- mst(g)
dfs_res      <- dfs(mst_g, root = 1, neimode = "all", unreachable = FALSE)
## Warning: The `neimode` argument of `dfs()` is deprecated as of igraph 1.3.0.
## ℹ Please use the `mode` argument instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
tour         <- c(as.integer(dfs_res$order), 1L)   # close loop

## ---- 7. 2-opt crossing removal to prevent line crossings ---
##  Repeatedly test all (i,j) pairs; reverse sub-tour if it
##  shortens the route.  Stops when no improvement found.

two_opt <- function(tour, dm) {
  n     <- length(tour) - 1   # last = first (depot), so n unique stops
  improved <- TRUE
  while (improved) {
    improved <- FALSE
    for (i in 1:(n - 2)) {
      for (j in (i + 2):n) {
        # skip the edge that wraps around
        if (i == 1 && j == n) next
        d_old <- dm[tour[i],   tour[i+1]] +
          dm[tour[j],   tour[j+1]]
        d_new <- dm[tour[i],   tour[j]]   +
          dm[tour[i+1], tour[j+1]]
        if (d_new < d_old - 1e-6) {
          tour[(i+1):j] <- rev(tour[(i+1):j])
          improved <- TRUE
        }
      }
    }
  }
  tour
}

tour <- two_opt(tour, dist_mat)

## ---- 8. Assign sequential stop numbers ---------------------
##  Depot = 0, first town visited = 1, ..., last = 100
unique_tour         <- tour[1:n_nodes]
stop_num            <- integer(n_nodes)
for (i in seq_along(unique_tour)) stop_num[unique_tour[i]] <- i - 1L

all_nodes$stop_num  <- stop_num
all_nodes$map_label <- ifelse(all_nodes$depot,
                              "Copenhagen",
                              as.character(stop_num))

## ---- 9. Tour stats -----------------------------------------
tour_coords    <- all_nodes[tour, ]
tour_length_km <- round(
  sum(dist_mat[cbind(head(tour, -1), tail(tour, -1))]) / 1000, 1)
cat(sprintf("Tour length (after 2-opt): %.1f km\n", tour_length_km))
## Tour length (after 2-opt): 1978.1 km
## ---- 10. sf objects ----------------------------------------
route_line <- st_sfc(st_linestring(as.matrix(tour_coords[, c("X","Y")])),
                     crs = utm32)
all_sf     <- st_as_sf(all_nodes, coords = c("X","Y"), crs = utm32)
towns_df   <- all_nodes[!all_nodes$depot, ]
depot_df   <- all_nodes[ all_nodes$depot, ]

## ---- 11. Build map -----------------------------------------
p <- ggplot() +
  
  ## Country – white fill, black border, no colour tint
  geom_sf(data = denmark_utm,
          fill = "white", colour = "black", linewidth = 0.5) +
  
  ## Route lines – red
  geom_sf(data = route_line,
          colour = "#CC0000", linewidth = 0.45, alpha = 0.9) +
  
  ## Town circles – blue filled
  geom_point(data   = towns_df,
             aes(x = X, y = Y),
             shape  = 21, fill = "#2171b5",
             colour = "black", size = 2.0, stroke = 0.4) +
  
  ## Copenhagen circle – green filled
  geom_point(data   = depot_df,
             aes(x = X, y = Y),
             shape  = 21, fill = "#238b45",
             colour = "black", size = 4.0, stroke = 0.8) +
  
  ## Town number labels (numbers only, no "Stop")
  geom_text(data         = towns_df,
            aes(x = X, y = Y, label = map_label),
            size          = 1.7,
            colour        = "black",
            hjust         = 0,
            nudge_x       = 3200,
            check_overlap = TRUE) +
  
  ## Copenhagen plain text label (no box)
  geom_text(data     = depot_df,
            aes(x = X, y = Y, label = map_label),
            size      = 3.0,
            colour    = "black",
            fontface  = "bold",
            nudge_y   = 16000,
            hjust     = 0.5) +
  
  ## Scale bar & north arrow
  annotation_scale(location = "bl", width_hint = 0.22,
                   unit_category = "metric") +
  annotation_north_arrow(location = "tr",
                         style    = north_arrow_fancy_orienteering()) +
  
  ## Title: black, bold, UPPER CASE, underlined via element_text
  labs(
    title = "COCACOLA OPTIMAL DISTRIBUTION ROUTE IN DENMARK",
    subtitle = sprintf(
      "100 towns  |  Distribution Centre: Copenhagen  |  Total route: %.1f km  |  UTM Zone 32N (EPSG:32632)",
      tour_length_km),
    x = "Easting",
    y = "Northing"
  ) +
  
  theme_minimal(base_size = 10) +
  theme(
    ## Title: black, bold, underlined
    plot.title        = element_text(size = 11, face = "bold",
                                     colour = "black",
                                     hjust = 0.5,
                                     margin = margin(b = 4),
                                     decoration = "underline"),
    plot.subtitle     = element_text(size = 7, colour = "black",
                                     hjust = 0.5),
    ## No caption
    plot.caption      = element_blank(),
    ## White/plain backgrounds – no colour tint
    panel.background  = element_rect(fill = "white", colour = NA),
    plot.background   = element_rect(fill = "white", colour = NA),
    panel.grid.major  = element_line(colour = "#cccccc", linewidth = 0.25),
    axis.text         = element_text(size = 7),
    axis.title        = element_text(size = 8)
  )
## Warning in element_text(size = 11, face = "bold", colour = "black", hjust = 0.5, : `...` must be empty.
## ✖ Problematic argument:
## • decoration = "underline"
## ---- 12. Show in RStudio Plots panel -----------------------
print(p)

## ---- 13. Save as A4 Landscape PDF -------------------------
##  A4 landscape in inches: 11.69 wide x 8.27 tall
pdf_dir  <- "C:/Users/User/Desktop/CLASS NOTES/4.2 Notes/GIS Applications/Assignments/Assignment 5"

if (!dir.exists(pdf_dir)) {
  dir.create(pdf_dir, recursive = TRUE)
  message("Created directory: ", pdf_dir)
}

pdf_file <- file.path(pdf_dir, "denmark_tsp_route.pdf")

pdf(pdf_file, width = 11.69, height = 8.27, paper = "a4r")
print(p)
dev.off()
## png 
##   2
cat(sprintf("\nPDF (A4 Landscape) saved to:\n  %s\n", pdf_file))
## 
## PDF (A4 Landscape) saved to:
##   C:/Users/User/Desktop/CLASS NOTES/4.2 Notes/GIS Applications/Assignments/Assignment 5/denmark_tsp_route.pdf
## ---- 14. Tour order summary --------------------------------
cat("\n--- Tour order ---\n")
## 
## --- Tour order ---
for (i in seq_along(unique_tour)) {
  idx  <- unique_tour[i]
  lbl  <- if (all_nodes$depot[idx]) "Copenhagen (Depot)" else
    paste0("Stop ", stop_num[idx])
  cat(sprintf("  %3d : %s\n", i - 1L, lbl))
}
##     0 : Copenhagen (Depot)
##     1 : Stop 1
##     2 : Stop 2
##     3 : Stop 3
##     4 : Stop 4
##     5 : Stop 5
##     6 : Stop 6
##     7 : Stop 7
##     8 : Stop 8
##     9 : Stop 9
##    10 : Stop 10
##    11 : Stop 11
##    12 : Stop 12
##    13 : Stop 13
##    14 : Stop 14
##    15 : Stop 15
##    16 : Stop 16
##    17 : Stop 17
##    18 : Stop 18
##    19 : Stop 19
##    20 : Stop 20
##    21 : Stop 21
##    22 : Stop 22
##    23 : Stop 23
##    24 : Stop 24
##    25 : Stop 25
##    26 : Stop 26
##    27 : Stop 27
##    28 : Stop 28
##    29 : Stop 29
##    30 : Stop 30
##    31 : Stop 31
##    32 : Stop 32
##    33 : Stop 33
##    34 : Stop 34
##    35 : Stop 35
##    36 : Stop 36
##    37 : Stop 37
##    38 : Stop 38
##    39 : Stop 39
##    40 : Stop 40
##    41 : Stop 41
##    42 : Stop 42
##    43 : Stop 43
##    44 : Stop 44
##    45 : Stop 45
##    46 : Stop 46
##    47 : Stop 47
##    48 : Stop 48
##    49 : Stop 49
##    50 : Stop 50
##    51 : Stop 51
##    52 : Stop 52
##    53 : Stop 53
##    54 : Stop 54
##    55 : Stop 55
##    56 : Stop 56
##    57 : Stop 57
##    58 : Stop 58
##    59 : Stop 59
##    60 : Stop 60
##    61 : Stop 61
##    62 : Stop 62
##    63 : Stop 63
##    64 : Stop 64
##    65 : Stop 65
##    66 : Stop 66
##    67 : Stop 67
##    68 : Stop 68
##    69 : Stop 69
##    70 : Stop 70
##    71 : Stop 71
##    72 : Stop 72
##    73 : Stop 73
##    74 : Stop 74
##    75 : Stop 75
##    76 : Stop 76
##    77 : Stop 77
##    78 : Stop 78
##    79 : Stop 79
##    80 : Stop 80
##    81 : Stop 81
##    82 : Stop 82
##    83 : Stop 83
##    84 : Stop 84
##    85 : Stop 85
##    86 : Stop 86
##    87 : Stop 87
##    88 : Stop 88
##    89 : Stop 89
##    90 : Stop 90
##    91 : Stop 91
##    92 : Stop 92
##    93 : Stop 93
##    94 : Stop 94
##    95 : Stop 95
##    96 : Stop 96
##    97 : Stop 97
##    98 : Stop 98
##    99 : Stop 99
##   100 : Stop 100
cat("  --> Return to Copenhagen\n")
##   --> Return to Copenhagen