diff --git a/README.md b/README.md index 471c69c..fe11fef 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,39 @@ puts "Nearest to London: #{nearest_point.first.name} (longitude #{nearest_point. # Nearest to London: London (longitude -0.127647, latitude 51.507322) ``` +### Distance + +For distance calculations, the squared Euclidean distance is used. However, you can easily monkey-patch the `Kd::Tree#distance` method to implement another algorithm, such as the Haversine formula, to calculate distances between two points given their latitudes and longitudes. + +```crystal +require "haversine" + +module Kd + class Tree(T) + private def distance(m : T, n : T) + # Calling `Haversine.distance` with 2 pairs of latitude/longitude coordinates. + # Returns a distance in meters. + Haversine.distance({m.latitude, m.longitude}, {n.latitude, n.longitude}).to_meters + end + end +end + +points = [ + GeoLocation.new("New York", -73.935242, 40.730610), + GeoLocation.new("Los Angeles", -118.243683, 34.052235), + GeoLocation.new("London", -0.127647, 51.507322), + GeoLocation.new("Tokyo", 139.691711, 35.689487), +] + +kd_tree = Kd::Tree(GeoLocation).new(points) + +# Find the nearest point to London +target = GeoLocation.new("Near London", -0.125740, 51.508530) +nearest_point = kd_tree.nearest(target, 1) +puts "Nearest to London: #{nearest_point.first.name} (longitude #{nearest_point.first.longitude}, latitude #{nearest_point.first.latitude})" +# Nearest to London: London (longitude -0.127647, latitude 51.507322) +``` + ## Performance Using a tree with 1 million points `[x, y] of Float64` on my i7-8550U CPU @ 1.80GHz: diff --git a/samples/haversine_distance.cr b/samples/haversine_distance.cr new file mode 100644 index 0000000..33ea779 --- /dev/null +++ b/samples/haversine_distance.cr @@ -0,0 +1,55 @@ +require "haversine" +require "../src/kd_tree" + +class GeoLocation + property name : String + property longitude : Float64 + property latitude : Float64 + + def initialize(@name : String, @longitude : Float64, @latitude : Float64) + end + + # Define an indexer to allow easy access by index for longitude and latitude + def [](index : Int32) : Float64 + case index + when 0 then @longitude + when 1 then @latitude + else raise "Index out of bounds" + end + end + + # Assuming all GeoLocation objects are 2-dimensional + def size + 2 + end +end + +module Kd + class Tree(T) + private def distance(m : T, n : T) + # Calling `Haversine.distance` with 2 pairs of latitude/longitude coordinates. + # Returns a distance in meters. + Haversine.distance({m.latitude, m.longitude}, {n.latitude, n.longitude}).to_meters + end + end +end + +# Example Usage: +# Create an array of GeoLocation points +points = [ + GeoLocation.new("New York", -73.935242, 40.730610), + GeoLocation.new("Los Angeles", -118.243683, 34.052235), + GeoLocation.new("London", -0.127647, 51.507322), + GeoLocation.new("Paris", 2.349014, 48.864716), + GeoLocation.new("Tokyo", 139.691711, 35.689487), +] + +# Initialize the KD-tree with these points +kd_tree = Kd::Tree(GeoLocation).new(points) + +# Find the nearest point to London +target = GeoLocation.new("Near London", -0.125740, 51.508530) +nearest_point = kd_tree.nearest(target, 3) +puts "First: #{nearest_point[0].name} (longitude #{nearest_point[0].longitude}, latitude #{nearest_point[0].latitude})" +puts "Second: #{nearest_point[1].name} (longitude #{nearest_point[1].longitude}, latitude #{nearest_point[1].latitude})" +puts "Third: #{nearest_point[2].name} (longitude #{nearest_point[2].longitude}, latitude #{nearest_point[2].latitude})" diff --git a/shard.yml b/shard.yml index b530050..256452f 100644 --- a/shard.yml +++ b/shard.yml @@ -15,6 +15,8 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba + haversine: + github: geocrystal/haversine crystal: ">= 1.0.0"