georuby 1.9.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +29 -0
  3. data/History.txt +4 -0
  4. data/LICENSE +21 -0
  5. data/README.rdoc +184 -0
  6. data/Rakefile +48 -0
  7. data/VERSION +1 -0
  8. data/georuby.gemspec +128 -0
  9. data/lib/geo_ruby.rb +23 -0
  10. data/lib/geo_ruby/geojson.rb +129 -0
  11. data/lib/geo_ruby/georss.rb +133 -0
  12. data/lib/geo_ruby/gpx.rb +1 -0
  13. data/lib/geo_ruby/gpx4r/gpx.rb +118 -0
  14. data/lib/geo_ruby/shp.rb +1 -0
  15. data/lib/geo_ruby/shp4r/dbf.rb +42 -0
  16. data/lib/geo_ruby/shp4r/shp.rb +718 -0
  17. data/lib/geo_ruby/simple_features/envelope.rb +167 -0
  18. data/lib/geo_ruby/simple_features/ewkb_parser.rb +218 -0
  19. data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
  20. data/lib/geo_ruby/simple_features/geometry.rb +236 -0
  21. data/lib/geo_ruby/simple_features/geometry_collection.rb +144 -0
  22. data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
  23. data/lib/geo_ruby/simple_features/helper.rb +18 -0
  24. data/lib/geo_ruby/simple_features/line_string.rb +228 -0
  25. data/lib/geo_ruby/simple_features/linear_ring.rb +34 -0
  26. data/lib/geo_ruby/simple_features/multi_line_string.rb +63 -0
  27. data/lib/geo_ruby/simple_features/multi_point.rb +58 -0
  28. data/lib/geo_ruby/simple_features/multi_polygon.rb +64 -0
  29. data/lib/geo_ruby/simple_features/point.rb +381 -0
  30. data/lib/geo_ruby/simple_features/polygon.rb +175 -0
  31. data/nofxx-georuby.gemspec +149 -0
  32. data/spec/data/geojson/feature_collection.json +34 -0
  33. data/spec/data/georss/atom.xml +21 -0
  34. data/spec/data/georss/gml.xml +40 -0
  35. data/spec/data/georss/w3c.xml +22 -0
  36. data/spec/data/gpx/fells_loop.gpx +1077 -0
  37. data/spec/data/gpx/long.gpx +1642 -0
  38. data/spec/data/gpx/long.kml +31590 -0
  39. data/spec/data/gpx/long.nmea +2220 -0
  40. data/spec/data/gpx/short.gpx +13634 -0
  41. data/spec/data/gpx/short.kml +130 -0
  42. data/spec/data/gpx/tracktreks.gpx +706 -0
  43. data/spec/data/multipoint.dbf +0 -0
  44. data/spec/data/multipoint.shp +0 -0
  45. data/spec/data/multipoint.shx +0 -0
  46. data/spec/data/point.dbf +0 -0
  47. data/spec/data/point.shp +0 -0
  48. data/spec/data/point.shx +0 -0
  49. data/spec/data/polygon.dbf +0 -0
  50. data/spec/data/polygon.shp +0 -0
  51. data/spec/data/polygon.shx +0 -0
  52. data/spec/data/polyline.dbf +0 -0
  53. data/spec/data/polyline.shp +0 -0
  54. data/spec/data/polyline.shx +0 -0
  55. data/spec/geo_ruby/geojson_spec.rb +147 -0
  56. data/spec/geo_ruby/georss.rb +218 -0
  57. data/spec/geo_ruby/georss_spec.rb +14 -0
  58. data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
  59. data/spec/geo_ruby/shp4r/shp_spec.rb +239 -0
  60. data/spec/geo_ruby/simple_features/envelope_spec.rb +47 -0
  61. data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
  62. data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
  63. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
  64. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
  65. data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
  66. data/spec/geo_ruby/simple_features/line_string_spec.rb +259 -0
  67. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +24 -0
  68. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
  69. data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
  70. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
  71. data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
  72. data/spec/geo_ruby/simple_features/polygon_spec.rb +122 -0
  73. data/spec/geo_ruby_spec.rb +27 -0
  74. data/spec/spec_helper.rb +73 -0
  75. metadata +228 -0
