philiprehberger-geo_point

Tests Gem Version Last updated

Geographic coordinate operations with Haversine/Vincenty distance, geohash, rhumb lines, and bounding box

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-geo_point"

Or install directly:

gem install philiprehberger-geo_point

Usage

require "philiprehberger/geo_point"

nyc = Philiprehberger::GeoPoint.point(40.7128, -74.0060)
london = Philiprehberger::GeoPoint.point(51.5074, -0.1278)

nyc.distance_to(london)  # => ~5570.0 (km)

Distance Units

nyc.distance_to(london, unit: :km)  # => ~5570.0
nyc.distance_to(london, unit: :mi)  # => ~3461.0
nyc.distance_to(london, unit: :m)   # => ~5570000.0
nyc.distance_to(london, unit: :nm)  # => ~3007.0

Vincenty Distance

nyc.distance_to(london, method: :vincenty)  # => ~5585.0 (ellipsoid-accurate)
nyc.distance_to(london, method: :haversine) # => ~5570.0 (spherical, default)

Bearing

nyc.bearing_to(london)  # => ~51.2 (degrees, 0-360)

Midpoint

mid = nyc.midpoint(london)
mid.lat  # => ~52.5
mid.lon  # => ~-36.0

Destination Point

dest = nyc.destination(51.2, 5570)  # bearing, distance in km
dest.lat  # => ~51.5
dest.lon  # => ~-0.1

Destination Point (keyword form, meters)

# Distance in meters, bearing in degrees (0 = north, clockwise)
dest = nyc.destination(distance: 5_570_000, bearing: 51.2)
dest.lat  # => ~51.5
dest.lon  # => ~-0.1

Geohash

nyc.to_geohash                    # => "dr5ru6j2c62g" (precision 12)
nyc.to_geohash(precision: 6)     # => "dr5ru6"

point = Philiprehberger::GeoPoint.from_geohash("dr5ru6j2c62g")
point.lat  # => ~40.7128
point.lon  # => ~-74.0060

Cross-Track Distance

point = Philiprehberger::GeoPoint.point(1, 5)
path_start = Philiprehberger::GeoPoint.point(0, 0)
path_end = Philiprehberger::GeoPoint.point(0, 10)

point.cross_track_distance(path_start, path_end)  # => ~111.2 (km, positive = right of path)

Rhumb Lines

nyc.rhumb_distance_to(london)  # => ~5800.0 (km, constant-bearing route)
nyc.rhumb_bearing_to(london)   # => ~79.3 (degrees, constant bearing)

Polygon Containment

triangle = [
  Philiprehberger::GeoPoint.point(0, 0),
  Philiprehberger::GeoPoint.point(0, 10),
  Philiprehberger::GeoPoint.point(10, 5)
]

inside = Philiprehberger::GeoPoint.point(3, 5)
outside = Philiprehberger::GeoPoint.point(20, 20)

Philiprehberger::GeoPoint.inside_polygon?(inside, triangle)   # => true
Philiprehberger::GeoPoint.inside_polygon?(outside, triangle)  # => false

Nearest Point

origin = Philiprehberger::GeoPoint.point(40.7128, -74.0060)
nearest = Philiprehberger::GeoPoint.nearest(origin, points)

Points Within Radius

nearby = Philiprehberger::GeoPoint.within_radius(origin, points, 50)

Clustering

clusters = Philiprehberger::GeoPoint.cluster(points, radius_km: 10)
clusters.each { |group| puts "Cluster of #{group.length} points" }

Bounding Box

box = Philiprehberger::GeoPoint::BoundingBox.around(nyc, 50)
box.contains?(nyc)     # => true
box.contains?(london)  # => false

Bounding Box Area

box = Philiprehberger::GeoPoint::BoundingBox.around(point, 50)
box.area_km2  # => 10000.0 (approximate km²)

DMS Formatting

nyc.to_dms  # => "40°42'46\"N 74°0'22\"W"
nyc.to_a    # => [40.7128, -74.0060]
nyc.to_h    # => {lat: 40.7128, lon: -74.0060}

API

GeoPoint

Method Description
.point(lat, lon) Create a new Point instance
.from_geohash(hash) Decode a geohash string to a Point at the center of the cell
.inside_polygon?(point, vertices) Check if a point is inside a polygon using ray-casting
.nearest(origin, points) Find closest point from array
.within_radius(origin, points, radius_km) Filter points by distance
.cluster(points, radius_km:) Group nearby points into clusters

GeoPoint::Point

Method Description
.new(lat, lon) Create point with coordinate validation (-90..90, -180..180)
#distance_to(other, unit: :km, method: :haversine) Distance via Haversine or Vincenty (:km, :mi, :m, :nm)
#bearing_to(other) Initial bearing in degrees (0-360)
#midpoint(other) Geographic midpoint between two points
#destination(bearing, distance, unit: :km) Point at given bearing and distance
#destination(distance:, bearing:) Forward geodesic: destination from distance (meters) and bearing (degrees)
#to_geohash(precision: 12) Encode point as a geohash string (precision 1-12)
#cross_track_distance(path_start, path_end, unit: :km) Perpendicular distance to great circle path
#rhumb_distance_to(other, unit: :km) Rhumb line (constant-bearing) distance
#rhumb_bearing_to(other) Rhumb line bearing in degrees (0-360)
#to_dms Format as degrees, minutes, seconds string
#to_a Return [lat, lon] array
#to_h Return lon: hash

GeoPoint::BoundingBox

Method Description
.around(point, radius, unit: :km) Create bounding box around a point
#contains?(point) Check if point is within the box
#to_a Return [min_lat, max_lat, min_lon, max_lon] array
#area_km2 Approximate surface area in km²
#to_h Return hash with all bounds

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT