Class: AIXM::XY

Inherits:
Object show all
Includes:
Concerns::HashEquality
Defined in:
lib/aixm/xy.rb

Overview

Geographical coordinates

Warning!

Coordinate tuples can be noted in mathematical order (XY = longitude first, latitude second) or in (more common) geographical order (YX = latitude first, longitude second). However you sort the attributes, make sure not to flip them by accident.

See en.wikipedia.org/wiki/Geographic_coordinate_system

Recognized notations:

  • DD - examples: 12.12345678 (north or east), -12.12345678 (south or west)

  • DMS - examples: 11°22’33.44“N, 1112233.44W,

Constants:

  • AIXM::MIN - characters recognized as DMS minute symbols

  • AIXM::SEC - characters recognized as DMS second symbols

  • AIXM::DMS_RE - regular expression to match DMS coordinate notations

Examples:

All of the below are equivalent

AIXM.xy(lat: 11.375955555555556, long: -111.37595555555555)
AIXM.xy(lat: %q(11°22'33.44"), long: %q(-111°22'33.44"))
AIXM.xy(lat: %q(11°22'33.44N"), long: %q(111°22'33.44W"))
AIXM.xy(lat: '112233.44N', long: '1112233.44W')

See Also:

Constant Summary collapse

EARTH_RADIUS =

meters

6_371_008.8

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Concerns::HashEquality

#eql?, #hash

Constructor Details

#initialize(lat:, long:) ⇒ XY

See the overview for examples.



37
38
39
# File 'lib/aixm/xy.rb', line 37

def initialize(lat:, long:)
  self.lat, self.long = lat, long
end

Instance Attribute Details

#latString, Float #lat=(value) ⇒ Object

Latitude

Overloads:

  • #latString, Float

    Parameters:

    • schema (Symbol, nil)

      either :aixm or :ofmx or nil

    Returns:

    • (String, Float)
  • #lat=(value) ⇒ Object

    Parameters:

    • value (String, Numeric)


59
60
61
62
63
64
65
# File 'lib/aixm/xy.rb', line 59

def lat(schema=nil)
  case schema
    when :ofmx then ("%011.8f" % @lat.abs.round(8)) + (@lat.negative? ? 'S' : 'N')
    when :aixm then @lat.to_dms(2).gsub(/[^\d.]/, '') + (@lat.negative? ? 'S' : 'N')
    else @lat.round(8)
  end
end

#longString, Float #long=(value) ⇒ Object

Longitude

Overloads:

  • #longString, Float

    Parameters:

    • schema (Symbol, nil)

      either :aixm or :ofmx or nil

    Returns:

    • (String, Float)
  • #long=(value) ⇒ Object

    Parameters:

    • value (String, Numeric)


80
81
82
83
84
85
86
# File 'lib/aixm/xy.rb', line 80

def long(schema=nil)
  case schema
    when :ofmx then ("%012.8f" % @long.abs.round(8)) + (@long.negative? ? 'W' : 'E')
    when :aixm then @long.to_dms(3).gsub(/[^\d.]/, '') + (@long.negative? ? 'W' : 'E')
    else @long.round(8)
  end
end

Instance Method Details

#==(other) ⇒ Object

See Also:

  • Object#==


163
164
165
# File 'lib/aixm/xy.rb', line 163

def ==(other)
  self.class === other && lat == other.lat && long == other.long
end

#add_distance(distance, bearing) ⇒ AIXM::XY

Calculate a new point by adding the distance in the given bearing

Parameters:

Returns:



149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/aixm/xy.rb', line 149

def add_distance(distance, bearing)
  angular_dist = distance.to_m.dim / EARTH_RADIUS
  dest_lat = Math.asin(
    Math.sin(lat.to_rad) * Math.cos(angular_dist) +
    Math.cos(lat.to_rad) * Math.sin(angular_dist) * Math.cos(bearing.to_f.to_rad)
  )
  dest_long = long.to_rad + Math.atan2(
    Math.sin(bearing.to_f.to_rad) * Math.sin(angular_dist) * Math.cos(lat.to_rad),
    Math.cos(angular_dist) - Math.sin(lat.to_rad) * Math.sin(dest_lat)
  )
  AIXM.xy(lat: dest_lat.to_deg, long: dest_long.to_deg)
end

#bearing(other) ⇒ AIXM::A

Bearing to another point

Parameters:

Returns:



131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/aixm/xy.rb', line 131

def bearing(other)
  fail "cannot calculate bearing to identical point" if self == other
  delta_long = other.long.to_rad - long.to_rad
  AIXM.a(
    Math.atan2(
      Math.cos(other.lat.to_rad) * Math.sin(delta_long),
      Math.cos(lat.to_rad) * Math.sin(other.lat.to_rad) -
        Math.sin(lat.to_rad) * Math.cos(other.lat.to_rad) *
        Math.cos(delta_long)
    ).to_deg
  )
end

#distance(other) ⇒ AIXM::D

Distance to another point as calculated by the Haversine formula

Parameters:

Returns:



112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/aixm/xy.rb', line 112

def distance(other)
  if self == other
    AIXM.d(0, :m)
  else
    value = 2 * EARTH_RADIUS * Math.asin(
      Math.sqrt(
        Math.sin((other.lat.to_rad - lat.to_rad) / 2) ** 2 +
          Math.cos(lat.to_rad) * Math.cos(other.lat.to_rad) *
          Math.sin((other.long.to_rad - long.to_rad) / 2) ** 2
      )
    )
    AIXM.d(value.round, :m)
  end
end

#inspectString

Returns:

  • (String)


42
43
44
# File 'lib/aixm/xy.rb', line 42

def inspect
  %Q(#<#{self.class} #{to_s}>)
end

#seconds?Boolean

Whether both longitude and latitude have zero DMS seconds (which may indicate rounded or estimated coordinates).

Returns:

  • (Boolean)


97
98
99
# File 'lib/aixm/xy.rb', line 97

def seconds?
  !(long.to_dms[-6,5].to_f.zero? && lat.to_dms[-6,5].to_f.zero?)
end

#to_pointAIXM::Component::Geometry::Point

Convert to point



104
105
106
# File 'lib/aixm/xy.rb', line 104

def to_point
  AIXM.point(xy: self)
end

#to_sString

Returns human readable representation.

Returns:

  • (String)

    human readable representation



47
48
49
# File 'lib/aixm/xy.rb', line 47

def to_s
  [lat(:ofmx), long(:ofmx)].join(' '.freeze)
end