geekdaily-georuby 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  3. data/.github/ISSUE_TEMPLATE/code-of-conduct-issue.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.gitignore +7 -0
  6. data/.rubocop.yml +8 -0
  7. data/.travis.yml +24 -0
  8. data/CODE_OF_CONDUCT.md +132 -0
  9. data/CONTRIBUTING.md +19 -0
  10. data/Gemfile +4 -0
  11. data/Guardfile +16 -0
  12. data/History.txt +4 -0
  13. data/MIT-LICENSE +7 -0
  14. data/README.md +237 -0
  15. data/Rakefile +32 -0
  16. data/bench.rb +35 -0
  17. data/georuby.gemspec +38 -0
  18. data/lib/geo_ruby.rb +11 -0
  19. data/lib/geo_ruby/ewk.rb +2 -0
  20. data/lib/geo_ruby/ewk/ewkb_parser.rb +206 -0
  21. data/lib/geo_ruby/ewk/ewkt_parser.rb +321 -0
  22. data/lib/geo_ruby/geojson.rb +139 -0
  23. data/lib/geo_ruby/georss.rb +156 -0
  24. data/lib/geo_ruby/gpx.rb +113 -0
  25. data/lib/geo_ruby/kml.rb +96 -0
  26. data/lib/geo_ruby/shp.rb +2 -0
  27. data/lib/geo_ruby/shp4r/dbf.rb +60 -0
  28. data/lib/geo_ruby/shp4r/shp.rb +726 -0
  29. data/lib/geo_ruby/simple_features.rb +21 -0
  30. data/lib/geo_ruby/simple_features/circle.rb +59 -0
  31. data/lib/geo_ruby/simple_features/envelope.rb +174 -0
  32. data/lib/geo_ruby/simple_features/geometry.rb +252 -0
  33. data/lib/geo_ruby/simple_features/geometry_collection.rb +143 -0
  34. data/lib/geo_ruby/simple_features/geometry_factory.rb +84 -0
  35. data/lib/geo_ruby/simple_features/helper.rb +18 -0
  36. data/lib/geo_ruby/simple_features/line_string.rb +224 -0
  37. data/lib/geo_ruby/simple_features/linear_ring.rb +34 -0
  38. data/lib/geo_ruby/simple_features/multi_line_string.rb +56 -0
  39. data/lib/geo_ruby/simple_features/multi_point.rb +52 -0
  40. data/lib/geo_ruby/simple_features/multi_polygon.rb +55 -0
  41. data/lib/geo_ruby/simple_features/point.rb +463 -0
  42. data/lib/geo_ruby/simple_features/polygon.rb +172 -0
  43. data/lib/geo_ruby/version.rb +3 -0
  44. data/lib/georuby.rb +2 -0
  45. data/spec/data/geojson/feature.json +9 -0
  46. data/spec/data/geojson/feature_collection.json +33 -0
  47. data/spec/data/georss/atom.xml +21 -0
  48. data/spec/data/georss/gml.xml +40 -0
  49. data/spec/data/georss/w3c.xml +22 -0
  50. data/spec/data/gpx/fells_loop.gpx +1077 -0
  51. data/spec/data/gpx/long.gpx +1642 -0
  52. data/spec/data/gpx/long.kml +31590 -0
  53. data/spec/data/gpx/long.nmea +2220 -0
  54. data/spec/data/gpx/short.gpx +13634 -0
  55. data/spec/data/gpx/short.kml +130 -0
  56. data/spec/data/gpx/tracktreks.gpx +706 -0
  57. data/spec/data/multipoint.dbf +0 -0
  58. data/spec/data/multipoint.shp +0 -0
  59. data/spec/data/multipoint.shx +0 -0
  60. data/spec/data/point.dbf +0 -0
  61. data/spec/data/point.shp +0 -0
  62. data/spec/data/point.shx +0 -0
  63. data/spec/data/polygon.dbf +0 -0
  64. data/spec/data/polygon.shp +0 -0
  65. data/spec/data/polygon.shx +0 -0
  66. data/spec/data/polyline.dbf +0 -0
  67. data/spec/data/polyline.shp +0 -0
  68. data/spec/data/polyline.shx +0 -0
  69. data/spec/geo_ruby/ewk/ewkb_parser_spec.rb +157 -0
  70. data/spec/geo_ruby/ewk/ewkt_parser_spec.rb +178 -0
  71. data/spec/geo_ruby/geojson_spec.rb +164 -0
  72. data/spec/geo_ruby/georss_spec.rb +238 -0
  73. data/spec/geo_ruby/gpx_spec.rb +103 -0
  74. data/spec/geo_ruby/kml_spec.rb +102 -0
  75. data/spec/geo_ruby/shp4r/shp_spec.rb +246 -0
  76. data/spec/geo_ruby/simple_features/circle_spec.rb +14 -0
  77. data/spec/geo_ruby/simple_features/envelope_spec.rb +47 -0
  78. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
  79. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
  80. data/spec/geo_ruby/simple_features/geometry_spec.rb +37 -0
  81. data/spec/geo_ruby/simple_features/line_string_spec.rb +268 -0
  82. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +24 -0
  83. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
  84. data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
  85. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
  86. data/spec/geo_ruby/simple_features/point_spec.rb +547 -0
  87. data/spec/geo_ruby/simple_features/polygon_spec.rb +121 -0
  88. data/spec/geo_ruby_spec.rb +22 -0
  89. data/spec/spec_helper.rb +73 -0
  90. metadata +322 -0
@@ -0,0 +1,34 @@
1
+ require 'geo_ruby/simple_features/line_string'
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ # Represents a linear ring, which is a closed line string (see LineString).
6
+ # Currently, no check is performed to verify if the linear ring is really closed.
7
+ class LinearRing < LineString
8
+ def initialize(srid = DEFAULT_SRID, with_z = false, with_m = false)
9
+ super(srid, with_z, with_m)
10
+ end
11
+
12
+ # fix kml export
13
+ alias_method :orig_kml_representation, :kml_representation
14
+ def kml_representation(options = {})
15
+ orig_kml_representation(options).gsub('LineString', 'LinearRing')
16
+ end
17
+
18
+ # Does this linear string contain the given point? We use the
19
+ # algorithm described here:
20
+ #
21
+ # http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
22
+ def contains_point?(point)
23
+ x, y = point.x, point.y
24
+ tuples = @points.zip(@points[1..-1] + [@points[0]])
25
+ crossings =
26
+ tuples.select do |a, b|
27
+ (b.y > y != a.y > y) && (x < (a.x - b.x) * (y - b.y) / (a.y - b.y) + b.x)
28
+ end
29
+
30
+ crossings.size % 2 == 1
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ require 'geo_ruby/simple_features/geometry_collection'
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ # Represents a group of line strings (see LineString).
6
+ class MultiLineString < GeometryCollection
7
+ def initialize(srid = DEFAULT_SRID, _with_z = false, _with_m = false)
8
+ super(srid)
9
+ end
10
+
11
+ def binary_geometry_type #:nodoc:
12
+ 5
13
+ end
14
+
15
+ def points
16
+ geometries.map(&:points).flatten
17
+ end
18
+
19
+ # Text representation of a multi line string
20
+ def text_representation(allow_z = true, allow_m = true) #:nodoc:
21
+ @geometries.collect { |line_string| '(' + line_string.text_representation(allow_z, allow_m) + ')' }.join(',')
22
+ end
23
+
24
+ # WKT geometry type
25
+ def text_geometry_type #:nodoc:
26
+ 'MULTILINESTRING'
27
+ end
28
+
29
+ def to_line_string(_join = true)
30
+ LineString.from_points(points)
31
+ end
32
+
33
+ def to_coordinates
34
+ geometries.map(&:to_coordinates)
35
+ end
36
+
37
+ def as_json(_options = {})
38
+ { type: 'MultiLineString', coordinates: to_coordinates }
39
+ end
40
+
41
+ # Creates a new multi line string from an array of line strings
42
+ def self.from_line_strings(line_strings, srid = DEFAULT_SRID, with_z = false, with_m = false)
43
+ multi_line_string = new(srid, with_z, with_m)
44
+ multi_line_string.concat(line_strings)
45
+ multi_line_string
46
+ end
47
+
48
+ # Creates a new multi line string from sequences of points : (((x,y)...(x,y)),((x,y)...(x,y)))
49
+ def self.from_coordinates(point_sequences, srid = DEFAULT_SRID, with_z = false, with_m = false)
50
+ multi_line_string = new(srid, with_z, with_m)
51
+ multi_line_string.concat(point_sequences.collect { |points| LineString.from_coordinates(points, srid, with_z, with_m) })
52
+ multi_line_string
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,52 @@
1
+ require 'geo_ruby/simple_features/geometry_collection'
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ # Represents a group of points (see Point).
6
+ class MultiPoint < GeometryCollection
7
+ def initialize(srid = DEFAULT_SRID, with_z = false, with_m = false)
8
+ super(srid, with_z, with_m)
9
+ end
10
+
11
+ def binary_geometry_type #:nodoc:
12
+ 4
13
+ end
14
+
15
+ def points
16
+ @geometries
17
+ end
18
+
19
+ # Text representation of a MultiPoint
20
+ def text_representation(allow_z = true, allow_m = true) #:nodoc:
21
+ '(' + @geometries.collect { |point| point.text_representation(allow_z, allow_m) }.join('),(') + ')'
22
+ end
23
+
24
+ # WKT geoemtry type
25
+ def text_geometry_type #:nodoc:
26
+ 'MULTIPOINT'
27
+ end
28
+
29
+ def to_coordinates
30
+ points.map(&:to_coordinates)
31
+ end
32
+
33
+ def as_json(_options = {})
34
+ { type: 'MultiPoint', coordinates: to_coordinates }
35
+ end
36
+
37
+ # Creates a new multi point from an array of points
38
+ def self.from_points(points, srid = DEFAULT_SRID, with_z = false, with_m = false)
39
+ multi_point = new(srid, with_z, with_m)
40
+ multi_point.concat(points)
41
+ multi_point
42
+ end
43
+
44
+ # Creates a new multi point from a list of point coordinates : ((x,y)...(x,y))
45
+ def self.from_coordinates(points, srid = DEFAULT_SRID, with_z = false, with_m = false)
46
+ multi_point = new(srid, with_z, with_m)
47
+ multi_point.concat(points.collect { |point| Point.from_coordinates(point, srid, with_z, with_m) })
48
+ multi_point
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ require 'geo_ruby/simple_features/geometry_collection'
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ # Represents a group of polygons (see Polygon).
6
+ class MultiPolygon < GeometryCollection
7
+ def initialize(srid = DEFAULT_SRID, _with_z = false, _with_m = false)
8
+ super(srid)
9
+ end
10
+
11
+ def binary_geometry_type #:nodoc:
12
+ 6
13
+ end
14
+
15
+ def points
16
+ @points ||= geometries.reduce([]) do |arr, r|
17
+ arr.concat(r.rings.map(&:points).flatten)
18
+ end
19
+ end
20
+
21
+ # Text representation of a MultiPolygon
22
+ def text_representation(allow_z = true, allow_m = true) #:nodoc:
23
+ @geometries.map { |polygon| '(' + polygon.text_representation(allow_z, allow_m) + ')' }.join(',')
24
+ end
25
+
26
+ # WKT geometry type
27
+ def text_geometry_type #:nodoc:
28
+ 'MULTIPOLYGON'
29
+ end
30
+
31
+ def to_coordinates
32
+ geometries.map(&:to_coordinates)
33
+ end
34
+
35
+ def as_json(_options = {})
36
+ { type: 'MultiPolygon',
37
+ coordinates: to_coordinates }
38
+ end
39
+
40
+ # Creates a multi polygon from an array of polygons
41
+ def self.from_polygons(polygons, srid = DEFAULT_SRID, with_z = false, with_m = false)
42
+ multi_polygon = new(srid, with_z, with_m)
43
+ multi_polygon.concat(polygons)
44
+ multi_polygon
45
+ end
46
+
47
+ # Creates a multi polygon from sequences of points : ((((x,y)...(x,y)),((x,y)...(x,y)),((x,y)...(x,y)))
48
+ def self.from_coordinates(point_sequence_sequences, srid = DEFAULT_SRID, with_z = false, with_m = false)
49
+ multi_polygon = new(srid, with_z, with_m)
50
+ multi_polygon.concat(point_sequence_sequences.collect { |point_sequences| Polygon.from_coordinates(point_sequences, srid, with_z, with_m) })
51
+ multi_polygon
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,463 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'geo_ruby/simple_features/geometry'
3
+
4
+ module GeoRuby
5
+ module SimpleFeatures
6
+ # Represents a point. It is in 3D if the Z coordinate is not +nil+.
7
+ class Point < Geometry
8
+ DEG2RAD = Math::PI / 180
9
+
10
+ attr_accessor :x, :y, :z, :m
11
+
12
+ # If you prefer calling the coordinates lat and lon
13
+ # (or lng, for GeoKit compatibility)
14
+ alias_method :lon, :x
15
+ alias_method :lng, :x
16
+ alias_method :lat, :y
17
+
18
+ def initialize(srid = DEFAULT_SRID, with_z = false, with_m = false)
19
+ super(srid, with_z, with_m)
20
+ @x = @y = 0.0
21
+ @z = 0.0 # default value : meaningful if with_z
22
+ @m = 0.0 # default value : meaningful if with_m
23
+ end
24
+
25
+ # Sets all coordinates in one call.
26
+ # Use the +m+ accessor to set the m.
27
+ def set_x_y_z(x, y, z)
28
+ # TODO: If you pass nil, nil, nil you get back 0.0, 0.0, 0.0 ... seems legit
29
+ @x = x && !x.is_a?(Numeric) ? x.to_f : x
30
+ @y = y && !y.is_a?(Numeric) ? y.to_f : y
31
+ @z = z && !z.is_a?(Numeric) ? z.to_f : z
32
+ self
33
+ end
34
+ alias_method :set_lon_lat_z, :set_x_y_z
35
+
36
+ # Sets all coordinates of a 2D point in one call
37
+ def set_x_y(x, y)
38
+ @x = x && !x.is_a?(Numeric) ? x.to_f : x
39
+ @y = y && !y.is_a?(Numeric) ? y.to_f : y
40
+ self
41
+ end
42
+ alias_method :set_lon_lat, :set_x_y
43
+
44
+ # Return the distance between the 2D points (ie taking care only
45
+ # of the x and y coordinates), assuming the points are in
46
+ # projected coordinates.
47
+ #
48
+ # Euclidian distance in whatever unit the x and y ordinates are.
49
+ def euclidian_distance(point)
50
+ Math.hypot((point.x - x),(point.y - y))
51
+ end
52
+
53
+ # Spherical distance in meters, using 'Haversine' formula.
54
+ # with a radius of 6471000m
55
+ # Assumes x is the lon and y the lat, in degrees.
56
+ # The user has to make sure using this distance makes sense
57
+ # (ie she should be in latlon coordinates)
58
+ # TODO: Look at https://gist.github.com/timols/5268103 for comparison
59
+ def spherical_distance(point, r = 6_370_997.0)
60
+ dlat = (point.lat - lat) * DEG2RAD / 2
61
+ dlon = (point.lon - lon) * DEG2RAD / 2
62
+
63
+ a = Math.sin(dlat)**2 + Math.cos(lat * DEG2RAD) *
64
+ Math.cos(point.lat * DEG2RAD) * Math.sin(dlon)**2
65
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
66
+ r * c
67
+ end
68
+
69
+ #
70
+ # Ellipsoidal distance in m using Vincenty's formula.
71
+ # Lifted entirely from Chris Veness's code at
72
+ # http://www.movable-type.co.uk/scripts/LatLongVincenty.html
73
+ # and adapted for Ruby.
74
+ #
75
+ # Assumes the x and y are the lon and lat in degrees.
76
+ # a is the semi-major axis (equatorial radius) of the ellipsoid
77
+ # b is the semi-minor axis (polar radius) of the ellipsoid
78
+ # Their values by default are set to the WGS84 ellipsoid.
79
+ #
80
+ def ellipsoidal_distance(point, a = 6_378_137.0, b = 6_356_752.3142)
81
+ # TODO: Look at https://github.com/rbur004/vincenty/blob/master/lib/vincenty.rb
82
+ # and https://github.com/skyderby/vincenty_distance/blob/master/lib/vincenty.rb
83
+ # as reference, or just choose to depend on one of them?
84
+ f = (a - b) / a
85
+ l = (point.lon - lon) * DEG2RAD
86
+
87
+ u1 = Math.atan((1 - f) * Math.tan(lat * DEG2RAD))
88
+ u2 = Math.atan((1 - f) * Math.tan(point.lat * DEG2RAD))
89
+ sin_u1 = Math.sin(u1)
90
+ cos_u1 = Math.cos(u1)
91
+ sin_u2 = Math.sin(u2)
92
+ cos_u2 = Math.cos(u2)
93
+
94
+ lambda = l
95
+ lambda_p = 2 * Math::PI
96
+ iter_limit = 20
97
+
98
+ while (lambda - lambda_p).abs > 1e-12 && --iter_limit > 0
99
+ sin_lambda = Math.sin(lambda)
100
+ cos_lambda = Math.cos(lambda)
101
+ sin_sigma = \
102
+ Math.hypot((cos_u2 * sin_lambda), (cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda))
103
+
104
+ return 0 if sin_sigma == 0 # coincident points
105
+
106
+ cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda
107
+ sigma = Math.atan2(sin_sigma, cos_sigma)
108
+ sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin_sigma
109
+ cos_sq_alpha = 1 - sin_alpha * sin_alpha
110
+ cos2_sigma_m = cos_sigma - 2 * sin_u1 * sin_u2 / cos_sq_alpha
111
+
112
+ # equatorial line: cos_sq_alpha=0
113
+ cos2_sigma_m = 0 if cos2_sigma_m.nan?
114
+
115
+ c = f / 16 * cos_sq_alpha * (4 + f * (4 - 3 * cos_sq_alpha))
116
+ lambda_p = lambda
117
+ lambda = l + (1 - c) * f * sin_alpha * (sigma + c * sin_sigma *
118
+ (cos2_sigma_m + c * cos_sigma * (-1 + 2 * cos2_sigma_m *
119
+ cos2_sigma_m)))
120
+ end
121
+
122
+ return NaN if iter_limit == 0 # formula failed to converge
123
+
124
+ usq = cos_sq_alpha * (a * a - b * b) / (b * b)
125
+ a_bis = 1 + usq / 16_384 * (4096 + usq * (-768 + usq * (320 - 175 * usq)))
126
+ b_bis = usq / 1024 * (256 + usq * (-128 + usq * (74 - 47 * usq)))
127
+ delta_sigma = b_bis * sin_sigma * (cos2_sigma_m + b_bis / 4 *
128
+ (cos_sigma * (-1 + 2 * cos2_sigma_m * cos2_sigma_m) - b_bis / 6 *
129
+ cos2_sigma_m * (-3 + 4 * sin_sigma * sin_sigma) * (-3 + 4 *
130
+ cos2_sigma_m * cos2_sigma_m)))
131
+
132
+ b * a_bis * (sigma - delta_sigma)
133
+ end
134
+
135
+ # Orthogonal Distance
136
+ # Based http://www.allegro.cc/forums/thread/589720
137
+ def orthogonal_distance(line, tail = nil)
138
+ head, tail = tail ? [line, tail] : [line[0], line[-1]]
139
+ a, b = @x - head.x, @y - head.y
140
+ c, d = tail.x - head.x, tail.y - head.y
141
+
142
+ dot = a * c + b * d
143
+ len = c * c + d * d
144
+ return 0.0 if len.zero?
145
+ res = dot / len
146
+
147
+ xx, yy = \
148
+ if res < 0
149
+ [head.x, head.y]
150
+ elsif res > 1
151
+ [tail.x, tail.y]
152
+ else
153
+ [head.x + res * c, head.y + res * d]
154
+ end
155
+ # TODO: benchmark if worth creating an instance
156
+ # euclidian_distance(Point.from_x_y(xx, yy))
157
+ Math.hypot((@x - xx), (@y - yy))
158
+ end
159
+
160
+ # Bearing from a point to another, in degrees.
161
+ def bearing_to(other)
162
+ return 0 if self == other
163
+ theta = Math.atan2(other.x - x, other.y - y)
164
+ theta += Math::PI * 2 if theta < 0
165
+ theta / DEG2RAD
166
+ end
167
+
168
+ # Bearing from a point to another as symbols. (:n, :s, :sw, :ne...)
169
+ def bearing_text(other)
170
+ case bearing_to(other)
171
+ when 1..22 then :n
172
+ when 23..66 then :ne
173
+ when 67..112 then :e
174
+ when 113..146 then :se
175
+ when 147..202 then :s
176
+ when 203..246 then :sw
177
+ when 247..292 then :w
178
+ when 293..336 then :nw
179
+ when 337..360 then :n
180
+ else nil
181
+ end
182
+ end
183
+
184
+ # Bounding box in 2D/3D. Returns an array of 2 points
185
+ def bounding_box
186
+ if with_z
187
+ [Point.from_x_y_z(@x, @y, @z), Point.from_x_y_z(@x, @y, @z)]
188
+ else
189
+ [Point.from_x_y(@x, @y), Point.from_x_y(@x, @y)]
190
+ end
191
+ end
192
+
193
+ def m_range
194
+ [@m, @m]
195
+ end
196
+
197
+ # Tests the equality of the position of points + m
198
+ def ==(other)
199
+ return false unless other.is_a?(Point)
200
+ @x == other.x && @y == other.y && @z == other.z && @m == other.m
201
+ end
202
+
203
+ # Binary representation of a point.
204
+ # It lacks some headers to be a valid EWKB representation.
205
+ def binary_representation(allow_z = true, allow_m = true) #:nodoc:
206
+ bin_rep = [@x.to_f, @y.to_f].pack('EE')
207
+ bin_rep += [@z.to_f].pack('E') if @with_z && allow_z # Default value so no crash
208
+ bin_rep += [@m.to_f].pack('E') if @with_m && allow_m # idem
209
+ bin_rep
210
+ end
211
+
212
+ # WKB geometry type of a point
213
+ def binary_geometry_type #:nodoc:
214
+ 1
215
+ end
216
+
217
+ # Text representation of a point
218
+ def text_representation(allow_z = true, allow_m = true) #:nodoc:
219
+ tex_rep = "#{@x} #{@y}"
220
+ tex_rep += " #{@z}" if @with_z && allow_z
221
+ tex_rep += " #{@m}" if @with_m && allow_m
222
+ tex_rep
223
+ end
224
+
225
+ # WKT geometry type of a point
226
+ def text_geometry_type #:nodoc:
227
+ 'POINT'
228
+ end
229
+
230
+ # georss simple representation
231
+ def georss_simple_representation(options) #:nodoc:
232
+ georss_ns = options[:georss_ns] || 'georss'
233
+ geom_attr = options[:geom_attr]
234
+ "<#{georss_ns}:point#{geom_attr}>#{y} #{x}</#{georss_ns}:point>\n"
235
+ end
236
+
237
+ # georss w3c representation
238
+ def georss_w3cgeo_representation(options) #:nodoc:
239
+ w3cgeo_ns = options[:w3cgeo_ns] || 'geo'
240
+ "<#{w3cgeo_ns}:lat>#{y}</#{w3cgeo_ns}:lat>\n<#{w3cgeo_ns}:long>#{x}</#{w3cgeo_ns}:long>\n"
241
+ end
242
+
243
+ # georss gml representation
244
+ def georss_gml_representation(options) #:nodoc:
245
+ georss_ns = options[:georss_ns] || 'georss'
246
+ gml_ns = options[:gml_ns] || 'gml'
247
+ "<#{georss_ns}:where>\n<#{gml_ns}:Point>\n<#{gml_ns}:pos>#{y} #{x}" \
248
+ "</#{gml_ns}:pos>\n</#{gml_ns}:Point>\n</#{georss_ns}:where>\n"
249
+ end
250
+
251
+ # outputs the geometry in kml format : options are
252
+ # <tt>:id</tt>, <tt>:tesselate</tt>, <tt>:extrude</tt>,
253
+ # <tt>:altitude_mode</tt>.
254
+ # If the altitude_mode option is not present, the Z (if present)
255
+ # will not be output (since it won't be used by GE anyway:
256
+ # clampToGround is the default)
257
+ def kml_representation(options = {}) #:nodoc:
258
+ out = "<Point#{options[:id_attr]}>\n"
259
+ out += options[:geom_data] if options[:geom_data]
260
+ out += "<coordinates>#{x},#{y}"
261
+ out += ",#{options[:fixed_z] || z || 0}" if options[:allow_z]
262
+ out += "</coordinates>\n"
263
+ out + "</Point>\n"
264
+ end
265
+
266
+ def html_representation(options = {})
267
+ options[:coord] = true if options[:coord].nil?
268
+ out = '<span class=\'geo\'>'
269
+ out += "<abbr class='latitude' title='#{x}'>#{as_lat(options)}</abbr>"
270
+ out += "<abbr class='longitude' title='#{y}'>#{as_long(options)}</abbr>"
271
+ out + '</span>'
272
+ end
273
+
274
+ # Human representation of the geom, don't use directly, use:
275
+ # #as_lat, #as_long, #as_latlong
276
+ def human_representation(options = {}, g = { x: x, y: y })
277
+ g.map do |k, v|
278
+ deg = v.to_i.abs
279
+ min = (60 * (v.abs - deg)).to_i
280
+ labs = (v * 1_000_000).abs / 1_000_000
281
+ sec = ((((labs - labs.to_i) * 60) -
282
+ ((labs - labs.to_i) * 60).to_i) * 100_000) * 60 / 100_000
283
+ str = options[:full] ? '%.i°%.2i′%05.2f″' : '%.i°%.2i′%02.0f″'
284
+ if options[:coord]
285
+ out = format(str, deg, min, sec)
286
+ # Add cardinal
287
+ out + (k == :x ? v > 0 ? 'N' : 'S' : v > 0 ? 'E' : 'W')
288
+ else
289
+ format(str, v.to_i, min, sec)
290
+ end
291
+ end
292
+ end
293
+
294
+ # Outputs the geometry coordinate in human format:
295
+ # 47°52′48″N
296
+ def as_lat(options = {})
297
+ human_representation(options, x: x).join
298
+ end
299
+
300
+ # Outputs the geometry coordinate in human format:
301
+ # -20°06′00W″
302
+ def as_long(options = {})
303
+ human_representation(options, y: y).join
304
+ end
305
+ alias_method :as_lng, :as_long
306
+
307
+ # Outputs the geometry in coordinates format:
308
+ # 47°52′48″, -20°06′00″
309
+ def as_latlong(options = {})
310
+ human_representation(options).join(', ')
311
+ end
312
+ alias_method :as_ll, :as_latlong
313
+
314
+ # Convert cartesian (stored) to polar coordinates
315
+ # http://www.java2s.com/Code/Ruby/Development/ConverttheCartesianpointxytopolarmagnitudeanglecoordinates.htm
316
+ # https://tutorial.math.lamar.edu/classes/calcii/polarcoordinates.aspx
317
+ # https://www.mathsisfun.com/polar-cartesian-coordinates.html
318
+
319
+ # outputs radius
320
+ def r
321
+ Math.hypot(y,x)
322
+ end
323
+ alias_method :rad, :r
324
+
325
+ # Outputs theta
326
+ def theta_rad
327
+ Math.atan2(@y, @x)
328
+ end
329
+
330
+ # Outputs theta in degrees
331
+ def theta_deg
332
+ theta_rad / DEG2RAD
333
+ end
334
+ alias_method :t, :theta_deg
335
+ alias_method :tet, :theta_deg
336
+ alias_method :tetha, :theta_deg
337
+
338
+ # Outputs an array containing polar distance and theta
339
+ def as_polar
340
+ [r, t]
341
+ end
342
+
343
+ # Outputs the point in json format
344
+ def as_json(_options = {})
345
+ { type: 'Point', coordinates: to_coordinates }
346
+ end
347
+
348
+ # Invert signal of all coordinates
349
+ def -@
350
+ set_x_y_z(-@x, -@y, -@z)
351
+ end
352
+
353
+ # Helper to get all coordinates as array.
354
+ def to_coordinates
355
+ coord = [x, y]
356
+ coord << z if with_z
357
+ coord << m if with_m
358
+ coord
359
+ end
360
+
361
+ # Simple helper for 2D maps
362
+ def to_xy
363
+ [x, y]
364
+ end
365
+
366
+ # Simple helper for 3D maps
367
+ def to_xyz
368
+ [x, y, z]
369
+ end
370
+
371
+ # Creates a point from a geo object that contains latitude and longitude
372
+ def self.from_geo(geo_obj, srid = DEFAULT_SRID)
373
+ lat_names = %w(latitude lat).map(&:to_s)
374
+ long_names = %w(longitude long lng).map(&:to_s)
375
+
376
+ lat_method = lat_names.select {|mth| geo_obj.respond_to?(mth)}.first
377
+ long_method = long_names.select {|mth| geo_obj.respond_to?(mth)}.first
378
+
379
+ if lat_method && long_method
380
+ return from_coordinates([geo_obj.send(long_method), geo_obj.send(lat_method)], srid)
381
+ else
382
+ raise ArgumentError, 'object must have both latitude and longitude methods'
383
+ end
384
+ end
385
+
386
+ # Creates a point from an array of coordinates
387
+ def self.from_coordinates(coords, srid = DEFAULT_SRID, z = false, m = false)
388
+ if !(z || m)
389
+ from_x_y(coords[0], coords[1], srid)
390
+ elsif z && m
391
+ from_x_y_z_m(coords[0], coords[1], coords[2], coords[3], srid)
392
+ elsif z
393
+ from_x_y_z(coords[0], coords[1], coords[2], srid)
394
+ else
395
+ from_x_y_m(coords[0], coords[1], coords[2], srid)
396
+ end
397
+ end
398
+
399
+ # Creates a point from the X and Y coordinates
400
+ def self.from_x_y(x, y, srid = DEFAULT_SRID)
401
+ point = new(srid)
402
+ point.set_x_y(x, y)
403
+ end
404
+
405
+ # Creates a point from the X, Y and Z coordinates
406
+ def self.from_x_y_z(x, y, z, srid = DEFAULT_SRID)
407
+ point = new(srid, true)
408
+ point.set_x_y_z(x, y, z)
409
+ end
410
+
411
+ # Creates a point from the X, Y and M coordinates
412
+ def self.from_x_y_m(x, y, m, srid = DEFAULT_SRID)
413
+ point = new(srid, false, true)
414
+ point.m = m
415
+ point.set_x_y(x, y)
416
+ end
417
+
418
+ # Creates a point from the X, Y, Z and M coordinates
419
+ def self.from_x_y_z_m(x, y, z, m, srid = DEFAULT_SRID)
420
+ point = new(srid, true, true)
421
+ point.m = m
422
+ point.set_x_y_z(x, y, z)
423
+ end
424
+
425
+ # Creates a point using polar coordinates
426
+ # r and theta(degrees)
427
+ def self.from_r_t(r, t, srid = DEFAULT_SRID)
428
+ t *= DEG2RAD
429
+ x = r * Math.cos(t)
430
+ y = r * Math.sin(t)
431
+ point = new(srid)
432
+ point.set_x_y(x, y)
433
+ end
434
+
435
+ # Creates a point using coordinates like 22`34 23.45N
436
+ def self.from_latlong(lat, lon, srid = DEFAULT_SRID)
437
+ p = [lat, lon].map do |l|
438
+ sig, deg, min, sec, cen = \
439
+ l.scan(/(-)?(\d{1,2})\D*(\d{2})\D*(\d{2})(\D*(\d{1,3}))?/).flatten
440
+ sig = true if l =~ /W|S/
441
+ dec = deg.to_i + (min.to_i * 60 + "#{sec}#{cen}".to_f) / 3600
442
+ sig ? dec * -1 : dec
443
+ end
444
+ point = new(srid)
445
+ point.set_x_y(p[0], p[1])
446
+ end
447
+
448
+ class << self
449
+ # Aliasing the constructors in case you like lat/lon over y/x
450
+ {:from_x_y => [:xy, :from_xy, :from_lon_lat],
451
+ :from_x_y_z => [:xyz, :from_xyz, :from_lon_lat_z],
452
+ :from_x_y_m => [:from_lon_lat_m],
453
+ :from_x_y_z_m => [:from_lon_lat_z_m],
454
+ :from_r_t => [:from_rad_tet]
455
+ }.each do |orig_method, aliases|
456
+ aliases.each do |aliased_method|
457
+ alias_method aliased_method, orig_method
458
+ end
459
+ end
460
+ end
461
+ end # Point
462
+ end # SimpleFeatures
463
+ end # GeoRuby