lib/terraformer/coordinate.rb in terraformer-0.0.1 vs lib/terraformer/coordinate.rb in terraformer-0.0.2

- old
+ new

@@ -1,9 +1,13 @@ module Terraformer class Coordinate < ::Array + # http://en.wikipedia.org/wiki/Earth_radius#Mean_radius + # + EARTH_MEAN_RADIUS = 6371009.to_d + attr_accessor :crs class << self def from arys @@ -15,28 +19,30 @@ end def big_decimal n case n when String - BigDecimal.new n + BigDecimal(n) when BigDecimal n when Numeric - n.to_d + BigDecimal(n.to_s) + else + raise ArgumentError end end end - def initialize _x, _y = nil, _z = nil, _m = nil - super 4 - case _x - when Array + def initialize _x, _y = nil, _z = nil + super 3 + case + when Array === _x raise ArgumentError if _y self.x = _x[0] self.y = _x[1] - when Numeric + when Numeric === _x || String === _x raise ArgumentError unless _y self.x = _x self.y = _y else raise ArgumentError.new "invalid argument: #{_x}" @@ -61,43 +67,47 @@ def z self[2] end - def m - self[3] - end - - [:z=, :m=, :<<, :+ , :-, :*, :&, :|].each do |sym| + [:z=, :<<, :*, :&, :|].each do |sym| define_method(sym){|*a| raise NotImplementedError } end def to_geographic xerd = (x / EARTH_RADIUS).to_deg _x = xerd - (((xerd + 180.0) / 360.0).floor * 360.0) _y = ( (Math::PI / 2).to_d - (2 * BigMath.atan(BigMath.exp(-1.0 * y / EARTH_RADIUS, PRECISION), PRECISION)) ).to_deg - geog = self.class.new _x, _y + geog = self.class.new _x.round(PRECISION), _y.round(PRECISION) geog.crs = GEOGRAPHIC_CRS geog end def to_mercator _x = x.to_rad * EARTH_RADIUS syr = BigMath.sin y.to_rad, PRECISION _y = (EARTH_RADIUS / 2.0) * BigMath.log((1.0 + syr) / (1.0 - syr), PRECISION) - merc = self.class.new _x, _y + merc = self.class.new _x.round(PRECISION), _y.round(PRECISION) merc.crs = MERCATOR_CRS merc end + def to_s + [x,y,z].compact.join ',' + end + def to_json *args - [x, y, z, m].map! {|e| e.nil? ? nil : e.to_f}.compact.to_json(*args) + [x, y, z].map! {|e| e.to_f if e}.compact.to_json(*args) end + def to_point + Point.new self + end + def geographic? crs.nil? or crs == GEOGRAPHIC_CRS end def mercator? @@ -111,9 +121,88 @@ [center.x + radius.to_d * BigMath.cos(radians, PRECISION), center.y + radius.to_d * BigMath.sin(radians, PRECISION)] } coordinates << coordinates[0] Polygon.new(coordinates).to_geographic + end + + def <=> other + raise ArgumentError unless Coordinate === other + dx = x - other.x + dy = y - other.y + case + when dx > dy; 1 + when dx < dy; -1 + else; 0 + end + end + + def arithmetic operator, obj + case obj + when Array + _x = self.x.__send__ operator, obj[0] if obj[0] + _y = self.y.__send__ operator, obj[1] if obj[1] + Coordinate.new((_x || x), (_y || y)) + else + raise NotImplementedError + end + end + private :arithmetic + + def + obj + arithmetic :+, obj + end + + def - obj + arithmetic :-, obj + end + + def squared_euclidean_distance_to obj + raise ArgumentError unless Coordinate === obj + dx = obj.x - x + dy = obj.y - y + dx * dx + dy * dy + end + + def euclidean_distance_to obj + BigMath.sqrt squared_euclidean_distance_to(obj), PRECISION + end + + def haversine_distance_to other + raise ArgumentError unless Coordinate === other + + d_lat = (self.y - other.y).to_rad + d_lon = (self.x - other.x).to_rad + + lat_r = self.y.to_rad + other_lat_r = other.y.to_rad + + a = BigMath.sin(d_lat / 2, PRECISION)**2 + + BigMath.sin(d_lon / 2, PRECISION)**2 * + BigMath.cos(lat_r, PRECISION) * BigMath.cos(other_lat_r, PRECISION) + + y = BigMath.sqrt a, PRECISION + x = BigMath.sqrt (1 - a), PRECISION + c = 2 * BigMath.atan2(y, x, PRECISION) + + c * EARTH_MEAN_RADIUS + end + + def distance_and_bearing_to obj + raise ArgumentError unless Coordinate === obj + Geodesic.compute_distance_and_bearing y, x, obj.y, obj.x + end + + def distance_to obj + distance_and_bearing_to(obj)[:distance] + end + + def initial_bearing_to obj + distance_and_bearing_to(obj)[:bearing][:initial] + end + + def final_bearing_to obj + distance_and_bearing_to(obj)[:bearing][:final] end end end