georuby 1.9.3

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 (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,167 @@
1
+ module GeoRuby
2
+ module SimpleFeatures
3
+
4
+ #Contains the bounding box of a geometry
5
+ class Envelope
6
+ attr_accessor :lower_corner, :upper_corner
7
+ attr_accessor :srid, :with_z, :zoom
8
+
9
+ #Creates a enw Envelope with +lower_corner+ as the first element of the corners array and +upper_corner+ as the second element
10
+ def initialize(srid = DEFAULT_SRID, with_z = false)
11
+ @srid = srid
12
+ @with_z = with_z
13
+ end
14
+
15
+ #Merges the argument with the current evelope
16
+ def extend!(envelope)
17
+ lower_corner.x = [lower_corner.x,envelope.lower_corner.x].min
18
+ lower_corner.y = [lower_corner.y,envelope.lower_corner.y].min
19
+ upper_corner.x = [upper_corner.x,envelope.upper_corner.x].max
20
+ upper_corner.y = [upper_corner.y,envelope.upper_corner.y].max
21
+ self
22
+ end
23
+
24
+ #Merges the argument with the current evelope and sends back a new
25
+ #envelope without changing the current one
26
+ def extend(envelope)
27
+ e = Envelope.from_points([Point.from_x_y(lower_corner.x,lower_corner.y),
28
+ Point.from_x_y(upper_corner.x,upper_corner.y)],srid,with_z)
29
+ e.extend!(envelope)
30
+ e
31
+ end
32
+ # def bounding_box(markers)
33
+ # max_lat, max_lon, min_lat, min_lon = -Float::MAX, -Float::MAX, Float::MAX, Float::MAX
34
+ # markers.each do |marker|
35
+ # coord = marker.point
36
+ # max_lat = coord.lat if coord.lat > max_lat
37
+ # min_lat = coord.lat if coord.lat < min_lat
38
+ # max_lon = coord.lng if coord.lng > max_lon
39
+ # min_lon = coord.lng if coord.lng < min_lon
40
+ # end
41
+ # min_point = Point.from_x_y(min_lat,min_lon)
42
+ # max_point = Point.from_x_y(max_lat,max_lon)
43
+
44
+ # end
45
+ # centrelat = (max_lat + min_lat)/2
46
+ # centrelng = (max_lon + min_lon)/2
47
+ # # logger.info("distance[#{distance}],zoom[#{zoom}]")
48
+ # #return GLatLngBounds.new(GLatLng.new(min_point),GLatLng.new(max_point)), [centrelat,centrelng], zoom
49
+ # return [centrelat,centrelng], zoom
50
+
51
+ #Sends back the center of the envelope
52
+ def center
53
+ Point.from_x_y((lower_corner.x + upper_corner.x)/2,(lower_corner.y + upper_corner.y)/2, srid)
54
+ end
55
+
56
+ #Zoom level
57
+ def zoom
58
+ distance = lower_corner.spherical_distance(upper_corner)/10000
59
+ @zoom = case distance
60
+ when 150..9000 then 5
61
+ when 80..149 then 6
62
+ when 50..79 then 7
63
+ when 20..49 then 8
64
+ when 10..19 then 9
65
+ when 5..9 then 10
66
+ else 13
67
+ end
68
+ end
69
+
70
+ #Tests the equality of line strings
71
+ def ==(other_envelope)
72
+ if other_envelope.class != self.class
73
+ false
74
+ else
75
+ upper_corner == other_envelope.upper_corner and lower_corner == other_envelope.lower_corner
76
+ end
77
+ end
78
+
79
+ #georss serialization: Dialect can be passed as option <tt>:dialect</tt> and set to <tt>:simple</tt> (default)
80
+ #<tt>:w3cgeo</tt> or <tt>:gml</tt>. Options <tt>:featuretypetag
81
+ def as_georss(options = {})
82
+ dialect= options[:dialect] || :simple
83
+ case(dialect)
84
+ when :simple
85
+ geom_attr = ""
86
+ geom_attr += " featuretypetag=\"#{options[:featuretypetag]}\"" if options[:featuretypetag]
87
+ geom_attr += " relationshiptag=\"#{options[:relationshiptag]}\"" if options[:relationshiptag]
88
+ geom_attr += " floor=\"#{options[:floor]}\"" if options[:floor]
89
+ geom_attr += " radius=\"#{options[:radius]}\"" if options[:radius]
90
+ geom_attr += " elev=\"#{options[:elev]}\"" if options[:elev]
91
+
92
+ georss_simple_representation(options.merge(:geom_attr => geom_attr))
93
+ when :w3cgeo
94
+ georss_w3cgeo_representation(options)
95
+ when :gml
96
+ georss_gml_representation(options)
97
+ end
98
+ end
99
+
100
+ #georss simple representation
101
+ def georss_simple_representation(options = {}) #:nodoc:
102
+ georss_ns = options[:georss_ns] || "georss"
103
+ geom_attr = options[:geom_attr]
104
+ "<#{georss_ns}:box#{geom_attr}>#{lower_corner.y} #{lower_corner.x} #{upper_corner.y} #{upper_corner.x}</#{georss_ns}:box>\n"
105
+ end
106
+
107
+ #georss w3c representation : outputs the first point of the line
108
+ def georss_w3cgeo_representation(options = {}) #:nodoc:
109
+ w3cgeo_ns = options[:w3cgeo_ns] || "geo"
110
+ point = self.center
111
+ "<#{w3cgeo_ns}:lat>#{point.y}</#{w3cgeo_ns}:lat>\n<#{w3cgeo_ns}:long>#{point.x}</#{w3cgeo_ns}:long>\n"
112
+ end
113
+
114
+ #georss gml representation
115
+ def georss_gml_representation(options = {}) #:nodoc:
116
+ georss_ns = options[:georss_ns] || "georss"
117
+ gml_ns = options[:gml_ns] || "gml"
118
+ result = "<#{georss_ns}:where>\n<#{gml_ns}:Envelope>\n"
119
+ result += "<#{gml_ns}:LowerCorner>" + "#{lower_corner.y} #{lower_corner.x}" + "</#{gml_ns}:LowerCorner>"
120
+ result += "<#{gml_ns}:UpperCorner>" + "#{upper_corner.y} #{upper_corner.x}" + "</#{gml_ns}:UpperCorner>"
121
+ result += "</#{gml_ns}:Envelope>\n</#{georss_ns}:where>\n"
122
+ end
123
+
124
+ #Sends back a latlonaltbox
125
+ def as_kml(options = {})
126
+ geom_data = ""
127
+ geom_data = "<altitudeMode>#{options[:altitude_mode]}</altitudeMode>\n" if options[:altitude_mode]
128
+
129
+ allow_z = with_z && (!options[:altitude_mode].nil?) && options[:atitude_mode] != "clampToGround"
130
+
131
+ kml_representation(options.merge(:geom_data => geom_data,:allow_z => allow_z))
132
+ end
133
+
134
+ def kml_representation(options = {})#:nodoc:
135
+ result = "<LatLonAltBox>\n"
136
+ result += options[:geom_data]
137
+ result += "<north>#{upper_corner.y}</north>\n"
138
+ result += "<south>#{lower_corner.y}</south>\n"
139
+ result += "<east>#{upper_corner.x}</east>\n"
140
+ result += "<west>#{lower_corner.x}</west>\n"
141
+
142
+ if with_z
143
+ result += "<minAltitude>#{lower_corner.z}</minAltitude>"
144
+ result += "<maxAltitude>#{upper_corner.z}</maxAltitude>"
145
+ end
146
+
147
+ result += "</LatLonAltBox>\n"
148
+ end
149
+
150
+ #Creates a new envelope. Accept an array of 2 points as argument
151
+ def self.from_points(points,srid=DEFAULT_SRID,with_z=false)
152
+ raise "Not an array" unless points.class == Array
153
+ e = Envelope.new(srid,with_z)
154
+ e.lower_corner, e.upper_corner = points
155
+ e
156
+ end
157
+
158
+ #Creates a new envelope. Accept a sequence of point coordinates as argument : ((x,y),(x,y))
159
+ def self.from_coordinates(points,srid=DEFAULT_SRID,with_z=false)
160
+ e = Envelope.new(srid,with_z)
161
+ e.lower_corner, e.upper_corner = points.collect{|point_coords| Point.from_coordinates(point_coords,srid,with_z)}
162
+ e
163
+ end
164
+
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,218 @@
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
+ module GeoRuby
11
+ module SimpleFeatures
12
+
13
+ #Raised when an error in the EWKB string is detected
14
+ class EWKBFormatError < StandardError
15
+ end
16
+
17
+ #Parses EWKB strings and notifies of events (such as the beginning of the definition of geometry, the value of the SRID...) the factory passed as argument to the constructor.
18
+ #
19
+ #=Example
20
+ # factory = GeometryFactory::new
21
+ # ewkb_parser = EWKBParser::new(factory)
22
+ # ewkb_parser.parse(<EWKB String>)
23
+ # geometry = @factory.geometry
24
+ #
25
+ #You can also use directly the static method Geometry.from_ewkb
26
+ class EWKBParser
27
+
28
+ def initialize(factory)
29
+ @factory = factory
30
+ end
31
+
32
+ #Parses the ewkb string passed as argument and notifies the factory of events
33
+ def parse(ewkb)
34
+ @factory.reset
35
+ @with_z = false
36
+ @with_m = false
37
+ @unpack_structure = UnpackStructure::new(ewkb)
38
+ parse_geometry
39
+ @unpack_structure.done
40
+ @srid = nil
41
+ end
42
+
43
+ private
44
+
45
+ def parse_geometry
46
+ @unpack_structure.endianness = @unpack_structure.read_byte
47
+ geometry_type = @unpack_structure.read_uint
48
+
49
+ if (geometry_type & Z_MASK) != 0
50
+ @with_z = true
51
+ geometry_type = geometry_type & ~Z_MASK
52
+ end
53
+ if (geometry_type & M_MASK) != 0
54
+ @with_m = true
55
+ geometry_type = geometry_type & ~M_MASK
56
+ end
57
+ if (geometry_type & SRID_MASK) != 0
58
+ @srid = @unpack_structure.read_uint
59
+ geometry_type = geometry_type & ~SRID_MASK
60
+ else
61
+ #to manage multi geometries : the srid is not present in sub_geometries, therefore we take the srid of the parent ; if it is the root, we take the default srid
62
+ @srid ||= DEFAULT_SRID
63
+ end
64
+
65
+ case geometry_type
66
+ when 1
67
+ parse_point
68
+ when 2
69
+ parse_line_string
70
+ when 3
71
+ parse_polygon
72
+ when 4
73
+ parse_multi_point
74
+ when 5
75
+ parse_multi_line_string
76
+ when 6
77
+ parse_multi_polygon
78
+ when 7
79
+ parse_geometry_collection
80
+ else
81
+ raise EWKBFormatError::new("Unknown geometry type")
82
+ end
83
+ end
84
+
85
+ def parse_geometry_collection
86
+ parse_multi_geometries(GeometryCollection)
87
+ end
88
+
89
+ def parse_multi_polygon
90
+ parse_multi_geometries(MultiPolygon)
91
+ end
92
+
93
+ def parse_multi_line_string
94
+ parse_multi_geometries(MultiLineString)
95
+ end
96
+
97
+ def parse_multi_point
98
+ parse_multi_geometries(MultiPoint)
99
+ end
100
+
101
+ def parse_multi_geometries(geometry_type)
102
+ @factory.begin_geometry(geometry_type,@srid)
103
+ num_geometries = @unpack_structure.read_uint
104
+ 1.upto(num_geometries) { parse_geometry }
105
+ @factory.end_geometry(@with_z,@with_m)
106
+ end
107
+
108
+ def parse_polygon
109
+ @factory.begin_geometry(Polygon,@srid)
110
+ num_linear_rings = @unpack_structure.read_uint
111
+ 1.upto(num_linear_rings) {parse_linear_ring}
112
+ @factory.end_geometry(@with_z,@with_m)
113
+ end
114
+
115
+ def parse_linear_ring
116
+ parse_point_list(LinearRing)
117
+ end
118
+
119
+ def parse_line_string
120
+ parse_point_list(LineString)
121
+ end
122
+
123
+ #used to parse line_strings and linear_rings
124
+ def parse_point_list(geometry_type)
125
+ @factory.begin_geometry(geometry_type,@srid)
126
+ num_points = @unpack_structure.read_uint
127
+ 1.upto(num_points) {parse_point}
128
+ @factory.end_geometry(@with_z,@with_m)
129
+ end
130
+
131
+ def parse_point
132
+ @factory.begin_geometry(Point,@srid)
133
+ x, y = *@unpack_structure.read_point
134
+ if ! (@with_z or @with_m) #most common case probably
135
+ @factory.add_point_x_y(x,y)
136
+ elsif @with_m and @with_z
137
+ z = @unpack_structure.read_double
138
+ m = @unpack_structure.read_double
139
+ @factory.add_point_x_y_z_m(x,y,z,m)
140
+ elsif @with_z
141
+ z = @unpack_structure.read_double
142
+ @factory.add_point_x_y_z(x,y,z)
143
+ else
144
+ m = @unpack_structure.read_double
145
+ @factory.add_point_x_y_m(x,y,m)
146
+ end
147
+
148
+ @factory.end_geometry(@with_z,@with_m)
149
+ end
150
+ end
151
+
152
+ #Parses HexEWKB strings. In reality, it just transforms the HexEWKB string into the equivalent EWKB string and lets the EWKBParser do the actual parsing.
153
+ class HexEWKBParser < EWKBParser
154
+
155
+ #parses an HexEWKB string
156
+ def parse(hexewkb)
157
+ super(decode_hex(hexewkb))
158
+ end
159
+
160
+ #transforms a HexEWKB string into an EWKB string
161
+ def decode_hex(hexewkb)
162
+ [hexewkb].pack("H*")
163
+ end
164
+ end
165
+
166
+ class UnpackStructure #:nodoc:
167
+ NDR = 1
168
+ XDR = 0
169
+
170
+ def initialize(ewkb)
171
+ @position = 0
172
+ @ewkb = ewkb
173
+ end
174
+
175
+ def done
176
+ raise EWKBFormatError::new("Trailing data") if @position != @ewkb.length
177
+ end
178
+
179
+ def read_point
180
+ i = @position
181
+ @position += 16
182
+ raise EWKBFormatError::new("Truncated data") if @ewkb.length < @position
183
+ @ewkb.unpack("@#{i}#@double_mark#@double_mark@*")
184
+ end
185
+
186
+ def read_double
187
+ i = @position
188
+ @position += 8
189
+ raise EWKBFormatError::new("Truncated data") if @ewkb.length < @position
190
+ @ewkb.unpack("@#{i}#@double_mark@*").first
191
+ end
192
+
193
+ def read_uint
194
+ i = @position
195
+ @position += 4
196
+ raise EWKBFormatError::new("Truncated data") if @ewkb.length < @position
197
+ @ewkb.unpack("@#{i}#@uint_mark@*").first
198
+ end
199
+
200
+ def read_byte
201
+ i = @position
202
+ @position += 1
203
+ raise EWKBFormatError::new("Truncated data") if @ewkb.length < @position
204
+ @ewkb.unpack("@#{i}C@*").first
205
+ end
206
+
207
+ def endianness=(byte_order)
208
+ if(byte_order == NDR)
209
+ @uint_mark="V"
210
+ @double_mark="E"
211
+ elsif(byte_order == XDR)
212
+ @uint_mark="N"
213
+ @double_mark="G"
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,336 @@
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
+ require 'strscan'
11
+
12
+ module GeoRuby
13
+ module SimpleFeatures
14
+
15
+ #Raised when an error in the EWKT string is detected
16
+ class EWKTFormatError < StandardError
17
+ end
18
+
19
+ #Parses EWKT strings and notifies of events (such as the beginning of the definition of geometry, the value of the SRID...) the factory passed as argument to the constructor.
20
+ #
21
+ #=Example
22
+ # factory = GeometryFactory::new
23
+ # ewkt_parser = EWKTParser::new(factory)
24
+ # ewkt_parser.parse(<EWKT String>)
25
+ # geometry = @factory.geometry
26
+ #
27
+ #You can also use directly the static method Geometry.from_ewkt
28
+ class EWKTParser
29
+
30
+ def initialize(factory)
31
+ @factory = factory
32
+ @parse_options ={
33
+ "POINT" => method(:parse_point),
34
+ "LINESTRING" => method(:parse_line_string),
35
+ "POLYGON" => method(:parse_polygon),
36
+ "MULTIPOINT" => method(:parse_multi_point),
37
+ "MULTILINESTRING" => method(:parse_multi_line_string),
38
+ "MULTIPOLYGON" => method(:parse_multi_polygon),
39
+ "GEOMETRYCOLLECTION" => method(:parse_geometry_collection)
40
+ }
41
+ end
42
+
43
+ #Parses the ewkt string passed as argument and notifies the factory of events
44
+ def parse(ewkt)
45
+ @factory.reset
46
+ @tokenizer_structure = TokenizerStructure.new(ewkt)
47
+ @with_z=false
48
+ @with_m=false
49
+ @is_3dm = false
50
+ parse_geometry(true)
51
+ @srid=nil
52
+ end
53
+
54
+ private
55
+ def parse_geometry(srid_allowed)
56
+
57
+ token = @tokenizer_structure.get_next_token
58
+ if token == 'SRID'
59
+ #SRID present
60
+ raise EWKTFormatError.new("SRID not allowed at this position") if(!srid_allowed)
61
+ if @tokenizer_structure.get_next_token != '='
62
+ raise EWKTFormatError.new("Invalid SRID expression")
63
+ else
64
+ @srid = @tokenizer_structure.get_next_token.to_i
65
+ raise EWKTFormatError.new("Invalid SRID separator") if @tokenizer_structure.get_next_token != ';'
66
+ geom_type = @tokenizer_structure.get_next_token
67
+ end
68
+
69
+ else
70
+ #to manage multi geometries : the srid is not present in sub_geometries, therefore we take the srid of the parent ; if it is the root, we take the default srid
71
+ @srid= @srid || DEFAULT_SRID
72
+ geom_type = token
73
+ end
74
+
75
+ if geom_type[-1] == ?M
76
+ @is_3dm=true
77
+ @with_m=true
78
+ geom_type.chop! #remove the M
79
+ end
80
+
81
+ if @parse_options.has_key?(geom_type)
82
+ @parse_options[geom_type].call
83
+ else
84
+ raise EWKTFormatError.new("Urecognized geometry type: #{geom_type}")
85
+ end
86
+ end
87
+
88
+ def parse_geometry_collection
89
+ if @tokenizer_structure.get_next_token !='('
90
+ raise EWKTFormatError.new('Invalid GeometryCollection')
91
+ end
92
+
93
+ @factory.begin_geometry(GeometryCollection,@srid)
94
+
95
+ token = ''
96
+ while token != ')'
97
+ parse_geometry(false)
98
+ token = @tokenizer_structure.get_next_token
99
+ if token.nil?
100
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
101
+ end
102
+ end
103
+
104
+ @factory.end_geometry(@with_z,@with_m)
105
+ end
106
+
107
+ def parse_multi_polygon
108
+ if @tokenizer_structure.get_next_token !='('
109
+ raise EWKTFormatError.new('Invalid MultiLineString')
110
+ end
111
+
112
+ @factory.begin_geometry(MultiPolygon,@srid)
113
+ token = ''
114
+ while token != ')'
115
+ parse_polygon
116
+ token = @tokenizer_structure.get_next_token
117
+ if token.nil?
118
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
119
+ end
120
+ end
121
+
122
+ @factory.end_geometry(@with_z,@with_m)
123
+ end
124
+
125
+ def parse_multi_line_string
126
+ if @tokenizer_structure.get_next_token !='('
127
+ raise EWKTFormatError.new('Invalid MultiLineString')
128
+ end
129
+
130
+ @factory.begin_geometry(MultiLineString,@srid)
131
+
132
+ token = ''
133
+ while token != ')'
134
+ parse_line_string
135
+ token = @tokenizer_structure.get_next_token
136
+ if token.nil?
137
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
138
+ end
139
+ end
140
+
141
+ @factory.end_geometry(@with_z,@with_m)
142
+ end
143
+
144
+ def parse_polygon
145
+ if @tokenizer_structure.get_next_token !='('
146
+ raise EWKTFormatError.new('Invalid Polygon')
147
+ end
148
+
149
+ @factory.begin_geometry(Polygon,@srid)
150
+
151
+ token = ''
152
+ while token != ')'
153
+ parse_linear_ring
154
+ token = @tokenizer_structure.get_next_token
155
+ if token.nil?
156
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
157
+ end
158
+ end
159
+
160
+ @factory.end_geometry(@with_z,@with_m)
161
+ end
162
+
163
+ #must support the PostGIS form and the one in the specification
164
+ def parse_multi_point
165
+ if @tokenizer_structure.get_next_token !='('
166
+ raise EWKTFormatError.new('Invalid MultiPoint')
167
+ end
168
+
169
+ token = @tokenizer_structure.check_next_token
170
+ if token == '('
171
+ #specification
172
+ @factory.begin_geometry(MultiPoint,@srid)
173
+
174
+ token = ''
175
+ while token != ')'
176
+ parse_point
177
+ token = @tokenizer_structure.get_next_token
178
+ if token.nil?
179
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
180
+ end
181
+ end
182
+
183
+ @factory.end_geometry(@with_z,@with_m)
184
+ else
185
+ #postgis
186
+ parse_point_list(MultiPoint)
187
+ end
188
+ end
189
+
190
+ def parse_linear_ring
191
+ if @tokenizer_structure.get_next_token !='('
192
+ raise EWKTFormatError.new('Invalid Linear ring')
193
+ end
194
+
195
+ parse_point_list(LinearRing)
196
+ end
197
+
198
+ def parse_line_string
199
+ if @tokenizer_structure.get_next_token !='('
200
+ raise EWKTFormatError.new('Invalid Line string')
201
+ end
202
+
203
+ parse_point_list(LineString)
204
+ end
205
+
206
+ #used to parse line_strings and linear_rings and the PostGIS form of multi_points
207
+ def parse_point_list(geometry_type)
208
+ @factory.begin_geometry(geometry_type,@srid)
209
+
210
+ token = ''
211
+ while token != ')'
212
+ @factory.begin_geometry(Point,@srid)
213
+ token = parse_coords
214
+ if token.nil?
215
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
216
+ end
217
+ @factory.end_geometry(@with_z,@with_m)
218
+ end
219
+
220
+ @factory.end_geometry(@with_z,@with_m)
221
+ end
222
+
223
+ def parse_point
224
+ if @tokenizer_structure.get_next_token !='('
225
+ raise EWKTFormatError.new('Invalid Point')
226
+ end
227
+
228
+ @factory.begin_geometry(Point,@srid)
229
+
230
+ token = parse_coords
231
+
232
+ if token != ')'
233
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
234
+ end
235
+
236
+ @factory.end_geometry(@with_z,@with_m)
237
+ end
238
+
239
+ def parse_coords
240
+ coords = Array.new
241
+ x = @tokenizer_structure.get_next_token
242
+ y = @tokenizer_structure.get_next_token
243
+
244
+ if x.nil? or y.nil?
245
+ raise EWKTFormatError.new("Bad Point format")
246
+ end
247
+
248
+ if @is_3dm
249
+ m = @tokenizer_structure.get_next_token
250
+
251
+ if m.nil? or m == ',' or m == ')'
252
+ raise EWKTFormatError.new("No M dimension found")
253
+ else
254
+ @factory.add_point_x_y_m(x.to_f,y.to_f,m.to_f)
255
+ @tokenizer_structure.get_next_token
256
+ end
257
+ else
258
+ z = @tokenizer_structure.get_next_token
259
+
260
+ if z.nil?
261
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
262
+ end
263
+
264
+ if z == ',' or z == ')'
265
+ #2D : no z no m
266
+ @factory.add_point_x_y(x.to_f,y.to_f)
267
+ z
268
+ else
269
+ m = @tokenizer_structure.get_next_token
270
+ if m.nil?
271
+ raise EWKTFormatError.new("EWKT string not correctly terminated")
272
+ end
273
+
274
+ if m == ',' or m ==')'
275
+ #3Dz : no m
276
+ @with_z = true
277
+ @factory.add_point_x_y_z(x.to_f,y.to_f,z.to_f)
278
+ m
279
+ else
280
+ #4D
281
+ @with_z = true
282
+ @with_m = true
283
+ @factory.add_point_x_y_z_m(x.to_f,y.to_f,z.to_f,m.to_f)
284
+ @tokenizer_structure.get_next_token
285
+ end
286
+ end
287
+ end
288
+ end
289
+ end
290
+
291
+ class TokenizerStructure
292
+
293
+ def initialize(ewkt)
294
+ @ewkt = ewkt
295
+ @scanner = StringScanner.new(ewkt)
296
+ @regex = /\s*([\w.-]+)s*/
297
+ end
298
+
299
+ def get_next_token
300
+ if @scanner.scan(@regex).nil?
301
+ if @scanner.eos?
302
+ nil
303
+ else
304
+ ch = @scanner.getch
305
+ while ch == ' '
306
+ ch = @scanner.getch
307
+ end
308
+ ch
309
+ end
310
+ else
311
+ @scanner[1]
312
+ end
313
+ end
314
+
315
+
316
+ def check_next_token
317
+ check = @scanner.check(@regex)
318
+ if check.nil?
319
+ if @scanner.eos?
320
+ nil
321
+ else
322
+ pos = @scanner.pos
323
+ while @ewkt[pos].chr == ' '
324
+ pos+=1
325
+ end
326
+ @ewkt[pos].chr
327
+ end
328
+ else
329
+ check
330
+ end
331
+ end
332
+
333
+ end
334
+
335
+ end
336
+ end