#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# File: tower_location.py
# Author: Maciej Kaniewski
# Copyright (c) Maciej Kaniewski 2024
# MIT License

from scipy.optimize import minimize
import numpy as np
import matplotlib.pyplot as plt

# Define the towns' coordinates
towns = np.array(
    [
        [17, 14],  # Cincinnati
        [10, 10],  # Florence
        [12, 16],  # Covington
        [12, 22],  # Evendale
        [13, 17],  # Fairfax
        [19, 19],  # Milford
    ]
)

# Town names for labeling
town_names = ["Cincinnati", "Florence", "Covington", "Evendale", "Fairfax", "Milford"]

# Define the objective function: maximum Euclidean distance to the towns
def objective(x):
    distances = np.sqrt(np.sum((towns - x) ** 2, axis=1))
    return np.max(distances)

# Initial guess for the tower location (average of the given points)
x0 = np.mean(towns, axis=0)

# Perform the minimization (Sequential Least Squares Programming)
result = minimize(objective, x0, method="SLSQP")
tower_location = result.x

print(f"The optimal tower location is at {tower_location}")

# Function to draw a line between two points with a label for the distance
def draw_line_with_label(ax, point1, point2, label, color="gray"):
    ax.plot([point1[0], point2[0]], [point1[1], point2[1]], color=color, linestyle="--")
    midpoint = (point1 + point2) / 2
    ax.text(midpoint[0], midpoint[1], label, color=color, fontsize=9, ha="center")

# Create a new plot
fig, ax = plt.subplots()

# Plot the towns
ax.scatter(towns[:, 0], towns[:, 1], color="blue", label="Towns")
for i, txt in enumerate(town_names):
    ax.annotate(txt, (towns[i, 0], towns[i, 1]))

# Plot the optimal tower location
ax.scatter(
    tower_location[0], tower_location[1], color="red", label="Optimal Tower Location"
)
ax.text(tower_location[0], tower_location[1], " Tower", horizontalalignment="left")

# Draw lines from the optimal tower location to each town with distances
for town, name in zip(towns, town_names):
    distance = np.sqrt(np.sum((town - tower_location) ** 2))
    label = f"{distance:.2f} mi"
    draw_line_with_label(ax, tower_location, town, label)

# Additional plot settings
ax.set_title("Optimal Tower Location, Towns, and Distances")
ax.set_xlabel("West-East Distance (miles)")
ax.set_ylabel("South-North Distance (miles)")
ax.legend()
ax.grid(True)

plt.show()