Skip to content

Instantly share code, notes, and snippets.

@tshego3
Last active July 7, 2025 10:47
Show Gist options
  • Save tshego3/43b55106a1b47297da41ddf02f53569b to your computer and use it in GitHub Desktop.
Save tshego3/43b55106a1b47297da41ddf02f53569b to your computer and use it in GitHub Desktop.
Haversine formula-based Coordinate Distance Calculator
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;

public class Program
{
    public class Coordinate
    {
        public double Latitude { get; set; }
        public double Longitude { get; set; }
    }
	
    // Helper class to store coordinate and its calculated distance
    private class CoordinateDistance
    {
        public Coordinate Coordinate { get; set; }
        public double Distance { get; set; }
    }

    private static readonly List<Coordinate> _predefinedCoordinates = new List<Coordinate>
    {
        new Coordinate { Latitude = -26.2041, Longitude = 28.0473 }, // Johannesburg
        new Coordinate { Latitude = -33.8688, Longitude = 151.2093 },// Sydney
        new Coordinate { Latitude = 40.7128, Longitude = -74.0060 }, // New York
        new Coordinate { Latitude = -25.771394283754905, Longitude = 28.235580364439038 }, // Brooklyn Mall
        new Coordinate { Latitude = 35.6895, Longitude = 139.6917 }, // Tokyo
        new Coordinate { Latitude = 48.8566, Longitude = 2.3522 },    // Paris
        new Coordinate { Latitude = 55.7558, Longitude = 37.6173 },    // Moscow
        new Coordinate { Latitude = -25.78263134020646, Longitude = 28.275175392408805 }   // Menlyn Park Shopping Centre
    };

    /// <summary>
    /// Calculates the distance between two sets of coordinates using the Haversine formula.
    /// </summary>
    /// <param name="lat1">Latitude of the first point.</param>
    /// <param name="lon1">Longitude of the first point.</param>
    /// <param name="lat2">Latitude of the second point.</param>
    /// <param name="lon2">Longitude of the second point.</param>
    /// <returns>Distance in kilometers, or -1 if an error occurs.</returns>
    private static double CalculateHaversineDistance(double lat1, double lon1, double lat2, double lon2)
    {
        try
        {
            const double R = 6371; // Earth's radius in kilometers

            double dLat = ToRadians(lat2 - lat1);
            double dLon = ToRadians(lon2 - lon1);

            double a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
                       Math.Cos(ToRadians(lat1)) * Math.Cos(ToRadians(lat2)) *
                       Math.Sin(dLon / 2) * Math.Sin(dLon / 2);

            double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));

            return R * c; // Distance in kilometers
        }
        catch (ArithmeticException ex)
        {
            Console.Error.WriteLine($"Mathematical error during Haversine calculation: {ex.Message}");
            return -1; // Indicate an error
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"An unexpected error occurred during Haversine calculation: {ex.Message}");
            return -1; // Indicate an error
        }
    }

    /// <summary>
    /// Converts an angle from degrees to radians.
    /// </summary>
    /// <param name="angle">The angle in degrees.</param>
    /// <returns>The angle in radians.</returns>
    private static double ToRadians(double angle)
    {
        return Math.PI * angle / 180.0;
    }

    public static void Main()
    {
        // Define the request coordinate
        Coordinate requestCoordinate = new Coordinate() { Latitude = -25.754511453940673, Longitude = 28.181179918201046 }; // Tshwane Mail Centre

        // Basic validation for the request coordinate
        if (requestCoordinate.Latitude < -90 || requestCoordinate.Latitude > 90 ||
            requestCoordinate.Longitude < -180 || requestCoordinate.Longitude > 180)
        {
            Console.Error.WriteLine("Error: Invalid request coordinate provided. Latitude must be between -90 and 90, and Longitude between -180 and 180.");
            return;
        }

        double targetLatitude = requestCoordinate.Latitude;
        double targetLongitude = requestCoordinate.Longitude;

        // Use a list to store the results of the LINQ query
        List<CoordinateDistance> nearestCoordinatesWithDistance = new List<CoordinateDistance>();

        try
        {
            // Select, order, and take the top 3 nearest coordinates
            nearestCoordinatesWithDistance = _predefinedCoordinates
                .Select(coord => new CoordinateDistance
                {
                    Coordinate = coord,
                    Distance = CalculateHaversineDistance(targetLatitude, targetLongitude, coord.Latitude, coord.Longitude)
                })
                .Where(item => item.Distance != -1) // Filter out items where distance calculation failed
                .OrderBy(item => item.Distance)
                .Take(3)
                .ToList();

            // Extract just the Coordinate objects for serialization
            List<Coordinate> nearestCoordinates = nearestCoordinatesWithDistance
                .Select(item => item.Coordinate)
                .ToList();

            // Serialize and print the result
            JsonSerializerOptions options = new JsonSerializerOptions { WriteIndented = true };
            string jsonOutput = JsonSerializer.Serialize(nearestCoordinates, options);
            Console.WriteLine(jsonOutput);
        }
        catch (InvalidOperationException ex)
        {
            Console.Error.WriteLine($"Error during LINQ operations: {ex.Message}");
        }
        catch (JsonException ex)
        {
            Console.Error.WriteLine($"Error serializing coordinates to JSON: {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"An unexpected error occurred in Main: {ex.Message}");
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment