class GeoRuby::SimpleFeatures::Point
Represents a point. It is in 3D if the Z coordinate is not nil
.
Constants
- DEG2RAD
Attributes
Public Class Methods
Creates a point from an array of coordinates
# File lib/geo_ruby/simple_features/point.rb, line 386 def self.from_coordinates(coords, srid = DEFAULT_SRID, z = false, m = false) if !(z || m) from_x_y(coords[0], coords[1], srid) elsif z && m from_x_y_z_m(coords[0], coords[1], coords[2], coords[3], srid) elsif z from_x_y_z(coords[0], coords[1], coords[2], srid) else from_x_y_m(coords[0], coords[1], coords[2], srid) end end
Creates a point from a geo object that contains latitude and longitude
# File lib/geo_ruby/simple_features/point.rb, line 371 def self.from_geo(geo_obj, srid = DEFAULT_SRID) lat_names = %w(latitude lat).map(&:to_s) long_names = %w(longitude long lng).map(&:to_s) lat_method = lat_names.select {|mth| geo_obj.respond_to?(mth)}.first long_method = long_names.select {|mth| geo_obj.respond_to?(mth)}.first if lat_method && long_method return from_coordinates([geo_obj.send(long_method), geo_obj.send(lat_method)], srid) else raise ArgumentError, 'object must have both latitude and longitude methods' end end
Creates a point using coordinates like 22`34 23.45N
# File lib/geo_ruby/simple_features/point.rb, line 435 def self.from_latlong(lat, lon, srid = DEFAULT_SRID) p = [lat, lon].map do |l| sig, deg, min, sec, cen = \ l.scan(/(-)?(\d{1,2})\D*(\d{2})\D*(\d{2})(\D*(\d{1,3}))?/).flatten sig = true if l =~ /W|S/ dec = deg.to_i + (min.to_i * 60 + "#{sec}#{cen}".to_f) / 3600 sig ? dec * -1 : dec end point = new(srid) point.set_x_y(p[0], p[1]) end
Creates a point using polar coordinates r and theta(degrees)
# File lib/geo_ruby/simple_features/point.rb, line 426 def self.from_r_t(r, t, srid = DEFAULT_SRID) t *= DEG2RAD x = r * Math.cos(t) y = r * Math.sin(t) point = new(srid) point.set_x_y(x, y) end
Creates a point from the X and Y coordinates
# File lib/geo_ruby/simple_features/point.rb, line 399 def self.from_x_y(x, y, srid = DEFAULT_SRID) point = new(srid) point.set_x_y(x, y) end
Creates a point from the X, Y and M coordinates
# File lib/geo_ruby/simple_features/point.rb, line 411 def self.from_x_y_m(x, y, m, srid = DEFAULT_SRID) point = new(srid, false, true) point.m = m point.set_x_y(x, y) end
Creates a point from the X, Y and Z coordinates
# File lib/geo_ruby/simple_features/point.rb, line 405 def self.from_x_y_z(x, y, z, srid = DEFAULT_SRID) point = new(srid, true) point.set_x_y_z(x, y, z) end
Creates a point from the X, Y, Z and M coordinates
# File lib/geo_ruby/simple_features/point.rb, line 418 def self.from_x_y_z_m(x, y, z, m, srid = DEFAULT_SRID) point = new(srid, true, true) point.m = m point.set_x_y_z(x, y, z) end
GeoRuby::SimpleFeatures::Geometry::new
# File lib/geo_ruby/simple_features/point.rb, line 17 def initialize(srid = DEFAULT_SRID, with_z = false, with_m = false) super(srid, with_z, with_m) @x = @y = 0.0 @z = 0.0 # default value : meaningful if with_z @m = 0.0 # default value : meaningful if with_m end
Public Instance Methods
Invert signal of all coordinates
# File lib/geo_ruby/simple_features/point.rb, line 348 def -@ set_x_y_z(-@x, -@y, -@z) end
Tests the equality of the position of points + m
# File lib/geo_ruby/simple_features/point.rb, line 197 def ==(other) return false unless other.is_a?(Point) @x == other.x && @y == other.y && @z == other.z && @m == other.m end
Outputs the point in json format
# File lib/geo_ruby/simple_features/point.rb, line 343 def as_json(_options = {}) { type: 'Point', coordinates: to_coordinates } end
Outputs the geometry coordinate in human format: 47°52′48″N
# File lib/geo_ruby/simple_features/point.rb, line 295 def as_lat(options = {}) human_representation(options, x: x).join end
Outputs the geometry in coordinates format: 47°52′48″, -20°06′00″
# File lib/geo_ruby/simple_features/point.rb, line 308 def as_latlong(options = {}) human_representation(options).join(', ') end
Outputs the geometry coordinate in human format: -20°06′00W″
# File lib/geo_ruby/simple_features/point.rb, line 301 def as_long(options = {}) human_representation(options, y: y).join end
Outputs an array containing polar distance and theta
# File lib/geo_ruby/simple_features/point.rb, line 338 def as_polar [r, t] end
Bearing from a point to another as symbols. (:n, :s, :sw, :ne…)
# File lib/geo_ruby/simple_features/point.rb, line 168 def bearing_text(other) case bearing_to(other) when 1..22 then :n when 23..66 then :ne when 67..112 then :e when 113..146 then :se when 147..202 then :s when 203..246 then :sw when 247..292 then :w when 293..336 then :nw when 337..360 then :n else nil end end
Bearing from a point to another, in degrees.
# File lib/geo_ruby/simple_features/point.rb, line 160 def bearing_to(other) return 0 if self == other theta = Math.atan2(other.x - x, other.y - y) theta += Math::PI * 2 if theta < 0 theta / DEG2RAD end
Bounding box in 2D/3D. Returns an array of 2 points
# File lib/geo_ruby/simple_features/point.rb, line 184 def bounding_box if with_z [Point.from_x_y_z(@x, @y, @z), Point.from_x_y_z(@x, @y, @z)] else [Point.from_x_y(@x, @y), Point.from_x_y(@x, @y)] end end
Ellipsoidal distance in m using Vincenty's formula. Lifted entirely from Chris Veness's code at www.movable-type.co.uk/scripts/LatLongVincenty.html and adapted for Ruby.
Assumes the x and y are the lon and lat in degrees. a is the semi-major axis (equatorial radius) of the ellipsoid b is the semi-minor axis (polar radius) of the ellipsoid Their values by default are set to the WGS84 ellipsoid.
# File lib/geo_ruby/simple_features/point.rb, line 79 def ellipsoidal_distance(point, a = 6_378_137.0, b = 6_356_752.3142) # TODO: Look at https://github.com/rbur004/vincenty/blob/master/lib/vincenty.rb # and https://github.com/skyderby/vincenty_distance/blob/master/lib/vincenty.rb # as reference, or just choose to depend on one of them? f = (a - b) / a l = (point.lon - lon) * DEG2RAD u1 = Math.atan((1 - f) * Math.tan(lat * DEG2RAD)) u2 = Math.atan((1 - f) * Math.tan(point.lat * DEG2RAD)) sin_u1 = Math.sin(u1) cos_u1 = Math.cos(u1) sin_u2 = Math.sin(u2) cos_u2 = Math.cos(u2) lambda = l lambda_p = 2 * Math::PI iter_limit = 20 while (lambda - lambda_p).abs > 1e-12 && --iter_limit > 0 sin_lambda = Math.sin(lambda) cos_lambda = Math.cos(lambda) sin_sigma = \ Math.hypot((cos_u2 * sin_lambda), (cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda)) return 0 if sin_sigma == 0 # coincident points cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda sigma = Math.atan2(sin_sigma, cos_sigma) sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin_sigma cos_sq_alpha = 1 - sin_alpha * sin_alpha cos2_sigma_m = cos_sigma - 2 * sin_u1 * sin_u2 / cos_sq_alpha # equatorial line: cos_sq_alpha=0 cos2_sigma_m = 0 if cos2_sigma_m.nan? c = f / 16 * cos_sq_alpha * (4 + f * (4 - 3 * cos_sq_alpha)) lambda_p = lambda lambda = l + (1 - c) * f * sin_alpha * (sigma + c * sin_sigma * (cos2_sigma_m + c * cos_sigma * (-1 + 2 * cos2_sigma_m * cos2_sigma_m))) end return NaN if iter_limit == 0 # formula failed to converge usq = cos_sq_alpha * (a * a - b * b) / (b * b) a_bis = 1 + usq / 16_384 * (4096 + usq * (-768 + usq * (320 - 175 * usq))) b_bis = usq / 1024 * (256 + usq * (-128 + usq * (74 - 47 * usq))) delta_sigma = b_bis * sin_sigma * (cos2_sigma_m + b_bis / 4 * (cos_sigma * (-1 + 2 * cos2_sigma_m * cos2_sigma_m) - b_bis / 6 * cos2_sigma_m * (-3 + 4 * sin_sigma * sin_sigma) * (-3 + 4 * cos2_sigma_m * cos2_sigma_m))) b * a_bis * (sigma - delta_sigma) end
Return the distance between the 2D points (ie taking care only of the x and y coordinates), assuming the points are in projected coordinates.
Euclidian distance in whatever unit the x and y ordinates are.
# File lib/geo_ruby/simple_features/point.rb, line 48 def euclidian_distance(point) Math.hypot((point.x - x),(point.y - y)) end
# File lib/geo_ruby/simple_features/point.rb, line 265 def html_representation(options = {}) options[:coord] = true if options[:coord].nil? out = '<span class=\'geo\'>' out += "<abbr class='latitude' title='#{x}'>#{as_lat(options)}</abbr>" out += "<abbr class='longitude' title='#{y}'>#{as_long(options)}</abbr>" out + '</span>' end
Human representation of the geom, don't use directly, use: as_lat
, as_long
, as_latlong
# File lib/geo_ruby/simple_features/point.rb, line 275 def human_representation(options = {}, g = { x: x, y: y }) g.map do |k, v| deg = v.to_i.abs min = (60 * (v.abs - deg)).to_i labs = (v * 1_000_000).abs / 1_000_000 sec = ((((labs - labs.to_i) * 60) - ((labs - labs.to_i) * 60).to_i) * 100_000) * 60 / 100_000 str = options[:full] ? '%.i°%.2i′%05.2f″' : '%.i°%.2i′%02.0f″' if options[:coord] out = format(str, deg, min, sec) # Add cardinal out + (k == :x ? v > 0 ? 'N' : 'S' : v > 0 ? 'E' : 'W') else format(str, v.to_i, min, sec) end end end
# File lib/geo_ruby/simple_features/point.rb, line 192 def m_range [@m, @m] end
Orthogonal Distance Based www.allegro.cc/forums/thread/589720
# File lib/geo_ruby/simple_features/point.rb, line 136 def orthogonal_distance(line, tail = nil) head, tail = tail ? [line, tail] : [line[0], line[-1]] a, b = @x - head.x, @y - head.y c, d = tail.x - head.x, tail.y - head.y dot = a * c + b * d len = c * c + d * d return 0.0 if len.zero? res = dot / len xx, yy = \ if res < 0 [head.x, head.y] elsif res > 1 [tail.x, tail.y] else [head.x + res * c, head.y + res * d] end # TODO: benchmark if worth creating an instance # euclidian_distance(Point.from_x_y(xx, yy)) Math.hypot((@x - xx), (@y - yy)) end
outputs radius
# File lib/geo_ruby/simple_features/point.rb, line 319 def r Math.hypot(y,x) end
Sets all coordinates of a 2D point in one call
# File lib/geo_ruby/simple_features/point.rb, line 36 def set_x_y(x, y) @x = x && !x.is_a?(Numeric) ? x.to_f : x @y = y && !y.is_a?(Numeric) ? y.to_f : y self end
Sets all coordinates in one call. Use the m
accessor to set the m.
# File lib/geo_ruby/simple_features/point.rb, line 26 def set_x_y_z(x, y, z) # TODO: If you pass nil, nil, nil you get back 0.0, 0.0, 0.0 ... seems legit @x = x && !x.is_a?(Numeric) ? x.to_f : x @y = y && !y.is_a?(Numeric) ? y.to_f : y @z = z && !z.is_a?(Numeric) ? z.to_f : z self end
Spherical distance in meters, using 'Haversine' formula. with a radius of 6471000m Assumes x is the lon and y the lat, in degrees. The user has to make sure using this distance makes sense (ie she should be in latlon coordinates) TODO: Look at gist.github.com/timols/5268103 for comparison
# File lib/geo_ruby/simple_features/point.rb, line 58 def spherical_distance(point, r = 6_370_997.0) dlat = (point.lat - lat) * DEG2RAD / 2 dlon = (point.lon - lon) * DEG2RAD / 2 a = Math.sin(dlat)**2 + Math.cos(lat * DEG2RAD) * Math.cos(point.lat * DEG2RAD) * Math.sin(dlon)**2 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) r * c end
Outputs theta in degrees
# File lib/geo_ruby/simple_features/point.rb, line 330 def theta_deg theta_rad / DEG2RAD end
Outputs theta
# File lib/geo_ruby/simple_features/point.rb, line 325 def theta_rad Math.atan2(@y, @x) end
Helper to get all coordinates as array.
# File lib/geo_ruby/simple_features/point.rb, line 353 def to_coordinates coord = [x, y] coord << z if with_z coord << m if with_m coord end
Simple helper for 2D maps
# File lib/geo_ruby/simple_features/point.rb, line 361 def to_xy [x, y] end
Simple helper for 3D maps
# File lib/geo_ruby/simple_features/point.rb, line 366 def to_xyz [x, y, z] end