Class: GeoRuby::SimpleFeatures::Point

Inherits:
Geometry
  • Object
show all
Defined in:
lib/geo_ruby/simple_features/point.rb

Overview

Represents a point. It is in 3D if the Z coordinate is not nil.

Constant Summary collapse

DEG2RAD =
Math::PI / 180

Instance Attribute Summary collapse

Attributes inherited from Geometry

#srid, #with_m, #with_z

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Geometry

#as_ewkb, #as_ewkt, #as_georss, #as_hex_ewkb, #as_hex_wkb, #as_kml, #as_wkb, #as_wkt, #envelope, from_ewkb, from_ewkt, from_geojson, from_georss, from_georss_with_tags, from_hex_ewkb, from_kml, #to_json

Constructor Details

#initialize(srid = DEFAULT_SRID, with_z = false, with_m = false) ⇒ Point

Returns a new instance of Point.



18
19
20
21
22
23
# File 'lib/geo_ruby/simple_features/point.rb', line 18

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

Instance Attribute Details

#mObject

Returns the value of attribute m.



10
11
12
# File 'lib/geo_ruby/simple_features/point.rb', line 10

def m
  @m
end

#xObject Also known as: lon, lng

Returns the value of attribute x.



10
11
12
# File 'lib/geo_ruby/simple_features/point.rb', line 10

def x
  @x
end

#yObject Also known as: lat

Returns the value of attribute y.



10
11
12
# File 'lib/geo_ruby/simple_features/point.rb', line 10

def y
  @y
end

#zObject

Returns the value of attribute z.



10
11
12
# File 'lib/geo_ruby/simple_features/point.rb', line 10

def z
  @z
end

Class Method Details

.from_coordinates(coords, srid = DEFAULT_SRID, z = false, m = false) ⇒ Object

Creates a point from an array of coordinates



387
388
389
390
391
392
393
394
395
396
397
# File 'lib/geo_ruby/simple_features/point.rb', line 387

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

.from_geo(geo_obj, srid = DEFAULT_SRID) ⇒ Object

Creates a point from a geo object that contains latitude and longitude



372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/geo_ruby/simple_features/point.rb', line 372

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

.from_latlong(lat, lon, srid = DEFAULT_SRID) ⇒ Object

