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 =
0.0174532925199433
HALFPI =
1.5707963267948966

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

Constructor Details

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

Returns a new instance of Point.



22
23
24
25
26
27
# File 'lib/geo_ruby/simple_features/point.rb', line 22

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

#rObject (readonly)



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

def r
  @r
end

#tObject (readonly) Also known as: tet, tetha

radium and theta



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

def t
  @t
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, with_z = false, with_m = false) ⇒ Object

Creates a point from an array of coordinates



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

def self.from_coordinates(coords, srid = DEFAULT_SRID, with_z = false, with_m = false)
  if ! (with_z || with_m)
    from_x_y(coords[0], coords[1], srid)
  elsif with_z && with_m
    from_x_y_z_m(coords[0], coords[1], coords[2], coords[3], srid)
  elsif with_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_latlong(lat, lon, srid = DEFAULT_SRID) ⇒ Object

Creates a point using coordinates like 22`34 23.45N



420
421
422
423
424
425
426
427
428
429
# File 'lib/geo_ruby/simple_features/point.rb', line 420

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 Also known as: from_rad_tet

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



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

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 Also known as: xy, from_xy, from_lon_lat

Creates a point from the X and Y coordinates



384
385
386
387
# File 'lib/geo_ruby/simple_features/point.rb', line 384

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 Also known as: from_lon_lat_m

Creates a point from the X, Y and M coordinates



396
397
398
399
400
# File 'lib/geo_ruby/simple_features/point.rb', line 396

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 Also known as: xyz, from_xyz, from_lon_lat_z

Creates a point from the X, Y and Z coordinates



390
391
392
393
# File 'lib/geo_ruby/simple_features/point.rb', line 390

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 Also known as: from_lon_lat_z_m

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



403
404
405
406
407
# File 'lib/geo_ruby/simple_features/point.rb', line 403

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



336
337
338
# File 'lib/geo_ruby/simple_features/point.rb', line 336

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

#==(other) ⇒ Object

Tests the equality of the position of points + m



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

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

#as_json(options = {}) ⇒ Object



359
360
361
# File 'lib/geo_ruby/simple_features/point.rb', line 359

def as_json(options = {})
  { :type => 'Point', :coordinates => self.to_coordinates }
end

#as_lat(options = {}) ⇒ Object

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



288
289
290
# File 'lib/geo_ruby/simple_features/point.rb', line 288

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″



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

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″



294
295
296
# File 'lib/geo_ruby/simple_features/point.rb', line 294

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

#as_polarObject

Outputs an array containing polar distance and theta



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

def as_polar
  [r, t]
end

#bearing_text(other) ⇒ Object

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



163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/geo_ruby/simple_features/point.rb', line 163

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.



155
156
157
158
159
160
# File 'lib/geo_ruby/simple_features/point.rb', line 155

def bearing_to(other)
  return 0 if self == other
  a,b =  other.x - self.x, other.y - self.y
  res =  Math.acos(b / Math.sqrt(a*a+b*b)) / Math::PI * 180;
  a < 0 ? 360 - res : res
end

#binary_geometry_typeObject

WKB geometry type of a point



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

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.



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

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



179
180
181
182
183
184
185
# File 'lib/geo_ruby/simple_features/point.rb', line 179

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 = 6378137.0, b = 6356752.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 ones of the WGS84 ellipsoid



75
76
77
78
79
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
# File 'lib/geo_ruby/simple_features/point.rb', line 75

def ellipsoidal_distance(point, a = 6378137.0, b = 6356752.3142)
  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))
  sinU1 = Math.sin(u1)
  cosU1 = Math.cos(u1)
  sinU2 = Math.sin(u2)
  cosU2 = Math.cos(u2)

  lambda = l
  lambdaP = 2 * Math::PI
  iterLimit = 20

  while (lambda - lambdaP).abs > 1e-12 && --iterLimit > 0
    sinLambda = Math.sin(lambda)
    cosLambda = Math.cos(lambda)
    sinSigma  =\
    Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) +
              (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) *
              (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda))

    return 0 if sinSigma == 0 # coincident points

    cosSigma   = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda
    sigma      = Math.atan2(sinSigma, cosSigma)
    sinAlpha   = cosU1 * cosU2 * sinLambda / sinSigma
    cosSqAlpha = 1 - sinAlpha * sinAlpha
    cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha

    # equatorial line: cosSqAlpha=0
    cos2SigmaM = 0 if (cos2SigmaM.nan?)

    c = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha))
    lambdaP = lambda
    lambda = l + (1 - c) * f * sinAlpha * (sigma + c * sinSigma *
      (cos2SigmaM + c * cosSigma * (-1 + 2 * cos2SigmaM *
          cos2SigmaM)))
  end

  return NaN if iterLimit==0 #formula failed to converge

  uSq = cosSqAlpha * (a*a - b*b) / (b*b)
  a_bis = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
  b_bis = uSq/1024 * (256+uSq*(-128 + uSq * (74 - 47 * uSq)))
  deltaSigma = b_bis * sinSigma * (cos2SigmaM + b_bis / 4 *
    (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - b_bis / 6 *
      cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 *
        cos2SigmaM * cos2SigmaM)))

  b * a_bis * (sigma - deltaSigma)
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.



52
53
54
# File 'lib/geo_ruby/simple_features/point.rb', line 52

def euclidian_distance(point)
  Math.sqrt((point.x - x)**2 + (point.y - y)**2)
end

#georss_gml_representation(options) ⇒ Object

georss gml representation



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

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

#georss_simple_representation(options) ⇒ Object

georss simple representation



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

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



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

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



260
261
262
263
264
265
266
# File 'lib/geo_ruby/simple_features/point.rb', line 260

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



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/geo_ruby/simple_features/point.rb', line 270

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 = str % [deg,min,sec]
      out += k == :x ? v > 0 ? 'N' : 'S' : v > 0 ? 'E' : 'W'
    else
      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)



251
252
253
254
255
256
257
258
# File 'lib/geo_ruby/simple_features/point.rb', line 251

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



187
188
189
# File 'lib/geo_ruby/simple_features/point.rb', line 187

def m_range
  [@m,@m]
end

#orthogonal_distance(line, tail = nil) ⇒ Object

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



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/geo_ruby/simple_features/point.rb', line 131

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.sqrt((@x - xx) ** 2 + (@y - yy) ** 2)
end

#radObject

radium and theta



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

def r
  @r
end

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

Sets all coordinates of a 2D point in one call



40
41
42
43
44
# File 'lib/geo_ruby/simple_features/point.rb', line 40

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.



31
32
33
34
35
36
# File 'lib/geo_ruby/simple_features/point.rb', line 31

def set_x_y_z(x, y, z)
  @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 = 6370997.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)



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

def spherical_distance(point, r = 6370997.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



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

def text_geometry_type #:nodoc:
  'POINT'
end

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

Text representation of a point



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

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

Outputs theta in degrees



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

def theta_deg
  theta_rad / DEG2RAD
end

#theta_radObject

Outputs theta



316
317
318
319
320
321
322
323
# File 'lib/geo_ruby/simple_features/point.rb', line 316

def theta_rad
  if @x.zero?
    @y < 0 ? 3 * HALFPI : HALFPI
  else
    th = Math.atan(@y/@x)
    th += 2 * Math::PI if r > 0
  end
end

#to_coordinatesObject

TODO Perhaps should support with_m analogous to from_coordinates?



341
342
343
344
345
346
347
# File 'lib/geo_ruby/simple_features/point.rb', line 341

def to_coordinates
  if with_z
    [x, y, z]
  else
    [x, y]
  end
end

#to_json(options = {}) ⇒ Object Also known as: as_geojson

Simple geojson representation TODO add CRS / SRID support?



365
366
367
# File 'lib/geo_ruby/simple_features/point.rb', line 365

def to_json(options = {})
  as_json(options).to_json(options)
end

#to_xyObject

Simple helper for 2D maps



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

def to_xy
  [x, y]
end

#to_xyzObject

Simple helper for 3D maps



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

def to_xyz
  [x, y, z]
end