@@ -0,0 +1,381 @@
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.to_f,@y.to_f].pack("EE")
180
+ bin_rep += [@z.to_f].pack("E") if @with_z and allow_z #Default value so no crash
181
+ bin_rep += [@m.to_f].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
+ # TODO Perhaps should support with_m analogous to from_coordinates?
291
+ def to_coordinates
292
+ if with_z
293
+ [x,y,z]
294
+ else
295
+ [x,y]
296
+ end
297
+ end
298
+
299
+ # simple geojson representation
300
+ # TODO add CRS / SRID support?
301
+ def to_json(options = {})
302
+ {:type => 'Point',
303
+ :coordinates => self.to_coordinates}.to_json(options)
304
+ end
305
+ alias :as_geojson :to_json
306
+
307
+ #creates a point from an array of coordinates
308
+ def self.from_coordinates(coords,srid=DEFAULT_SRID,with_z=false,with_m=false)
309
+ if ! (with_z or with_m)
310
+ from_x_y(coords[0],coords[1],srid)
311
+ elsif with_z and with_m
312
+ from_x_y_z_m(coords[0],coords[1],coords[2],coords[3],srid)
313
+ elsif with_z
314
+ from_x_y_z(coords[0],coords[1],coords[2],srid)
315
+ else
316
+ from_x_y_m(coords[0],coords[1],coords[2],srid)
317
+ end
318
+ end
319
+
320
+ #creates a point from the X and Y coordinates
321
+ def self.from_x_y(x,y,srid=DEFAULT_SRID)
322
+ point= new(srid)
323
+ point.set_x_y(x,y)
324
+ end
325
+
326
+ #creates a point from the X, Y and Z coordinates
327
+ def self.from_x_y_z(x,y,z,srid=DEFAULT_SRID)
328
+ point= new(srid,true)
329
+ point.set_x_y_z(x,y,z)
330
+ end
331
+
332
+ #creates a point from the X, Y and M coordinates
333
+ def self.from_x_y_m(x,y,m,srid=DEFAULT_SRID)
334
+ point= new(srid,false,true)
335
+ point.m=m
336
+ point.set_x_y(x,y)
337
+ end
338
+
339
+ #creates a point from the X, Y, Z and M coordinates
340
+ def self.from_x_y_z_m(x,y,z,m,srid=DEFAULT_SRID)
341
+ point= new(srid,true,true)
342
+ point.m=m
343
+ point.set_x_y_z(x,y,z)
344
+ end
345
+
346
+ #creates a point using polar coordinates
347
+ #r and theta(degrees)
348
+ def self.from_r_t(r,t,srid=DEFAULT_SRID)
349
+ t *= DEG2RAD
350
+ x = r * Math.cos(t)
351
+ y = r * Math.sin(t)
352
+ point= new(srid)
353
+ point.set_x_y(x,y)
354
+ end
355
+
356
+ #creates a point using coordinates like 22`34 23.45N
357
+ def self.from_latlong(lat,lon,srid=DEFAULT_SRID)
358
+ p = [lat,lon].map do |l|
359
+ sig, deg, min, sec, cen = l.scan(/(-)?(\d{1,2})\D*(\d{2})\D*(\d{2})(\D*(\d{1,3}))?/).flatten
360
+ sig = true if l =~ /W|S/
361
+ dec = deg.to_i + (min.to_i * 60 + "#{sec}#{cen}".to_f) / 3600
362
+ sig ? dec * -1 : dec
363
+ end
364
+ point= new(srid)
365
+ point.set_x_y(p[0],p[1])
366
+ end
367
+
368
+ #aliasing the constructors in case you want to use lat/lon instead of y/x
369
+ class << self
370
+ alias :xy :from_x_y
371
+ alias :xyz :from_x_y_z
372
+ alias :from_lon_lat_z :from_x_y_z
373
+ alias :from_lon_lat :from_x_y
374
+ alias :from_lon_lat_z :from_x_y_z
375
+ alias :from_lon_lat_m :from_x_y_m
376
+ alias :from_lon_lat_z_m :from_x_y_z_m
377
+ alias :from_rad_tet :from_r_t
378
+ end
379
+ end
380
+ end
381
+ end
@@ -0,0 +1,175 @@
1
+ require 'geo_ruby/simple_features/geometry'
2
+
3
+ module GeoRuby
4
+
5
+ module SimpleFeatures
6
+
7
+ # Represents a polygon as an array of linear rings (see LinearRing).
8
+ # No check is performed regarding the validity of the geometries forming the polygon.
9
+ class Polygon < Geometry
10
+ #the list of rings forming the polygon
11
+ attr_reader :rings
12
+
13
+ def initialize(srid = DEFAULT_SRID,with_z=false,with_m=false)
14
+ super(srid,with_z,with_m)
15
+ @rings = []
16
+ end
17
+
18
+ #Delegate the unknown methods to the rings array
19
+ def method_missing(method_name,*args,&b)
20
+ @rings.send(method_name,*args,&b)
21
+ end
22
+
23
+ #Bounding box in 2D/3D. Returns an array of 2 points
24
+ def bounding_box
25
+ unless with_z
26
+ @rings[0].bounding_box
27
+ else
28
+ result = @rings[0].bounding_box #valid for x and y
29
+ max_z, min_z = result[1].z, result[0].z
30
+ 1.upto(size - 1) do |index|
31
+ bbox = @rings[index].bounding_box
32
+ sw = bbox[0]
33
+ ne = bbox[1]
34
+ max_z = ne.z if ne.z > max_z
35
+ min_z = sw.z if sw.z < min_z
36
+ end
37
+ result[1].z, result[0].z = max_z, min_z
38
+ result
39
+ end
40
+ end
41
+
42
+ def m_range
43
+ if with_m
44
+ max_m, min_m = -Float::MAX, Float::MAX
45
+ each do |lr|
46
+ lrmr = lr.m_range
47
+ max_m = lrmr[1] if lrmr[1] > max_m
48
+ min_m = lrmr[0] if lrmr[0] < min_m
49
+ end
50
+ [min_m,max_m]
51
+ else
52
+ [0,0]
53
+ end
54
+ end
55
+
56
+ #tests for other equality. The SRID is not taken into account.
57
+ def ==(other_polygon)
58
+ if other_polygon.class != self.class or
59
+ length != other_polygon.length
60
+ false
61
+ else
62
+ index=0
63
+ while index<length
64
+ return false if self[index] != other_polygon[index]
65
+ index+=1
66
+ end
67
+ true
68
+ end
69
+ end
70
+
71
+ #binary representation of a polygon, without the headers neccessary for a valid WKB string
72
+ def binary_representation(allow_z=true,allow_m=true)
73
+ rep = [length].pack("V")
74
+ each {|linear_ring| rep << linear_ring.binary_representation(allow_z,allow_m)}
75
+ rep
76
+ end
77
+
78
+ #WKB geometry type
79
+ def binary_geometry_type
80
+ 3
81
+ end
82
+
83
+ #Text representation of a polygon
84
+ def text_representation(allow_z=true,allow_m=true)
85
+ @rings.collect{|line_string| "(" + line_string.text_representation(allow_z,allow_m) + ")" }.join(",")
86
+ end
87
+
88
+ #WKT geometry type
89
+ def text_geometry_type
90
+ "POLYGON"
91
+ end
92
+
93
+ # Contains a point?
94
+ def contains_point?(point)
95
+ !@rings.select { |lr| lr.contains_point? point }.empty?
96
+ end
97
+
98
+ #georss simple representation : outputs only the outer ring
99
+ def georss_simple_representation(options)
100
+ georss_ns = options[:georss_ns] || "georss"
101
+ geom_attr = options[:geom_attr]
102
+ "<#{georss_ns}:polygon#{geom_attr}>" + self[0].georss_poslist + "</#{georss_ns}:polygon>\n"
103
+ end
104
+
105
+ #georss w3c representation : outputs the first point of the outer ring
106
+ def georss_w3cgeo_representation(options)
107
+ w3cgeo_ns = options[:w3cgeo_ns] || "geo"
108
+
109
+ "<#{w3cgeo_ns}:lat>#{self[0][0].y}</#{w3cgeo_ns}:lat>\n<#{w3cgeo_ns}:long>#{self[0][0].x}</#{w3cgeo_ns}:long>\n"
110
+ end
111
+ #georss gml representation
112
+ def georss_gml_representation(options)
113
+ georss_ns = options[:georss_ns] || "georss"
114
+ gml_ns = options[:gml_ns] || "gml"
115
+
116
+ result = "<#{georss_ns}:where>\n<#{gml_ns}:Polygon>\n<#{gml_ns}:exterior>\n<#{gml_ns}:LinearRing>\n<#{gml_ns}:posList>\n" + self[0].georss_poslist + "\n</#{gml_ns}:posList>\n</#{gml_ns}:LinearRing>\n</#{gml_ns}:exterior>\n</#{gml_ns}:Polygon>\n</#{georss_ns}:where>\n"
117
+ end
118
+
119
+ #outputs the geometry in kml format : options are <tt>:id</tt>, <tt>:tesselate</tt>, <tt>:extrude</tt>,
120
+ #<tt>:altitude_mode</tt>. If the altitude_mode option is not present, the Z (if present) will not be output (since
121
+ #it won't be used by GE anyway: clampToGround is the default)
122
+ def kml_representation(options = {})
123
+ result = "<Polygon#{options[:id_attr]}>\n"
124
+ result += options[:geom_data] if options[:geom_data]
125
+ rings.each_with_index do |ring, i|
126
+ if i == 0
127
+ boundary = "outerBoundaryIs"
128
+ else
129
+ boundary = "innerBoundaryIs"
130
+ end
131
+ result += "<#{boundary}><LinearRing><coordinates>\n"
132
+ result += ring.kml_poslist(options)
133
+ result += "\n</coordinates></LinearRing></#{boundary}>\n"
134
+ end
135
+ result += "</Polygon>\n"
136
+ end
137
+
138
+ def to_coordinates
139
+ rings.map{|lr| lr.to_coordinates}
140
+ end
141
+
142
+ # simple geojson representation
143
+ # TODO add CRS / SRID support?
144
+ def to_json(options = {})
145
+ {:type => 'Polygon',
146
+ :coordinates => self.to_coordinates}.to_json(options)
147
+ end
148
+ alias :as_geojson :to_json
149
+
150
+ #creates a new polygon. Accepts an array of linear strings as argument
151
+ def self.from_linear_rings(linear_rings,srid = DEFAULT_SRID,with_z=false,with_m=false)
152
+ polygon = new(srid,with_z,with_m)
153
+ polygon.concat(linear_rings)
154
+ polygon
155
+ end
156
+
157
+ #creates a new polygon. Accepts a sequence of points as argument : ((x,y)....(x,y)),((x,y).....(x,y))
158
+ def self.from_coordinates(point_sequences,srid=DEFAULT_SRID,with_z=false,with_m=false)
159
+ polygon = new(srid,with_z,with_m)
160
+ polygon.concat( point_sequences.map {|points| LinearRing.from_coordinates(points,srid,with_z,with_m) } )
161
+ polygon
162
+ end
163
+
164
+ #creates a new polygon from a list of Points (pt1....ptn),(pti....ptj)
165
+ def self.from_points(point_sequences, srid=DEFAULT_SRID,with_z=false,with_m=false)
166
+ polygon = new(srid,with_z,with_m)
167
+ polygon.concat( point_sequences.map {|points| LinearRing.from_points(points,srid,with_z,with_m) } )
168
+ polygon
169
+ end
170
+
171
+ end
172
+
173
+ end
174
+
175
+ end