ppe-georuby 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. data/.gitignore +6 -0
  2. data/History.txt +4 -0
  3. data/LICENSE +21 -0
  4. data/README.txt +118 -0
  5. data/Rakefile +48 -0
  6. data/VERSION +1 -0
  7. data/lib/geo_ruby.rb +22 -0
  8. data/lib/geo_ruby/gpx.rb +1 -0
  9. data/lib/geo_ruby/gpx4r/gpx.rb +117 -0
  10. data/lib/geo_ruby/shp.rb +1 -0
  11. data/lib/geo_ruby/shp4r/dbf.rb +41 -0
  12. data/lib/geo_ruby/shp4r/shp.rb +697 -0
  13. data/lib/geo_ruby/simple_features/envelope.rb +167 -0
  14. data/lib/geo_ruby/simple_features/ewkb_parser.rb +216 -0
  15. data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
  16. data/lib/geo_ruby/simple_features/geometry.rb +228 -0
  17. data/lib/geo_ruby/simple_features/geometry_collection.rb +136 -0
  18. data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
  19. data/lib/geo_ruby/simple_features/georss_parser.rb +135 -0
  20. data/lib/geo_ruby/simple_features/helper.rb +18 -0
  21. data/lib/geo_ruby/simple_features/line_string.rb +206 -0
  22. data/lib/geo_ruby/simple_features/linear_ring.rb +12 -0
  23. data/lib/geo_ruby/simple_features/multi_line_string.rb +51 -0
  24. data/lib/geo_ruby/simple_features/multi_point.rb +46 -0
  25. data/lib/geo_ruby/simple_features/multi_polygon.rb +52 -0
  26. data/lib/geo_ruby/simple_features/point.rb +364 -0
  27. data/lib/geo_ruby/simple_features/polygon.rb +157 -0
  28. data/ppe-georuby.gemspec +133 -0
  29. data/script/console +10 -0
  30. data/script/destroy +14 -0
  31. data/script/generate +14 -0
  32. data/script/txt2html +82 -0
  33. data/spec/data/gpx/fells_loop.gpx +1077 -0
  34. data/spec/data/gpx/long.gpx +1642 -0
  35. data/spec/data/gpx/long.kml +31590 -0
  36. data/spec/data/gpx/long.nmea +2220 -0
  37. data/spec/data/gpx/short.gpx +13634 -0
  38. data/spec/data/gpx/short.kml +130 -0
  39. data/spec/data/gpx/tracktreks.gpx +706 -0
  40. data/spec/data/multipoint.dbf +0 -0
  41. data/spec/data/multipoint.shp +0 -0
  42. data/spec/data/multipoint.shx +0 -0
  43. data/spec/data/point.dbf +0 -0
  44. data/spec/data/point.shp +0 -0
  45. data/spec/data/point.shx +0 -0
  46. data/spec/data/polygon.dbf +0 -0
  47. data/spec/data/polygon.shp +0 -0
  48. data/spec/data/polygon.shx +0 -0
  49. data/spec/data/polyline.dbf +0 -0
  50. data/spec/data/polyline.shp +0 -0
  51. data/spec/data/polyline.shx +0 -0
  52. data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
  53. data/spec/geo_ruby/shp4r/shp_spec.rb +240 -0
  54. data/spec/geo_ruby/simple_features/envelope_spec.rb +45 -0
  55. data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
  56. data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
  57. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
  58. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
  59. data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
  60. data/spec/geo_ruby/simple_features/georss_parser_spec.rb +218 -0
  61. data/spec/geo_ruby/simple_features/line_string_spec.rb +245 -0
  62. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +14 -0
  63. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
  64. data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
  65. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
  66. data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
  67. data/spec/geo_ruby/simple_features/polygon_spec.rb +108 -0
  68. data/spec/geo_ruby_spec.rb +27 -0
  69. data/spec/spec.opts +6 -0
  70. data/spec/spec_helper.rb +65 -0
  71. metadata +162 -0
