ppe-georuby 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
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