Class: Philiprehberger::GeoPoint::Point
- Inherits:
-
Object
- Object
- Philiprehberger::GeoPoint::Point
- Defined in:
- lib/philiprehberger/geo_point/point.rb
Overview
Represents a geographic coordinate with latitude and longitude. Provides Haversine/Vincenty distance, bearing, midpoint, destination, geohash, cross-track distance, polygon containment, and rhumb line calculations.
Constant Summary collapse
- EARTH_RADIUS_KM =
6371.0- UNIT_MULTIPLIERS =
{ km: 1.0, mi: 0.621371, m: 1000.0, nm: 0.539957 }.freeze
- WGS84_A =
WGS84 ellipsoid parameters
6_378_137.0- WGS84_F =
1.0 / 298.257223563
- WGS84_B =
WGS84_A * (1 - WGS84_F)
- GEOHASH_BASE32 =
'0123456789bcdefghjkmnpqrstuvwxyz'
Instance Attribute Summary collapse
-
#lat ⇒ Object
readonly
Returns the value of attribute lat.
-
#lon ⇒ Object
readonly
Returns the value of attribute lon.
Instance Method Summary collapse
- #==(other) ⇒ Object
- #bearing_to(other) ⇒ Object
- #cross_track_distance(path_start, path_end, unit: :km) ⇒ Object
- #destination(*args, distance: nil, bearing: nil, unit: :km) ⇒ Object
- #distance_to(other, unit: :km, method: :haversine) ⇒ Object
- #eql?(other) ⇒ Boolean
- #hash ⇒ Object
-
#initialize(lat, lon) ⇒ Point
constructor
A new instance of Point.
- #inspect ⇒ Object
- #midpoint(other) ⇒ Object
- #rhumb_bearing_to(other) ⇒ Object
- #rhumb_distance_to(other, unit: :km) ⇒ Object
- #to_a ⇒ Object
- #to_dms ⇒ Object
- #to_geohash(precision: 12) ⇒ Object
- #to_h ⇒ Object
- #to_s ⇒ Object
Constructor Details
#initialize(lat, lon) ⇒ Point
Returns a new instance of Point.
26 27 28 29 30 31 32 33 34 35 |
# File 'lib/philiprehberger/geo_point/point.rb', line 26 def initialize(lat, lon) lat = Float(lat) lon = Float(lon) raise ArgumentError, "Latitude must be between -90 and 90, got #{lat}" unless lat.between?(-90, 90) raise ArgumentError, "Longitude must be between -180 and 180, got #{lon}" unless lon.between?(-180, 180) @lat = lat @lon = lon end |
Instance Attribute Details
#lat ⇒ Object (readonly)
Returns the value of attribute lat.
24 25 26 |
# File 'lib/philiprehberger/geo_point/point.rb', line 24 def lat @lat end |
#lon ⇒ Object (readonly)
Returns the value of attribute lon.
24 25 26 |
# File 'lib/philiprehberger/geo_point/point.rb', line 24 def lon @lon end |
Instance Method Details
#==(other) ⇒ Object
212 213 214 |
# File 'lib/philiprehberger/geo_point/point.rb', line 212 def ==(other) other.is_a?(self.class) && @lat == other.lat && @lon == other.lon end |
#bearing_to(other) ⇒ Object
50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/philiprehberger/geo_point/point.rb', line 50 def bearing_to(other) lat1 = deg_to_rad(@lat) lat2 = deg_to_rad(other.lat) dlon = deg_to_rad(other.lon - @lon) y = Math.sin(dlon) * Math.cos(lat2) x = (Math.cos(lat1) * Math.sin(lat2)) - (Math.sin(lat1) * Math.cos(lat2) * Math.cos(dlon)) (rad_to_deg(Math.atan2(y, x)) + 360) % 360 end |
#cross_track_distance(path_start, path_end, unit: :km) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/philiprehberger/geo_point/point.rb', line 151 def cross_track_distance(path_start, path_end, unit: :km) validate_unit!(unit) d_start_to_self = path_start.distance_to(self, unit: :km) / EARTH_RADIUS_KM bearing_start_to_self = deg_to_rad(path_start.bearing_to(self)) bearing_start_to_end = deg_to_rad(path_start.bearing_to(path_end)) cross_track_rad = Math.asin( Math.sin(d_start_to_self) * Math.sin(bearing_start_to_self - bearing_start_to_end) ) km_to_unit(cross_track_rad * EARTH_RADIUS_KM, unit) end |
#destination(*args, distance: nil, bearing: nil, unit: :km) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/philiprehberger/geo_point/point.rb', line 80 def destination(*args, distance: nil, bearing: nil, unit: :km) # Keyword-only form: destination(distance: meters, bearing: degrees). # When both kwargs are provided (and no positional args), distance is in meters. if args.empty? && !distance.nil? && !bearing.nil? return destination_meters_kw(distance, bearing) end raise ArgumentError, 'wrong number of arguments (given 0, expected 2)' if args.length < 2 bearing_arg, distance_arg = args validate_unit!(unit) d = unit_to_km(distance_arg, unit) / EARTH_RADIUS_KM brng = deg_to_rad(bearing_arg) lat1 = deg_to_rad(@lat) lon1 = deg_to_rad(@lon) lat2 = Math.asin( (Math.sin(lat1) * Math.cos(d)) + (Math.cos(lat1) * Math.sin(d) * Math.cos(brng)) ) lon2 = lon1 + Math.atan2( Math.sin(brng) * Math.sin(d) * Math.cos(lat1), Math.cos(d) - (Math.sin(lat1) * Math.sin(lat2)) ) self.class.new(rad_to_deg(lat2), normalize_lon(rad_to_deg(lon2))) end |
#distance_to(other, unit: :km, method: :haversine) ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/philiprehberger/geo_point/point.rb', line 37 def distance_to(other, unit: :km, method: :haversine) validate_unit!(unit) case method when :haversine haversine_distance(other, unit) when :vincenty vincenty_distance(other, unit) else raise ArgumentError, "Unknown method :#{method}. Valid methods: haversine, vincenty" end end |
#eql?(other) ⇒ Boolean
216 217 218 |
# File 'lib/philiprehberger/geo_point/point.rb', line 216 def eql?(other) self == other end |
#hash ⇒ Object
220 221 222 |
# File 'lib/philiprehberger/geo_point/point.rb', line 220 def hash [@lat, @lon].hash end |
#inspect ⇒ Object
228 229 230 |
# File 'lib/philiprehberger/geo_point/point.rb', line 228 def inspect "#<#{self.class} lat=#{@lat} lon=#{@lon}>" end |
#midpoint(other) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/philiprehberger/geo_point/point.rb', line 62 def midpoint(other) lat1 = deg_to_rad(@lat) lat2 = deg_to_rad(other.lat) lon1 = deg_to_rad(@lon) dlon = deg_to_rad(other.lon - @lon) bx = Math.cos(lat2) * Math.cos(dlon) by = Math.cos(lat2) * Math.sin(dlon) mid_lat = Math.atan2( Math.sin(lat1) + Math.sin(lat2), Math.sqrt(((Math.cos(lat1) + bx)**2) + (by**2)) ) mid_lon = lon1 + Math.atan2(by, Math.cos(lat1) + bx) self.class.new(rad_to_deg(mid_lat), rad_to_deg(mid_lon)) end |
#rhumb_bearing_to(other) ⇒ Object
188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/philiprehberger/geo_point/point.rb', line 188 def rhumb_bearing_to(other) lat1 = deg_to_rad(@lat) lat2 = deg_to_rad(other.lat) dlon = deg_to_rad(other.lon - @lon) d_psi = Math.log(Math.tan((Math::PI / 4) + (lat2 / 2.0)) / Math.tan((Math::PI / 4) + (lat1 / 2.0))) dlon -= (2 * Math::PI) * (dlon <=> 0) if dlon.abs > Math::PI (rad_to_deg(Math.atan2(dlon, d_psi)) + 360) % 360 end |
#rhumb_distance_to(other, unit: :km) ⇒ Object
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/philiprehberger/geo_point/point.rb', line 165 def rhumb_distance_to(other, unit: :km) validate_unit!(unit) lat1 = deg_to_rad(@lat) lat2 = deg_to_rad(other.lat) dlat = lat2 - lat1 dlon = deg_to_rad(other.lon - @lon) d_psi = Math.log(Math.tan((Math::PI / 4) + (lat2 / 2.0)) / Math.tan((Math::PI / 4) + (lat1 / 2.0))) q = if d_psi.abs > 1e-12 dlat / d_psi else Math.cos(lat1) end dlon -= (2 * Math::PI) * (dlon <=> 0) if dlon.abs > Math::PI dist = Math.sqrt((dlat**2) + ((q**2) * (dlon**2))) * EARTH_RADIUS_KM km_to_unit(dist, unit) end |
#to_a ⇒ Object
204 205 206 |
# File 'lib/philiprehberger/geo_point/point.rb', line 204 def to_a [@lat, @lon] end |
#to_dms ⇒ Object
200 201 202 |
# File 'lib/philiprehberger/geo_point/point.rb', line 200 def to_dms "#{format_dms(@lat, 'N', 'S')} #{format_dms(@lon, 'E', 'W')}" end |
#to_geohash(precision: 12) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/philiprehberger/geo_point/point.rb', line 109 def to_geohash(precision: 12) raise ArgumentError, "Precision must be between 1 and 12, got #{precision}" unless precision.between?(1, 12) lat_range = [-90.0, 90.0] lon_range = [-180.0, 180.0] is_lon = true bit = 0 ch = 0 hash = +'' (precision * 5).times do if is_lon mid = (lon_range[0] + lon_range[1]) / 2.0 if @lon >= mid ch |= (1 << (4 - bit)) lon_range[0] = mid else lon_range[1] = mid end else mid = (lat_range[0] + lat_range[1]) / 2.0 if @lat >= mid ch |= (1 << (4 - bit)) lat_range[0] = mid else lat_range[1] = mid end end is_lon = !is_lon bit += 1 next unless bit == 5 hash << GEOHASH_BASE32[ch] bit = 0 ch = 0 end hash end |
#to_h ⇒ Object
208 209 210 |
# File 'lib/philiprehberger/geo_point/point.rb', line 208 def to_h { lat: @lat, lon: @lon } end |
#to_s ⇒ Object
224 225 226 |
# File 'lib/philiprehberger/geo_point/point.rb', line 224 def to_s "(#{@lat}, #{@lon})" end |