@@ -0,0 +1,18 @@
1
+ module GeoRuby
2
+ module SimpleFeatures
3
+ #indicates the presence of Z coordinates in EWKB strings
4
+ Z_MASK=0x80000000
5
+ #indicates the presence of M coordinates in EWKB strings.
6
+ M_MASK=0x40000000
7
+ #indicate the presence of a SRID in EWKB strings.
8
+ SRID_MASK=0x20000000
9
+ #GeoRSS namespace
10
+ GEORSS_NS = "http://www.georss.org/georss"
11
+ #GML Namespace
12
+ GML_NS = "http://www.opengis.net/gml"
13
+ #W3CGeo Namespace
14
+ W3CGEO_NS = "http://www.w3.org/2003/01/geo/wgs84_pos#"
15
+ #KML Namespace
16
+ KML_NS = "http://earth.google.com/kml/2.1"
17
+ end
18
+ end
@@ -0,0 +1,206 @@
1
+ require "geo_ruby/simple_features/geometry"
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ #Represents a line string as an array of points (see Point).
6
+ class LineString < Geometry
7
+ #the list of points forming the line string
8
+ attr_reader :points
9
+
10
+ def initialize(srid= DEFAULT_SRID,with_z=false,with_m=false)
11
+ super(srid,with_z,with_m)
12
+ @points=[]
13
+ end
14
+
15
+ #Delegate the unknown methods to the points array
16
+ def method_missing(method_name,*args,&b)
17
+ @points.send(method_name,*args,&b)
18
+ end
19
+
20
+ #tests if the line string is closed
21
+ def is_closed
22
+ #a bit naive...
23
+ @points.first == @points.last
24
+ end
25
+ alias :closed? :is_closed
26
+
27
+ #Bounding box in 2D/3D. Returns an array of 2 points
28
+ def bounding_box
29
+ max_x, min_x, max_y, min_y = -Float::MAX, Float::MAX, -Float::MAX, Float::MAX
30
+ if(with_z)
31
+ max_z, min_z = -Float::MAX,Float::MAX
32
+ each do |point|
33
+ max_y = point.y if point.y > max_y
34
+ min_y = point.y if point.y < min_y
35
+ max_x = point.x if point.x > max_x
36
+ min_x = point.x if point.x < min_x
37
+ max_z = point.z if point.z > max_z
38
+ min_z = point.z if point.z < min_z
39
+ end
40
+ [Point.from_x_y_z(min_x,min_y,min_z),Point.from_x_y_z(max_x,max_y,max_z)]
41
+ else
42
+ each do |point|
43
+ max_y = point.y if point.y > max_y
44
+ min_y = point.y if point.y < min_y
45
+ max_x = point.x if point.x > max_x
46
+ min_x = point.x if point.x < min_x
47
+ end
48
+ [Point.from_x_y(min_x,min_y),Point.from_x_y(max_x,max_y)]
49
+ end
50
+ end
51
+
52
+ def m_range
53
+ if with_m
54
+ max_m, min_m = -Float::MAX, Float::MAX
55
+ each do |point|
56
+ max_m = point.m if point.m > max_m
57
+ min_m = point.m if point.m < min_m
58
+ end
59
+ [min_m,max_m]
60
+ else
61
+ [0,0]
62
+ end
63
+ end
64
+
65
+ #call to native Geo intersect, return true or false
66
+ def intersects?(other_line_string)
67
+
68
+ end
69
+
70
+ def spherical_distance
71
+ total = 0
72
+ @points.each_with_index do |p,i|
73
+ total += p.spherical_distance(@points[i+1]) if @points[i+1]
74
+ end
75
+ total
76
+ end
77
+
78
+ def euclidian_distance
79
+ total = 0
80
+ @points.each_with_index do |p,i|
81
+ total += p.euclidian_distance(@points[i+1]) if @points[i+1]
82
+ end
83
+ total
84
+ end
85
+
86
+ #Tests the equality of line strings
87
+ def ==(other_line_string)
88
+ if(other_line_string.class != self.class or
89
+ other_line_string.length != self.length)
90
+ false
91
+ else
92
+ index=0
93
+ while index<length
94
+ return false if self[index] != other_line_string[index]
95
+ index+=1
96
+ end
97
+ true
98
+ end
99
+ end
100
+
101
+ #Binary representation of a line string
102
+ def binary_representation(allow_z=true,allow_m=true) #:nodoc:
103
+ rep = [length].pack("V")
104
+ each {|point| rep << point.binary_representation(allow_z,allow_m) }
105
+ rep
106
+ end
107
+
108
+ #WKB geometry type
109
+ def binary_geometry_type #:nodoc:
110
+ 2
111
+ end
112
+
113
+ #Text representation of a line string
114
+ def text_representation(allow_z=true,allow_m=true) #:nodoc:
115
+ @points.collect{|point| point.text_representation(allow_z,allow_m) }.join(",")
116
+ end
117
+ #WKT geometry type
118
+ def text_geometry_type #:nodoc:
119
+ "LINESTRING"
120
+ end
121
+
122
+ #georss simple representation
123
+ def georss_simple_representation(options) #:nodoc:
124
+ georss_ns = options[:georss_ns] || "georss"
125
+ geom_attr = options[:geom_attr]
126
+ "<#{georss_ns}:line#{geom_attr}>" + georss_poslist + "</#{georss_ns}:line>\n"
127
+ end
128
+ #georss w3c representation : outputs the first point of the line
129
+ def georss_w3cgeo_representation(options) #:nodoc:
130
+ w3cgeo_ns = options[:w3cgeo_ns] || "geo"
131
+ "<#{w3cgeo_ns}:lat>#{self[0].y}</#{w3cgeo_ns}:lat>\n<#{w3cgeo_ns}:long>#{self[0].x}</#{w3cgeo_ns}:long>\n"
132
+ end
133
+ #georss gml representation
134
+ def georss_gml_representation(options) #:nodoc:
135
+ georss_ns = options[:georss_ns] || "georss"
136
+ gml_ns = options[:gml_ns] || "gml"
137
+
138
+ result = "<#{georss_ns}:where>\n<#{gml_ns}:LineString>\n<#{gml_ns}:posList>\n"
139
+ result += georss_poslist
140
+ result += "\n</#{gml_ns}:posList>\n</#{gml_ns}:LineString>\n</#{georss_ns}:where>\n"
141
+ end
142
+
143
+ def georss_poslist #:nodoc:
144
+ map {|point| "#{point.y} #{point.x}"}.join(" ")
145
+ end
146
+
147
+ #outputs the geometry in kml format : options are <tt>:id</tt>, <tt>:tesselate</tt>, <tt>:extrude</tt>,
148
+ #<tt>:altitude_mode</tt>. If the altitude_mode option is not present, the Z (if present) will not be output (since
149
+ #it won't be used by GE anyway: clampToGround is the default)
150
+ def kml_representation(options = {}) #:nodoc:
151
+ result = "<LineString#{options[:id_attr]}>\n"
152
+ result += options[:geom_data] if options[:geom_data]
153
+ result += "<coordinates>"
154
+ result += kml_poslist(options)
155
+ result += "</coordinates>\n"
156
+ result += "</LineString>\n"
157
+ end
158
+
159
+ def kml_poslist(options) #:nodoc:
160
+ pos_list = if options[:allow_z]
161
+ map {|point| "#{point.x},#{point.y},#{options[:fixed_z] || point.z || 0}" }
162
+ else
163
+ map {|point| "#{point.x},#{point.y}" }
164
+ end
165
+ pos_list.reverse! if(options[:reverse])
166
+ pos_list.join(" ")
167
+ end
168
+
169
+ # Simplify linestring (Douglas Peucker Algorithm)
170
+ # http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
171
+ def simplify(epsilon=1)
172
+ LineString.from_points(do_simplify(@points, epsilon))
173
+ end
174
+
175
+ def do_simplify(list, epsilon)
176
+ index = dmax = 0
177
+ 2.upto(list.length - 1) do |i|
178
+ d = list[i].orthogonal_distance(list[0], list[-1])
179
+ index, dmax = i, d if d > dmax
180
+ end
181
+
182
+ if dmax >= epsilon
183
+ res1 = do_simplify(list[0..index], epsilon)
184
+ res2 = do_simplify(list[index..-1], epsilon)
185
+ res1[0..-2] + res2[0..-1]
186
+ else
187
+ [list[0], list[-1]]
188
+ end
189
+ end
190
+
191
+ #Creates a new line string. Accept an array of points as argument
192
+ def self.from_points(points,srid=DEFAULT_SRID,with_z=false,with_m=false)
193
+ line_string = new(srid,with_z,with_m)
194
+ line_string.concat(points)
195
+ line_string
196
+ end
197
+
198
+ #Creates a new line string. Accept a sequence of points as argument : ((x,y)...(x,y))
199
+ def self.from_coordinates(points,srid=DEFAULT_SRID,with_z=false,with_m=false)
200
+ line_string = new(srid,with_z,with_m)
201
+ line_string.concat( points.map {|p| Point.from_coordinates(p,srid,with_z,with_m) } )
202
+ line_string
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,12 @@
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). Currently, no check is performed to verify if the linear ring is really closed.
6
+ class LinearRing < LineString
7
+ def initialize(srid= DEFAULT_SRID,with_z=false,with_m=false)
8
+ super(srid,with_z,with_m)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ require 'geo_ruby/simple_features/geometry_collection'
2
+
3
+ module GeoRuby
4
+
5
+ module SimpleFeatures
6
+
7
+ #Represents a group of line strings (see LineString).
8
+ class MultiLineString < GeometryCollection
9
+
10
+ def initialize(srid = DEFAULT_SRID,with_z=false,with_m=false)
11
+ super(srid)
12
+ end
13
+
14
+ def binary_geometry_type #:nodoc:
15
+ 5
16
+ end
17
+
18
+ def points
19
+ geometries.map(&:points).flatten
20
+ end
21
+
22
+ #Text representation of a multi line string
23
+ def text_representation(allow_z=true,allow_m=true) #:nodoc:
24
+ @geometries.collect{|line_string| "(" + line_string.text_representation(allow_z,allow_m) + ")" }.join(",")
25
+ end
26
+
27
+ #WKT geometry type
28
+ def text_geometry_type #:nodoc:
29
+ "MULTILINESTRING"
30
+ end
31
+
32
+ def to_line_string(join = true)
33
+ LineString.from_points(points)
34
+ end
35
+
36
+ #Creates a new multi line string from an array of line strings
37
+ def self.from_line_strings(line_strings,srid=DEFAULT_SRID,with_z=false,with_m=false)
38
+ multi_line_string = new(srid,with_z,with_m)
39
+ multi_line_string.concat(line_strings)
40
+ multi_line_string
41
+ end
42
+
43
+ #Creates a new multi line string from sequences of points : (((x,y)...(x,y)),((x,y)...(x,y)))
44
+ def self.from_coordinates(point_sequences,srid=DEFAULT_SRID,with_z=false,with_m=false)
45
+ multi_line_string = new(srid,with_z,with_m)
46
+ multi_line_string.concat(point_sequences.collect {|points| LineString.from_coordinates(points,srid,with_z,with_m) })
47
+ multi_line_string
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,46 @@
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
+
8
+ def initialize(srid= DEFAULT_SRID,with_z=false,with_m=false)
9
+ super(srid,with_z,with_m)
10
+ end
11
+
12
+ def binary_geometry_type #:nodoc:
13
+ 4
14
+ end
15
+
16
+ def points
17
+ @geometries
18
+ end
19
+
20
+ #Text representation of a MultiPoint
21
+ def text_representation(allow_z=true,allow_m=true) #:nodoc:
22
+ "(" + @geometries.collect{|point| point.text_representation(allow_z,allow_m)}.join("),(") + ")"
23
+ end
24
+
25
+ #WKT geoemtry type
26
+ def text_geometry_type #:nodoc:
27
+ "MULTIPOINT"
28
+ end
29
+
30
+ #Creates a new multi point from an array of points
31
+ def self.from_points(points,srid= DEFAULT_SRID,with_z=false,with_m=false)
32
+ multi_point= new(srid,with_z,with_m)
33
+ multi_point.concat(points)
34
+ multi_point
35
+ end
36
+
37
+ #Creates a new multi point from a list of point coordinates : ((x,y)...(x,y))
38
+ def self.from_coordinates(points,srid= DEFAULT_SRID,with_z=false,with_m=false)
39
+ multi_point= new(srid,with_z,with_m)
40
+ multi_point.concat(points.collect {|point| Point.from_coordinates(point,srid,with_z,with_m)})
41
+ multi_point
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ require 'geo_ruby/simple_features/geometry_collection'
2
+
3
+ module GeoRuby
4
+
5
+ module SimpleFeatures
6
+
7
+ #Represents a group of polygons (see Polygon).
8
+ class MultiPolygon < GeometryCollection
9
+
10
+ def initialize(srid = DEFAULT_SRID,with_z=false,with_m=false)
11
+ super(srid)
12
+ end
13
+
14
+ def binary_geometry_type #:nodoc:
15
+ 6
16
+ end
17
+
18
+ def points
19
+ @points ||= geometries.inject([]) do |arr, r|
20
+ arr.concat(r.rings.map(&:points).flatten)
21
+ end
22
+ end
23
+
24
+ #Text representation of a MultiPolygon
25
+ def text_representation(allow_z=true,allow_m=true) #:nodoc:
26
+ @geometries.map {|polygon| "(" + polygon.text_representation(allow_z,allow_m) + ")"}.join(",")
27
+ end
28
+
29
+ #WKT geometry type
30
+ def text_geometry_type #:nodoc:
31
+ "MULTIPOLYGON"
32
+ end
33
+
34
+ #Creates a multi polygon from an array of polygons
35
+ def self.from_polygons(polygons,srid=DEFAULT_SRID,with_z=false,with_m=false)
36
+ multi_polygon = new(srid,with_z,with_m)
37
+ multi_polygon.concat(polygons)
38
+ multi_polygon
39
+ end
40
+
41
+ #Creates a multi polygon from sequences of points : ((((x,y)...(x,y)),((x,y)...(x,y)),((x,y)...(x,y)))
42
+ def self.from_coordinates(point_sequence_sequences,srid= DEFAULT_SRID,with_z=false,with_m=false)
43
+ multi_polygon = new(srid,with_z,with_m)
44
+ multi_polygon.concat( point_sequence_sequences.collect {|point_sequences| Polygon.from_coordinates(point_sequences,srid,with_z,with_m) } )
45
+ multi_polygon
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,364 @@
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 = 0.0174532925199433
9
+ HALFPI = 1.5707963267948966
10
+ attr_accessor :x,:y,:z,:m
11
+ attr_reader :r, :t # radium and theta
12
+
13
+ #if you prefer calling the coordinates lat and lon (or lng, for GeoKit compatibility)
14
+ alias :lon :x
15
+ alias :lng :x
16
+ alias :lat :y
17
+ alias :rad :r
18
+ alias :tet :t
19
+ alias :tetha :t
20
+
21
+ def initialize(srid=DEFAULT_SRID,with_z=false,with_m=false)
22
+ super(srid,with_z,with_m)
23
+ @x = @y = 0.0
24
+ @z=0.0 #default value : meaningful if with_z
25
+ @m=0.0 #default value : meaningful if with_m
26
+ end
27
+ #sets all coordinates in one call. Use the +m+ accessor to set the m.
28
+ def set_x_y_z(x,y,z)
29
+ @x=x
30
+ @y=y
31
+ @z=z
32
+ self
33
+ end
34
+ alias :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
39
+ @y=y
40
+ self
41
+ end
42
+ alias :set_lon_lat :set_x_y
43
+
44
+ #Return the distance between the 2D points (ie taking care only of the x and y coordinates), assuming
45
+ #the points are in projected coordinates. Euclidian distance in whatever unit the x and y ordinates are.
46
+ def euclidian_distance(point)
47
+ Math.sqrt((point.x - x)**2 + (point.y - y)**2)
48
+ end
49
+
50
+ # Spherical distance in meters, using 'Haversine' formula.
51
+ # with a radius of 6471000m
52
+ # Assumes x is the lon and y the lat, in degrees (Changed in version 1.1).
53
+ # The user has to make sure using this distance makes sense (ie she should be in latlon coordinates)
54
+ def spherical_distance(point,r=6370997.0)
55
+ dlat = (point.lat - lat) * DEG2RAD / 2
56
+ dlon = (point.lon - lon) * DEG2RAD / 2
57
+
58
+ a = Math.sin(dlat)**2 + Math.cos(lat * DEG2RAD) * Math.cos(point.lat * DEG2RAD) * Math.sin(dlon)**2
59
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a))
60
+ r * c
61
+ end
62
+
63
+ #Ellipsoidal distance in m using Vincenty's formula. Lifted entirely from Chris Veness's code at http://www.movable-type.co.uk/scripts/LatLongVincenty.html and adapted for Ruby. Assumes the x and y are the lon and lat in degrees.
64
+ #a is the semi-major axis (equatorial radius) of the ellipsoid
65
+ #b is the semi-minor axis (polar radius) of the ellipsoid
66
+ #Their values by default are set to the ones of the WGS84 ellipsoid
67
+ def ellipsoidal_distance(point, a = 6378137.0, b = 6356752.3142)
68
+ f = (a-b) / a
69
+ l = (point.lon - lon) * DEG2RAD
70
+
71
+ u1 = Math.atan((1-f) * Math.tan(lat * DEG2RAD ))
72
+ u2 = Math.atan((1-f) * Math.tan(point.lat * DEG2RAD))
73
+ sinU1 = Math.sin(u1)
74
+ cosU1 = Math.cos(u1)
75
+ sinU2 = Math.sin(u2)
76
+ cosU2 = Math.cos(u2)
77
+
78
+ lambda = l
79
+ lambdaP = 2 * Math::PI
80
+ iterLimit = 20
81
+
82
+ while (lambda-lambdaP).abs > 1e-12 && --iterLimit>0
83
+ sinLambda = Math.sin(lambda)
84
+ cosLambda = Math.cos(lambda)
85
+ sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda))
86
+
87
+ return 0 if sinSigma == 0 #coincident points
88
+
89
+ cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda
90
+ sigma = Math.atan2(sinSigma, cosSigma)
91
+ sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma
92
+ cosSqAlpha = 1 - sinAlpha*sinAlpha
93
+ cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha
94
+
95
+ cos2SigmaM = 0 if (cos2SigmaM.nan?) #equatorial line: cosSqAlpha=0
96
+
97
+ c = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha))
98
+ lambdaP = lambda
99
+ lambda = l + (1-c) * f * sinAlpha * (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)))
100
+ end
101
+ return NaN if iterLimit==0 #formula failed to converge
102
+
103
+ uSq = cosSqAlpha * (a*a - b*b) / (b*b)
104
+ a_bis = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)))
105
+ b_bis = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)))
106
+ 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)))
107
+
108
+ b*a_bis*(sigma-deltaSigma)
109
+ end
110
+
111
+ # Orthogonal Distance
112
+ # Based http://www.allegro.cc/forums/thread/589720
113
+ def orthogonal_distance(line, tail = nil)
114
+ head, tail = tail ? [line, tail] : [line[0], line[-1]]
115
+ a, b = @x - head.x, @y - head.y
116
+ c, d = tail.x - head.x, tail.y - head.y
117
+
118
+ dot = a * c + b * d
119
+ len = c * c + d * d
120
+ res = dot / len
121
+
122
+ xx, yy = if res < 0
123
+ [head.x, head.y]
124
+ elsif res > 1
125
+ [tail.x, tail.y]
126
+ else
127
+ [head.x + res * c, head.y + res * d]
128
+ end
129
+ # todo benchmark if worth creating an instance
130
+ # euclidian_distance(Point.from_x_y(xx, yy))
131
+ Math.sqrt((@x - xx) ** 2 + (@y - yy) ** 2)
132
+ end
133
+
134
+ #Bearing from a point to another, in degrees.
135
+ def bearing_to(other)
136
+ return 0 if self == other
137
+ a,b = other.x - self.x, other.y - self.y
138
+ res = Math.acos(b / Math.sqrt(a*a+b*b)) / Math::PI * 180;
139
+ a < 0 ? 360 - res : res
140
+ end
141
+
142
+ #Bearing from a point to another as symbols. (:n, :s, :sw, :ne...)
143
+ def bearing_text(other)
144
+ case bearing_to(other)
145
+ when 1..22 then :n
146
+ when 23..66 then :ne
147
+ when 67..112 then :e
148
+ when 113..146 then :se
149
+ when 147..202 then :s
150
+ when 203..246 then :sw
151
+ when 247..292 then :w
152
+ when 293..336 then :nw
153
+ when 337..360 then :n
154
+ else nil
155
+ end
156
+ end
157
+
158
+ #Bounding box in 2D/3D. Returns an array of 2 points
159
+ def bounding_box
160
+ unless with_z
161
+ [Point.from_x_y(@x,@y),Point.from_x_y(@x,@y)]
162
+ else
163
+ [Point.from_x_y_z(@x,@y,@z),Point.from_x_y_z(@x,@y,@z)]
164
+ end
165
+ end
166
+
167
+ def m_range
168
+ [@m,@m]
169
+ end
170
+
171
+ #tests the equality of the position of points + m
172
+ def ==(other)
173
+ return false unless other.kind_of?(Point)
174
+ @x == other.x and @y == other.y and @z == other.z and @m == other.m
175
+ end
176
+
177
+ #binary representation of a point. It lacks some headers to be a valid EWKB representation.
178
+ def binary_representation(allow_z=true,allow_m=true) #:nodoc:
179
+ bin_rep = [@x,@y].pack("EE")
180
+ bin_rep += [@z].pack("E") if @with_z and allow_z #Default value so no crash
181
+ bin_rep += [@m].pack("E") if @with_m and allow_m #idem
182
+ bin_rep
183
+ end
184
+
185
+ #WKB geometry type of a point
186
+ def binary_geometry_type#:nodoc:
187
+ 1
188
+ end
189
+
190
+ #text representation of a point
191
+ def text_representation(allow_z=true,allow_m=true) #:nodoc:
192
+ tex_rep = "#{@x} #{@y}"
193
+ tex_rep += " #{@z}" if @with_z and allow_z
194
+ tex_rep += " #{@m}" if @with_m and allow_m
195
+ tex_rep
196
+ end
197
+
198
+ #WKT geometry type of a point
199
+ def text_geometry_type #:nodoc:
200
+ "POINT"
201
+ end
202
+
203
+ #georss simple representation
204
+ def georss_simple_representation(options) #:nodoc:
205
+ georss_ns = options[:georss_ns] || "georss"
206
+ geom_attr = options[:geom_attr]
207
+ "<#{georss_ns}:point#{geom_attr}>#{y} #{x}</#{georss_ns}:point>\n"
208
+ end
209
+
210
+ #georss w3c representation
211
+ def georss_w3cgeo_representation(options) #:nodoc:
212
+ w3cgeo_ns = options[:w3cgeo_ns] || "geo"
213
+ "<#{w3cgeo_ns}:lat>#{y}</#{w3cgeo_ns}:lat>\n<#{w3cgeo_ns}:long>#{x}</#{w3cgeo_ns}:long>\n"
214
+ end
215
+
216
+ #georss gml representation
217
+ def georss_gml_representation(options) #:nodoc:
218
+ georss_ns = options[:georss_ns] || "georss"
219
+ gml_ns = options[:gml_ns] || "gml"
220
+ result = "<#{georss_ns}:where>\n<#{gml_ns}:Point>\n<#{gml_ns}:pos>"
221
+ result += "#{y} #{x}"
222
+ result += "</#{gml_ns}:pos>\n</#{gml_ns}:Point>\n</#{georss_ns}:where>\n"
223
+ end
224
+
225
+ #outputs the geometry in kml format : options are <tt>:id</tt>, <tt>:tesselate</tt>, <tt>:extrude</tt>,
226
+ #<tt>:altitude_mode</tt>. If the altitude_mode option is not present, the Z (if present) will not be output (since
227
+ #it won't be used by GE anyway: clampToGround is the default)
228
+ def kml_representation(options = {}) #:nodoc:
229
+ result = "<Point#{options[:id_attr]}>\n"
230
+ result += options[:geom_data] if options[:geom_data]
231
+ result += "<coordinates>#{x},#{y}"
232
+ result += ",#{options[:fixed_z] || z ||0}" if options[:allow_z]
233
+ result += "</coordinates>\n"
234
+ result += "</Point>\n"
235
+ end
236
+
237
+ # Outputs the geometry in coordinates format:
238
+ # 47°52′48″, -20°06′00″
239
+ def as_latlong(opts = { })
240
+ val = []
241
+ [x,y].each_with_index do |l,i|
242
+ deg = l.to_i.abs
243
+ min = (60 * (l.abs - deg)).to_i
244
+ labs = (l * 1000000).abs / 1000000
245
+ sec = ((((labs - labs.to_i) * 60) - ((labs - labs.to_i) * 60).to_i) * 100000) * 60 / 100000
246
+ str = opts[:full] ? "%.i°%.2i′%05.2f″" : "%.i°%.2i′%02.0f″"
247
+ if opts[:coord]
248
+ out = str % [deg,min,sec]
249
+ if i == 0
250
+ out += l > 0 ? "N" : "S"
251
+ else
252
+ out += l > 0 ? "E" : "W"
253
+ end
254
+ val << out
255
+ else
256
+ val << str % [l.to_i, min, sec]
257
+ end
258
+ end
259
+ val.join(", ")
260
+ end
261
+
262
+ # Polar stuff
263
+ #
264
+ # http://www.engineeringtoolbox.com/converting-cartesian-polar-coordinates-d_1347.html
265
+ # http://rcoordinate.rubyforge.org/svn/point.rb
266
+ # outputs radium
267
+ def r; Math.sqrt(@x**2 + @y**2); end
268
+
269
+ # outputs theta
270
+ def theta_rad
271
+ if @x.zero?
272
+ @y < 0 ? 3 * HALFPI : HALFPI
273
+ else
274
+ th = Math.atan(@y/@x)
275
+ th += 2 * Math::PI if r > 0
276
+ end
277
+ end
278
+
279
+ # outputs theta in degrees
280
+ def theta_deg; theta_rad / DEG2RAD; end
281
+
282
+ # outputs an array containing polar distance and theta
283
+ def as_polar; [r,t]; end
284
+
285
+ # invert signal of all coordinates
286
+ def -@
287
+ set_x_y_z(-@x, -@y, -@z)
288
+ end
289
+
290
+ #creates a point from an array of coordinates
291
+ def self.from_coordinates(coords,srid=DEFAULT_SRID,with_z=false,with_m=false)
292
+ if ! (with_z or with_m)
293
+ from_x_y(coords[0],coords[1],srid)
294
+ elsif with_z and with_m
295
+ from_x_y_z_m(coords[0],coords[1],coords[2],coords[3],srid)
296
+ elsif with_z
297
+ from_x_y_z(coords[0],coords[1],coords[2],srid)
298
+ else
299
+ from_x_y_m(coords[0],coords[1],coords[2],srid)
300
+ end
301
+ end
302
+
303
+ #creates a point from the X and Y coordinates
304
+ def self.from_x_y(x,y,srid=DEFAULT_SRID)
305
+ point= new(srid)
306
+ point.set_x_y(x,y)
307
+ end
308
+
309
+ #creates a point from the X, Y and Z coordinates
310
+ def self.from_x_y_z(x,y,z,srid=DEFAULT_SRID)
311
+ point= new(srid,true)
312
+ point.set_x_y_z(x,y,z)
313
+ end
314
+
315
+ #creates a point from the X, Y and M coordinates
316
+ def self.from_x_y_m(x,y,m,srid=DEFAULT_SRID)
317
+ point= new(srid,false,true)
318
+ point.m=m
319
+ point.set_x_y(x,y)
320
+ end
321
+
322
+ #creates a point from the X, Y, Z and M coordinates
323
+ def self.from_x_y_z_m(x,y,z,m,srid=DEFAULT_SRID)
324
+ point= new(srid,true,true)
325
+ point.m=m
326
+ point.set_x_y_z(x,y,z)
327
+ end
328
+
329
+ #creates a point using polar coordinates
330
+ #r and theta(degrees)
331
+ def self.from_r_t(r,t,srid=DEFAULT_SRID)
332
+ t *= DEG2RAD
333
+ x = r * Math.cos(t)
334
+ y = r * Math.sin(t)
335
+ point= new(srid)
336
+ point.set_x_y(x,y)
337
+ end
338
+
339
+ #creates a point using coordinates like 22`34 23.45N
340
+ def self.from_latlong(lat,lon,srid=DEFAULT_SRID)
341
+ p = [lat,lon].map do |l|
342
+ sig, deg, min, sec, cen = l.scan(/(-)?(\d{1,2})\D*(\d{2})\D*(\d{2})(\D*(\d{1,3}))?/).flatten
343
+ sig = true if l =~ /W|S/
344
+ dec = deg.to_i + (min.to_i * 60 + "#{sec}#{cen}".to_f) / 3600
345
+ sig ? dec * -1 : dec
346
+ end
347
+ point= new(srid)
348
+ point.set_x_y(p[0],p[1])
349
+ end
350
+
351
+ #aliasing the constructors in case you want to use lat/lon instead of y/x
352
+ class << self
353
+ alias :xy :from_x_y
354
+ alias :xyz :from_x_y_z
355
+ alias :from_lon_lat_z :from_x_y_z
356
+ alias :from_lon_lat :from_x_y
357
+ alias :from_lon_lat_z :from_x_y_z
358
+ alias :from_lon_lat_m :from_x_y_m
359
+ alias :from_lon_lat_z_m :from_x_y_z_m
360
+ alias :from_rad_tet :from_r_t
361
+ end
362
+ end
363
+ end
364
+ end