georuby_remake 1.0.0

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 (70) 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 +47 -0
  6. data/VERSION +1 -0
  7. data/georuby_remake.gemspec +132 -0
  8. data/lib/geo_ruby.rb +5 -0
  9. data/lib/geo_ruby/gpx4r.rb +9 -0
  10. data/lib/geo_ruby/gpx4r/gpx.rb +104 -0
  11. data/lib/geo_ruby/shp4r.rb +57 -0
  12. data/lib/geo_ruby/shp4r/dbf.rb +38 -0
  13. data/lib/geo_ruby/shp4r/shp_file.rb +297 -0
  14. data/lib/geo_ruby/shp4r/shp_record.rb +14 -0
  15. data/lib/geo_ruby/shp4r/shp_transaction.rb +345 -0
  16. data/lib/geo_ruby/simple_features.rb +20 -0
  17. data/lib/geo_ruby/simple_features/envelope.rb +167 -0
  18. data/lib/geo_ruby/simple_features/ewkb_parser.rb +216 -0
  19. data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
  20. data/lib/geo_ruby/simple_features/geometry.rb +230 -0
  21. data/lib/geo_ruby/simple_features/geometry_collection.rb +136 -0
  22. data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
  23. data/lib/geo_ruby/simple_features/georss_parser.rb +135 -0
  24. data/lib/geo_ruby/simple_features/helper.rb +18 -0
  25. data/lib/geo_ruby/simple_features/line_string.rb +206 -0
  26. data/lib/geo_ruby/simple_features/linear_ring.rb +12 -0
  27. data/lib/geo_ruby/simple_features/multi_line_string.rb +51 -0
  28. data/lib/geo_ruby/simple_features/multi_point.rb +46 -0
  29. data/lib/geo_ruby/simple_features/multi_polygon.rb +52 -0
  30. data/lib/geo_ruby/simple_features/point.rb +364 -0
  31. data/lib/geo_ruby/simple_features/polygon.rb +157 -0
  32. data/spec/data/gpx/fells_loop.gpx +1077 -0
  33. data/spec/data/gpx/long.gpx +1642 -0
  34. data/spec/data/gpx/long.kml +31590 -0
  35. data/spec/data/gpx/long.nmea +2220 -0
  36. data/spec/data/gpx/short.gpx +13634 -0
  37. data/spec/data/gpx/short.kml +130 -0
  38. data/spec/data/gpx/tracktreks.gpx +706 -0
  39. data/spec/data/multipoint.dbf +0 -0
  40. data/spec/data/multipoint.shp +0 -0
  41. data/spec/data/multipoint.shx +0 -0
  42. data/spec/data/point.dbf +0 -0
  43. data/spec/data/point.shp +0 -0
  44. data/spec/data/point.shx +0 -0
  45. data/spec/data/polygon.dbf +0 -0
  46. data/spec/data/polygon.shp +0 -0
  47. data/spec/data/polygon.shx +0 -0
  48. data/spec/data/polyline.dbf +0 -0
  49. data/spec/data/polyline.shp +0 -0
  50. data/spec/data/polyline.shx +0 -0
  51. data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
  52. data/spec/geo_ruby/shp4r/shp_spec.rb +240 -0
  53. data/spec/geo_ruby/simple_features/envelope_spec.rb +45 -0
  54. data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
  55. data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
  56. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
  57. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
  58. data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
  59. data/spec/geo_ruby/simple_features/georss_parser_spec.rb +218 -0
  60. data/spec/geo_ruby/simple_features/line_string_spec.rb +245 -0
  61. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +14 -0
  62. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
  63. data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
  64. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
  65. data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
  66. data/spec/geo_ruby/simple_features/polygon_spec.rb +108 -0
  67. data/spec/geo_ruby_spec.rb +27 -0
  68. data/spec/spec.opts +6 -0
  69. data/spec/spec_helper.rb +65 -0
  70. metadata +185 -0
@@ -0,0 +1,230 @@
1
+ module GeoRuby#:nodoc:
2
+ module SimpleFeatures
3
+ #arbitrary default SRID
4
+ DEFAULT_SRID = 4326 unless defined? DEFAULT_SRID
5
+
6
+ #Root of all geometric data classes.
7
+ #Objects of class Geometry should not be instantiated.
8
+ class Geometry
9
+ #SRID of the geometry
10
+ attr_reader :srid #writer defined below
11
+ #Flag indicating if the z ordinate of the geometry is meaningful
12
+ attr_accessor :with_z
13
+ alias :with_z? :with_z
14
+ #Flag indicating if the m ordinate of the geometry is meaningful
15
+ attr_accessor :with_m
16
+ alias :with_m? :with_m
17
+
18
+ def initialize(srid=DEFAULT_SRID,with_z=false,with_m=false)
19
+ @srid=srid
20
+ @with_z=with_z
21
+ @with_m=with_m
22
+ end
23
+
24
+ def srid=(new_srid)
25
+ @srid = new_srid
26
+ unless self.is_a?(Point)
27
+ self.each do |geom|
28
+ geom.srid=new_srid
29
+ end
30
+ end
31
+ end
32
+
33
+ #to be implemented in subclasses
34
+ def bounding_box
35
+ end
36
+
37
+ #to be implemented in subclasses
38
+ def m_range
39
+ end
40
+
41
+ #Returns an Envelope object for the geometry
42
+ def envelope
43
+ Envelope.from_points(bounding_box,srid,with_z)
44
+ end
45
+
46
+ #Outputs the geometry as an EWKB string.
47
+ #The +allow_srid+, +allow_z+ and +allow_m+ arguments allow the output to include srid, z and m respectively if they are present in the geometry. If these arguments are set to false, srid, z and m are not included, even if they are present in the geometry. By default, the output string contains all the information in the object.
48
+ def as_ewkb(allow_srid=true,allow_z=true,allow_m=true)
49
+ ewkb="";
50
+
51
+ ewkb << 1.chr #little_endian by default
52
+
53
+ type= binary_geometry_type
54
+ if @with_z and allow_z
55
+ type = type | Z_MASK
56
+ end
57
+ if @with_m and allow_m
58
+ type = type | M_MASK
59
+ end
60
+ if allow_srid
61
+ type = type | SRID_MASK
62
+ ewkb << [type,@srid].pack("VV")
63
+ else
64
+ ewkb << [type].pack("V")
65
+ end
66
+
67
+ ewkb << binary_representation(allow_z,allow_m)
68
+ end
69
+
70
+ #Outputs the geometry as a strict WKB string.
71
+ def as_wkb
72
+ as_ewkb(false,false,false)
73
+ end
74
+
75
+ #Outputs the geometry as a HexEWKB string. It is almost the same as a WKB string, except that each byte of a WKB string is replaced by its hexadecimal 2-character representation in a HexEWKB string.
76
+ def as_hex_ewkb(allow_srid=true,allow_z=true,allow_m=true)
77
+ as_ewkb(allow_srid, allow_z, allow_m).unpack('H*').join('').upcase
78
+ end
79
+
80
+ #Outputs the geometry as a strict HexWKB string
81
+ def as_hex_wkb
82
+ as_hex_ewkb(false,false,false)
83
+ end
84
+
85
+ #Outputs the geometry as an EWKT string.
86
+ def as_ewkt(allow_srid=true,allow_z=true,allow_m=true)
87
+ if allow_srid
88
+ ewkt="SRID=#{@srid};"
89
+ else
90
+ ewkt=""
91
+ end
92
+ ewkt << text_geometry_type
93
+ ewkt << "M" if @with_m and allow_m and (!@with_z or !allow_z) #to distinguish the M from the Z when there is actually no Z...
94
+ ewkt << "(" << text_representation(allow_z,allow_m) << ")"
95
+ end
96
+
97
+ #Outputs the geometry as strict WKT string.
98
+ def as_wkt
99
+ as_ewkt(false,false,false)
100
+ end
101
+
102
+ #Outputs the geometry in georss format.
103
+ #Assumes the geometries are in latlon format, with x as lon and y as lat.
104
+ #Pass the <tt>:dialect</tt> option to switch format. Possible values are: <tt>:simple</tt> (default), <tt>:w3cgeo</tt> and <tt>:gml</tt>.
105
+ def as_georss(options = {})
106
+ dialect= options[:dialect] || :simple
107
+ case(dialect)
108
+ when :simple
109
+ geom_attr = ""
110
+ geom_attr += " featuretypetag=\"#{options[:featuretypetag]}\"" if options[:featuretypetag]
111
+ geom_attr += " relationshiptag=\"#{options[:relationshiptag]}\"" if options[:relationshiptag]
112
+ geom_attr += " floor=\"#{options[:floor]}\"" if options[:floor]
113
+ geom_attr += " radius=\"#{options[:radius]}\"" if options[:radius]
114
+ geom_attr += " elev=\"#{options[:elev]}\"" if options[:elev]
115
+ georss_simple_representation(options.merge(:geom_attr => geom_attr))
116
+ when :w3cgeo
117
+ georss_w3cgeo_representation(options)
118
+ when :gml
119
+ georss_gml_representation(options)
120
+ end
121
+ end
122
+
123
+ #Iutputs the geometry in kml format : options are <tt>:id</tt>, <tt>:tesselate</tt>, <tt>:extrude</tt>,
124
+ #<tt>:altitude_mode</tt>. If the altitude_mode option is not present, the Z (if present) will not be output (since
125
+ #it won't be used by GE anyway: clampToGround is the default)
126
+ def as_kml(options = {})
127
+ id_attr = ""
128
+ id_attr = " id=\"#{options[:id]}\"" if options[:id]
129
+
130
+ geom_data = ""
131
+ geom_data += "<extrude>#{options[:extrude]}</extrude>\n" if options[:extrude]
132
+ geom_data += "<tesselate>#{options[:tesselate]}</tesselate>\n" if options[:tesselate]
133
+ geom_data += "<altitudeMode>#{options[:altitude_mode]}</altitudeMode>\n" if options[:altitude_mode]
134
+
135
+ allow_z = (with_z || !options[:altitude].nil? )&& (!options[:altitude_mode].nil?) && options[:atitude_mode] != "clampToGround"
136
+ fixed_z = options[:altitude]
137
+
138
+ kml_representation(options.merge(:id_attr => id_attr, :geom_data => geom_data, :allow_z => allow_z, :fixed_z => fixed_z))
139
+ end
140
+
141
+ #Creates a geometry based on a EWKB string. The actual class returned depends of the content of the string passed as argument. Since WKB strings are a subset of EWKB, they are also valid.
142
+ def self.from_ewkb(ewkb)
143
+ factory = GeometryFactory::new
144
+ ewkb_parser= EWKBParser::new(factory)
145
+ ewkb_parser.parse(ewkb)
146
+ factory.geometry
147
+ end
148
+
149
+ #Creates a geometry based on a HexEWKB string
150
+ def self.from_hex_ewkb(hexewkb)
151
+ factory = GeometryFactory::new
152
+ hexewkb_parser= HexEWKBParser::new(factory)
153
+ hexewkb_parser.parse(hexewkb)
154
+ factory.geometry
155
+ end
156
+
157
+ #Creates a geometry based on a EWKT string. Since WKT strings are a subset of EWKT, they are also valid.
158
+ def self.from_ewkt(ewkt)
159
+ factory = GeometryFactory::new
160
+ ewkt_parser= EWKTParser::new(factory)
161
+ ewkt_parser.parse(ewkt)
162
+ factory.geometry
163
+ end
164
+
165
+ #sends back a geometry based on the GeoRSS string passed as argument
166
+ def self.from_georss(georss)
167
+ georss_parser= GeorssParser::new
168
+ georss_parser.parse(georss)
169
+ georss_parser.geometry
170
+ end
171
+
172
+ #sends back an array: The first element is the goemetry based on the GeoRSS string passed as argument. The second one is the GeoRSSTags (found only with the Simple format)
173
+ def self.from_georss_with_tags(georss)
174
+ georss_parser= GeorssParser::new
175
+ georss_parser.parse(georss,true)
176
+ [georss_parser.geometry, georss_parser.georss_tags]
177
+ end
178
+
179
+ #Sends back a geometry from a KML encoded geometry string.
180
+ #Limitations : Only supports points, linestrings and polygons (no collection for now).
181
+ #Addapted from Pramukta's code
182
+ def self.from_kml(kml)
183
+ return GeoRuby::SimpleFeatures::Geometry.from_ewkt(kml_to_wkt(kml))
184
+ end
185
+
186
+ require 'rexml/document'
187
+ def self.kml_to_wkt(kml)
188
+ doc = REXML::Document.new(kml)
189
+ wkt = ""
190
+ if ["Point", "LineString", "Polygon" ].include?(doc.root.name)
191
+ case doc.root.name
192
+ when "Point" then
193
+ coords = doc.elements["/Point/coordinates"].text.gsub(/\n/," ")
194
+ wkt = doc.root.name.upcase + "(" + split_coords(coords).join(' ') + ")"
195
+ when "LineString" then
196
+ coords = doc.elements["/LineString/coordinates"].text.gsub(/\n/," ")
197
+ coords = split_coords(coords)
198
+ wkt = doc.root.name.upcase + "(" + coords.join(",") + ")"
199
+ when "Polygon" then
200
+ # polygons have one outer ring and zero or more inner rings
201
+ bounds = []
202
+ bounds << doc.elements["/Polygon/outerBoundaryIs/LinearRing/coordinates"].text
203
+ inner_coords_elements = doc.elements.each("/Polygon/innerBoundaryIs/LinearRing/coordinates") do |inner_coords|
204
+ inner_coords = inner_coords.text
205
+ bounds << inner_coords
206
+ end
207
+
208
+ wkt = doc.root.name.upcase + "(" + bounds.map do |bound|
209
+ bound.gsub!(/\n/, " ")
210
+ bound = split_coords(bound)
211
+ if bound.first != bound.last
212
+ bound.push bound.first
213
+ end
214
+ "(" + bound.join(",") + ")"
215
+ end.join(",") + ")"
216
+ end
217
+ end
218
+ return wkt
219
+ end
220
+
221
+ private
222
+
223
+ def self.split_coords(coords)
224
+ coords.split(" ").collect { |coord|
225
+ coord.gsub(","," ")
226
+ }
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,136 @@
1
+ #require 'geo_ruby/simple_features/geometry'
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ #Represents a collection of arbitrary geometries
6
+ class GeometryCollection < Geometry
7
+ attr_reader :geometries
8
+
9
+ def initialize(srid = DEFAULT_SRID,with_z=false,with_m=false)
10
+ super(srid,with_z,with_m)
11
+ @geometries = []
12
+ end
13
+
14
+ #Delegate the unknown methods to the geometries array
15
+ def method_missing(method_name,*args,&b)
16
+ @geometries.send(method_name,*args,&b)
17
+ end
18
+
19
+ #Bounding box in 2D/3D. Returns an array of 2 points
20
+ def bounding_box
21
+ max_x, min_x, max_y, min_y = -Float::MAX, Float::MAX, -Float::MAX, Float::MAX, -Float::MAX, Float::MAX
22
+ if with_z
23
+ max_z, min_z = -Float::MAX, Float::MAX
24
+ each do |geometry|
25
+ bbox = geometry.bounding_box
26
+ sw = bbox[0]
27
+ ne = bbox[1]
28
+
29
+ max_y = ne.y if ne.y > max_y
30
+ min_y = sw.y if sw.y < min_y
31
+ max_x = ne.x if ne.x > max_x
32
+ min_x = sw.x if sw.x < min_x
33
+ max_z = ne.z if ne.z > max_z
34
+ min_z = sw.z if sw.z < min_z
35
+ end
36
+ [Point.from_x_y_z(min_x,min_y,min_z),Point.from_x_y_z(max_x,max_y,max_z)]
37
+ else
38
+ each do |geometry|
39
+ bbox = geometry.bounding_box
40
+ sw = bbox[0]
41
+ ne = bbox[1]
42
+
43
+ max_y = ne.y if ne.y > max_y
44
+ min_y = sw.y if sw.y < min_y
45
+ max_x = ne.x if ne.x > max_x
46
+ min_x = sw.x if sw.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 |lr|
56
+ lrmr = lr.m_range
57
+ max_m = lrmr[1] if lrmr[1] > max_m
58
+ min_m = lrmr[0] if lrmr[0] < min_m
59
+ end
60
+ [min_m,max_m]
61
+ else
62
+ [0,0]
63
+ end
64
+ end
65
+
66
+ #tests the equality of geometry collections
67
+ def ==(other_collection)
68
+ if(other_collection.class != self.class)
69
+ false
70
+ elsif length != other_collection.length
71
+ false
72
+ else
73
+ index=0
74
+ while index<length
75
+ return false if self[index] != other_collection[index]
76
+ index+=1
77
+ end
78
+ true
79
+ end
80
+ end
81
+
82
+ #Binary representation of the collection
83
+ def binary_representation(allow_z=true,allow_m=true) #:nodoc:
84
+ rep = [length].pack("V")
85
+ #output the list of geometries without outputting the SRID first and with the same setting regarding Z and M
86
+ each {|geometry| rep << geometry.as_ewkb(false,allow_z,allow_m) }
87
+ rep
88
+ end
89
+
90
+ #WKB geometry type of the collection
91
+ def binary_geometry_type #:nodoc:
92
+ 7
93
+ end
94
+
95
+ #Text representation of a geometry collection
96
+ def text_representation(allow_z=true,allow_m=true) #:nodoc:
97
+ @geometries.collect{|geometry| geometry.as_ewkt(false,allow_z,allow_m)}.join(",")
98
+ end
99
+
100
+ #WKT geometry type
101
+ def text_geometry_type #:nodoc:
102
+ "GEOMETRYCOLLECTION"
103
+ end
104
+
105
+ #georss simple representation : outputs only the first geometry of the collection
106
+ def georss_simple_representation(options)#:nodoc:
107
+ self[0].georss_simple_representation(options)
108
+ end
109
+ #georss w3c representation : outputs the first point of the outer ring
110
+ def georss_w3cgeo_representation(options)#:nodoc:
111
+ self[0].georss_w3cgeo_representation(options)
112
+ end
113
+ #georss gml representation : outputs only the first geometry of the collection
114
+ def georss_gml_representation(options)#:nodoc:
115
+ self[0].georss_gml_representation(options)
116
+ end
117
+
118
+ #outputs the geometry in kml format
119
+ def kml_representation(options = {}) #:nodoc:
120
+ result = "<MultiGeometry#{options[:id_attr]}>\n"
121
+ options[:id_attr] = "" #the subgeometries do not have an ID
122
+ each do |geometry|
123
+ result += geometry.kml_representation(options)
124
+ end
125
+ result += "</MultiGeometry>\n"
126
+ end
127
+
128
+ #creates a new GeometryCollection from an array of geometries
129
+ def self.from_geometries(geometries,srid=DEFAULT_SRID,with_z=false,with_m=false)
130
+ geometry_collection = new(srid,with_z,with_m)
131
+ geometry_collection.concat(geometries)
132
+ geometry_collection
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,81 @@
1
+ require 'geo_ruby/simple_features/point'
2
+ require 'geo_ruby/simple_features/line_string'
3
+ require 'geo_ruby/simple_features/linear_ring'
4
+ require 'geo_ruby/simple_features/polygon'
5
+ require 'geo_ruby/simple_features/multi_point'
6
+ require 'geo_ruby/simple_features/multi_line_string'
7
+ require 'geo_ruby/simple_features/multi_polygon'
8
+ require 'geo_ruby/simple_features/geometry_collection'
9
+
10
+
11
+ module GeoRuby
12
+ module SimpleFeatures
13
+ #Creates a new geometry according to constructions received from a parser, for example EWKBParser.
14
+ class GeometryFactory
15
+ #the built geometry
16
+ attr_reader :geometry
17
+
18
+ def initialize
19
+ @geometry = nil
20
+ @geometry_stack = []
21
+ end
22
+ #resets the factory
23
+ def reset
24
+ @geometry = nil
25
+ @geometry_stack = []
26
+ end
27
+ #add a 2D point to the current geometry
28
+ def add_point_x_y(x,y)
29
+ @geometry_stack.last.set_x_y(x,y)
30
+ end
31
+ #add 2D points to the current geometry
32
+ def add_points_x_y(xy)
33
+ xy.each_slice(2) {|slice| add_point_x_y(*slice)}
34
+ end
35
+ #add a 3D point to the current geometry
36
+ def add_point_x_y_z(x,y,z)
37
+ @geometry_stack.last.set_x_y_z(x,y,z)
38
+ end
39
+ #add 3D points to the current geometry
40
+ def add_points_x_y_z(xyz)
41
+ xyz.each_slice(3) {|slice| add_point_x_y_z(*slice)}
42
+ end
43
+ #add a 2D point with M to the current geometry
44
+ def add_point_x_y_m(x,y,m)
45
+ @geometry_stack.last.set_x_y(x,y)
46
+ @geometry_stack.last.m=m
47
+ end
48
+ #add 2D points with M to the current geometry
49
+ def add_points_x_y_m(xym)
50
+ xym.each_slice(3) {|slice| add_point_x_y_m(*slice)}
51
+ end
52
+ #add a 3D point with M to the current geometry
53
+ def add_point_x_y_z_m(x,y,z,m)
54
+ @geometry_stack.last.set_x_y_z(x,y,z)
55
+ @geometry_stack.last.m=m
56
+ end
57
+ #add 3D points with M to the current geometry
58
+ def add_points_x_y_z_m(xyzm)
59
+ xyzm.each_slice(4) {|slice| add_point_x_y_z_m(*slice)}
60
+ end
61
+ #begin a geometry of type +geometry_type+
62
+ def begin_geometry(geometry_type,srid=DEFAULT_SRID)
63
+ geometry= geometry_type::new(srid)
64
+ @geometry= geometry if @geometry.nil?
65
+ @geometry_stack << geometry
66
+ end
67
+ #terminates the current geometry
68
+ def end_geometry(with_z=false,with_m=false)
69
+ @geometry=@geometry_stack.pop
70
+ @geometry.with_z=with_z
71
+ @geometry.with_m=with_m
72
+ #add the newly defined geometry to its parent if there is one
73
+ @geometry_stack.last << geometry if !@geometry_stack.empty?
74
+ end
75
+ #abort a geometry
76
+ def abort_geometry
77
+ reset
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,135 @@
1
+ #require 'geo_ruby/simple_features/point'
2
+ #require 'geo_ruby/simple_features/line_string'
3
+ #require 'geo_ruby/simple_features/linear_ring'
4
+ #require 'geo_ruby/simple_features/polygon'
5
+ #require 'geo_ruby/simple_features/multi_point'
6
+ #require 'geo_ruby/simple_features/multi_line_string'
7
+ #require 'geo_ruby/simple_features/multi_polygon'
8
+ #require 'geo_ruby/simple_features/geometry_collection'
9
+ #require 'geo_ruby/simple_features/envelope'
10
+
11
+ module GeoRuby
12
+ module SimpleFeatures
13
+
14
+ #Raised when an error in the GeoRSS string is detected
15
+ class GeorssFormatError < StandardError
16
+ end
17
+
18
+ #Contains tags possibly found on GeoRss Simple geometries
19
+ class GeorssTags < Struct.new(:featuretypetag,:relationshiptag,:elev,:floor,:radius)
20
+ end
21
+
22
+ #Parses GeoRSS strings
23
+ #You can also use directly the static method Geometry.from_georss
24
+ class GeorssParser
25
+ attr_reader :georss_tags, :geometry
26
+
27
+ #Parses the georss geometry passed as argument and notifies the factory of events
28
+ #The parser assumes
29
+ def parse(georss,with_tags = false)
30
+ @geometry = nil
31
+ @georss_tags = GeorssTags.new
32
+ parse_geometry(georss,with_tags)
33
+ end
34
+
35
+ private
36
+ def parse_geometry(georss,with_tags)
37
+ georss.strip!
38
+ #check for W3CGeo first
39
+ if georss =~ /<[^:>]*:lat\s*>([^<]*)</
40
+ #if valid, it is W3CGeo
41
+ lat = $1.to_f
42
+ if georss =~ /<[^:>]*:long\s*>([^<]*)</
43
+ lon = $1.to_f
44
+ @geometry = Point.from_x_y(lon,lat)
45
+ else
46
+ raise GeorssFormatError.new("Bad W3CGeo GeoRSS format")
47
+ end
48
+ elsif georss =~ /^<\s*[^:>]*:where\s*>/
49
+ #GML format found
50
+ gml = $'.strip
51
+ if gml =~ /^<\s*[^:>]*:Point\s*>/
52
+ #gml point
53
+ if gml =~ /<\s*[^:>]*:pos\s*>([^<]*)/
54
+ point = $1.split(" ")
55
+ #lat comes first
56
+ @geometry = Point.from_x_y(point[1].to_f,point[0].to_f)
57
+ else
58
+ raise GeorssFormatError.new("Bad GML GeoRSS format: Malformed Point")
59
+ end
60
+ elsif gml =~ /^<\s*[^:>]*:LineString\s*>/
61
+ if gml =~ /<\s*[^:>]*:posList\s*>([^<]*)/
62
+ xy = $1.split(" ")
63
+ @geometry = LineString.new
64
+ 0.upto(xy.size/2 - 1) { |index| @geometry << Point.from_x_y(xy[index*2 + 1].to_f,xy[index*2].to_f)}
65
+ else
66
+ raise GeorssFormatError.new("Bad GML GeoRSS format: Malformed LineString")
67
+ end
68
+ elsif gml =~ /^<\s*[^:>]*:Polygon\s*>/
69
+ if gml =~ /<\s*[^:>]*:posList\s*>([^<]*)/
70
+ xy = $1.split(" ")
71
+ @geometry = Polygon.new
72
+ linear_ring = LinearRing.new
73
+ @geometry << linear_ring
74
+ xy = $1.split(" ")
75
+ 0.upto(xy.size/2 - 1) { |index| linear_ring << Point.from_x_y(xy[index*2 + 1].to_f,xy[index*2].to_f)}
76
+ else
77
+ raise GeorssFormatError.new("Bad GML GeoRSS format: Malformed Polygon")
78
+ end
79
+ elsif gml =~ /^<\s*[^:>]*:Envelope\s*>/
80
+ if gml =~ /<\s*[^:>]*:lowerCorner\s*>([^<]*)</
81
+ lc = $1.split(" ").collect { |x| x.to_f}.reverse
82
+ if gml =~ /<\s*[^:>]*:upperCorner\s*>([^<]*)</
83
+ uc = $1.split(" ").collect { |x| x.to_f}.reverse
84
+ @geometry = Envelope.from_coordinates([lc,uc])
85
+ else
86
+ raise GeorssFormatError.new("Bad GML GeoRSS format: Malformed Envelope")
87
+ end
88
+ else
89
+ raise GeorssFormatError.new("Bad GML GeoRSS format: Malformed Envelope")
90
+ end
91
+ else
92
+ raise GeorssFormatError.new("Bad GML GeoRSS format: Unknown geometry type")
93
+ end
94
+ else
95
+ #must be simple format
96
+ if georss =~ /^<\s*[^>:]*:point([^>]*)>(.*)</m
97
+ tags = $1
98
+ point = $2.gsub(","," ").split(" ")
99
+ @geometry = Point.from_x_y(point[1].to_f,point[0].to_f)
100
+ elsif georss =~ /^<\s*[^>:]*:line([^>]*)>(.*)</m
101
+ tags = $1
102
+ @geometry = LineString.new
103
+ xy = $2.gsub(","," ").split(" ")
104
+ 0.upto(xy.size/2 - 1) { |index| @geometry << Point.from_x_y(xy[index*2 + 1].to_f,xy[index*2].to_f)}
105
+ elsif georss =~ /^<\s*[^>:]*:polygon([^>]*)>(.*)</m
106
+ tags = $1
107
+ @geometry = Polygon.new
108
+ linear_ring = LinearRing.new
109
+ @geometry << linear_ring
110
+ xy = $2.gsub(","," ").split(" ")
111
+ 0.upto(xy.size/2 - 1) { |index| linear_ring << Point.from_x_y(xy[index*2 + 1].to_f,xy[index*2].to_f)}
112
+ elsif georss =~ /^<\s*[^>:]*:box([^>]*)>(.*)</m
113
+ tags = $1
114
+ corners = []
115
+ xy = $2.gsub(","," ").split(" ")
116
+ 0.upto(xy.size/2 - 1) {|index| corners << Point.from_x_y(xy[index*2 + 1].to_f,xy[index*2].to_f)}
117
+ @geometry = Envelope.from_points(corners)
118
+ else
119
+ raise GeorssFormatError.new("Bad Simple GeoRSS format: Unknown geometry type")
120
+ end
121
+
122
+ #geometry found: parse tags
123
+ return unless with_tags
124
+
125
+ @georss_tags.featuretypetag = $1 if tags =~ /featuretypetag=['"]([^"']*)['"]/
126
+ @georss_tags.relationshiptag = $1 if tags =~ /relationshiptag=['"]([^'"]*)['"]/
127
+ @georss_tags.elev = $1.to_f if tags =~ /elev=['"]([^'"]*)['"]/
128
+ @georss_tags.floor = $1.to_i if tags =~ /floor=['"]([^'"]*)['"]/
129
+ @georss_tags.radius = $1.to_f if tags =~ /radius=['"]([^'"]*)['"]/
130
+
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end