Creates a point using coordinates like 22`34 23.45N



436
437
438
439
440
441
442
443
444
445
446
# File 'lib/geo_ruby/simple_features/point.rb', line 436

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

.from_r_t(r, t, srid = DEFAULT_SRID) ⇒ Object

Creates a point using polar coordinates r and theta(degrees)



427
428
429
430
431
432
433
# File 'lib/geo_ruby/simple_features/point.rb', line 427

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

.from_x_y(x, y, srid = DEFAULT_SRID) ⇒ Object

Creates a point from the X and Y coordinates



400
401
402
403
# File 'lib/geo_ruby/simple_features/point.rb', line 400

def self.from_x_y(x, y, srid = DEFAULT_SRID)
  point = new(srid)
  point.set_x_y(x, y)
end

.from_x_y_m(x, y, m, srid = DEFAULT_SRID) ⇒ Object

Creates a point from the X, Y and M coordinates



412
413
414
415
416
# File 'lib/geo_ruby/simple_features/point.rb', line 412

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

.from_x_y_z(x, y, z, srid = DEFAULT_SRID) ⇒ Object

Creates a point from the X, Y and Z coordinates



406
407
408
409
# File 'lib/geo_ruby/simple_features/point.rb', line 406

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

.from_x_y_z_m(x, y, z, m, srid = DEFAULT_SRID) ⇒ Object

Creates a point from the X, Y, Z and M coordinates



419
420
421
422
423
# File 'lib/geo_ruby/simple_features/point.rb', line 419

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

Instance Method Details

#-@Object

Invert signal of all coordinates



349
350
351
# File 'lib/geo_ruby/simple_features/point.rb', line 349

def -@
  set_x_y_z(-@x, -@y, -@z)
end

#==(other) ⇒ Object

Tests the equality of the position of points + m



198
199
200
201
# File 'lib/geo_ruby/simple_features/point.rb', line 198

def ==(other)
  return false unless other.is_a?(Point)
  @x == other.x && @y == other.y && @z == other.z && @m == other.m
end

#as_json(_options = {}) ⇒ Object

Outputs the point in json format



344
345
346
# File 'lib/geo_ruby/simple_features/point.rb', line 344

def as_json(_options = {})
  { type: 'Point', coordinates: to_coordinates }
end

#as_lat(options = {}) ⇒ Object

Outputs the geometry coordinate in human format: 47°52′48″N



296
297
298
# File 'lib/geo_ruby/simple_features/point.rb', line 296

def as_lat(options = {})
  human_representation(options, x: x).join
end

#as_latlong(options = {}) ⇒ Object Also known as: as_ll

Outputs the geometry in coordinates format: 47°52′48″, -20°06′00″



309
310
311
# File 'lib/geo_ruby/simple_features/point.rb', line 309

def as_latlong(options = {})
  human_representation(options).join(', ')
end

#as_long(options = {}) ⇒ Object Also known as: as_lng

Outputs the geometry coordinate in human format: -20°06′00W″



302
303
304
# File 'lib/geo_ruby/simple_features/point.rb', line 302

def as_long(options = {})
  human_representation(options, y: y).join
end

#as_polarObject

Outputs an array containing polar distance and theta



339
340
341
# File 'lib/geo_ruby/simple_features/point.rb', line 339

def as_polar
  [r, t]
end

#bearing_text(other) ⇒ Object

Bearing from a point to another as symbols. (:n, :s, :sw, :ne…)



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/geo_ruby/simple_features/point.rb', line 169

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_to(other) ⇒ Object

Bearing from a point to another, in degrees.



161
162
163
164
165
166
# File 'lib/geo_ruby/simple_features/point.rb', line 161

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

#binary_geometry_typeObject

WKB geometry type of a point



213
214
215
# File 'lib/geo_ruby/simple_features/point.rb', line 213

def binary_geometry_type #:nodoc:
  1
end

#binary_representation(allow_z = true, allow_m = true) ⇒ Object

Binary representation of a point. It lacks some headers to be a valid EWKB representation.



205
206
207
208
209
210
# File 'lib/geo_ruby/simple_features/point.rb', line 205

def binary_representation(allow_z = true, allow_m = true) #:nodoc:
  bin_rep = [@x.to_f, @y.to_f].pack('EE')
  bin_rep += [@z.to_f].pack('E') if @with_z && allow_z # Default value so no crash
  bin_rep += [@m.to_f].pack('E') if @with_m && allow_m # idem
  bin_rep
end

#bounding_boxObject

Bounding box in 2D/3D. Returns an array of 2 points



185
186
187
188
189
190
191
# File 'lib/geo_ruby/simple_features/point.rb', line 185

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(point, a = 6_378_137.0, b = 6_356_752.3142) ⇒ Object

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.



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
108
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
# File 'lib/geo_ruby/simple_features/point.rb', line 80

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

#euclidian_distance(point) ⇒ Object

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.



49
50
51
# File 'lib/geo_ruby/simple_features/point.rb', line 49

def euclidian_distance(point)
  Math.hypot((point.x - x),(point.y - y))
end

#georss_gml_representation(options) ⇒ Object

georss gml representation



244
245
246
247
248
249
# File 'lib/geo_ruby/simple_features/point.rb', line 244

def georss_gml_representation(options) #:nodoc:
  georss_ns = options[:georss_ns] || 'georss'
  gml_ns = options[:gml_ns] || 'gml'
  "<#{georss_ns}:where>\n<#{gml_ns}:Point>\n<#{gml_ns}:pos>#{y} #{x}" \
  "</#{gml_ns}:pos>\n</#{gml_ns}:Point>\n</#{georss_ns}:where>\n"
end

#georss_simple_representation(options) ⇒ Object

georss simple representation



231
232
233
234
235
# File 'lib/geo_ruby/simple_features/point.rb', line 231

def georss_simple_representation(options) #:nodoc:
  georss_ns = options[:georss_ns] || 'georss'
  geom_attr = options[:geom_attr]
  "<#{georss_ns}:point#{geom_attr}>#{y} #{x}</#{georss_ns}:point>\n"
end

#georss_w3cgeo_representation(options) ⇒ Object

georss w3c representation



238
239
240
241
# File 'lib/geo_ruby/simple_features/point.rb', line 238

def georss_w3cgeo_representation(options) #:nodoc:
  w3cgeo_ns = options[:w3cgeo_ns] || 'geo'
  "<#{w3cgeo_ns}:lat>#{y}</#{w3cgeo_ns}:lat>\n<#{w3cgeo_ns}:long>#{x}</#{w3cgeo_ns}:long>\n"
end

#html_representation(options = {}) ⇒ Object



266
267
268
269
270
271
272
# File 'lib/geo_ruby/simple_features/point.rb', line 266

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(options = {}, g = { x: x, y: y }) ⇒ Object

Human representation of the geom, don’t use directly, use: #as_lat, #as_long, #as_latlong



276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/geo_ruby/simple_features/point.rb', line 276

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

#kml_representation(options = {}) ⇒ Object

outputs the geometry in kml format : options are :id, :tesselate, :extrude, :altitude_mode. If the altitude_mode option is not present, the Z (if present) will not be output (since it won’t be used by GE anyway: clampToGround is the default)



257
258
259
260
261
262
263
264
# File 'lib/geo_ruby/simple_features/point.rb', line 257

def kml_representation(options = {}) #:nodoc:
  out = "<Point#{options[:id_attr]}>\n"
  out += options[:geom_data] if options[:geom_data]
  out += "<coordinates>#{x},#{y}"
  out += ",#{options[:fixed_z] || z || 0}" if options[:allow_z]
  out += "</coordinates>\n"
  out + "</Point>\n"
end

#m_rangeObject



193
194
195
# File 'lib/geo_ruby/simple_features/point.rb', line 193

def m_range
  [@m, @m]
end

#orthogonal_distance(line, tail = nil) ⇒ Object

Orthogonal Distance Based www.allegro.cc/forums/thread/589720



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/geo_ruby/simple_features/point.rb', line 137

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

#rObject Also known as: rad

outputs radius



320
321
322
# File 'lib/geo_ruby/simple_features/point.rb', line 320

def r
  Math.hypot(y,x)
end

#set_x_y(x, y) ⇒ Object Also known as: set_lon_lat

Sets all coordinates of a 2D point in one call



37
38
39
40
41
# File 'lib/geo_ruby/simple_features/point.rb', line 37

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

#set_x_y_z(x, y, z) ⇒ Object Also known as: set_lon_lat_z

Sets all coordinates in one call. Use the m accessor to set the m.



27
28
29
30
31
32
33
# File 'lib/geo_ruby/simple_features/point.rb', line 27

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(point, r = 6_370_997.0) ⇒ Object

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



59
60
61
62
63
64
65
66
67
# File 'lib/geo_ruby/simple_features/point.rb', line 59

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

#text_geometry_typeObject

WKT geometry type of a point



226
227
228
# File 'lib/geo_ruby/simple_features/point.rb', line 226

def text_geometry_type #:nodoc:
  'POINT'
end

#text_representation(allow_z = true, allow_m = true) ⇒ Object

Text representation of a point



218
219
220
221
222
223
# File 'lib/geo_ruby/simple_features/point.rb', line 218

def text_representation(allow_z = true, allow_m = true) #:nodoc:
  tex_rep = "#{@x} #{@y}"
  tex_rep += " #{@z}" if @with_z && allow_z
  tex_rep += " #{@m}" if @with_m && allow_m
  tex_rep
end

#theta_degObject Also known as: t, tet, tetha

Outputs theta in degrees



331
332
333
# File 'lib/geo_ruby/simple_features/point.rb', line 331

def theta_deg
  theta_rad / DEG2RAD
end

#theta_radObject

Outputs theta



326
327
328
# File 'lib/geo_ruby/simple_features/point.rb', line 326

def theta_rad
  Math.atan2(@y, @x)
end

#to_coordinatesObject

Helper to get all coordinates as array.



354
355
356
357
358
359
# File 'lib/geo_ruby/simple_features/point.rb', line 354

def to_coordinates
  coord = [x, y]
  coord << z if with_z
  coord << m if with_m
  coord
end

#to_xyObject

Simple helper for 2D maps



362
363
364
# File 'lib/geo_ruby/simple_features/point.rb', line 362

def to_xy
  [x, y]
end

#to_xyzObject

Simple helper for 3D maps



367
368
369
# File 'lib/geo_ruby/simple_features/point.rb', line 367

def to_xyz
  [x, y, z]
end