georuby 2.3.0 → 2.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -9
  3. data/Rakefile +13 -14
  4. data/lib/geo_ruby/ewk.rb +2 -0
  5. data/lib/geo_ruby/{simple_features → ewk}/ewkb_parser.rb +206 -218
  6. data/lib/geo_ruby/ewk/ewkt_parser.rb +321 -0
  7. data/lib/geo_ruby/geojson.rb +27 -32
  8. data/lib/geo_ruby/georss.rb +88 -66
  9. data/lib/geo_ruby/gpx.rb +113 -1
  10. data/lib/geo_ruby/kml.rb +43 -31
  11. data/lib/geo_ruby/shp.rb +1 -0
  12. data/lib/geo_ruby/shp4r/dbf.rb +7 -3
  13. data/lib/geo_ruby/shp4r/shp.rb +297 -284
  14. data/lib/geo_ruby/simple_features.rb +2 -6
  15. data/lib/geo_ruby/simple_features/circle.rb +15 -13
  16. data/lib/geo_ruby/simple_features/envelope.rb +84 -77
  17. data/lib/geo_ruby/simple_features/geometry.rb +89 -69
  18. data/lib/geo_ruby/simple_features/geometry_collection.rb +46 -43
  19. data/lib/geo_ruby/simple_features/geometry_factory.rb +50 -47
  20. data/lib/geo_ruby/simple_features/helper.rb +14 -14
  21. data/lib/geo_ruby/simple_features/line_string.rb +94 -97
  22. data/lib/geo_ruby/simple_features/linear_ring.rb +4 -9
  23. data/lib/geo_ruby/simple_features/multi_line_string.rb +18 -21
  24. data/lib/geo_ruby/simple_features/multi_point.rb +18 -20
  25. data/lib/geo_ruby/simple_features/multi_polygon.rb +19 -25
  26. data/lib/geo_ruby/simple_features/point.rb +134 -128
  27. data/lib/geo_ruby/simple_features/polygon.rb +60 -59
  28. data/lib/geo_ruby/version.rb +1 -1
  29. data/spec/data/geojson/feature.json +9 -0
  30. data/spec/data/geojson/feature_collection.json +3 -4
  31. data/spec/geo_ruby/{simple_features → ewk}/ewkb_parser_spec.rb +56 -57
  32. data/spec/geo_ruby/{simple_features → ewk}/ewkt_parser_spec.rb +62 -63
  33. data/spec/geo_ruby/geojson_spec.rb +34 -17
  34. data/spec/geo_ruby/georss_spec.rb +76 -64
  35. data/spec/geo_ruby/{gpx4r/gpx_spec.rb → gpx_spec.rb} +25 -25
  36. data/spec/geo_ruby/kml_spec.rb +40 -36
  37. data/spec/geo_ruby/shp4r/shp_spec.rb +51 -51
  38. data/spec/geo_ruby/simple_features/circle_spec.rb +2 -2
  39. data/spec/geo_ruby/simple_features/envelope_spec.rb +8 -8
  40. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +26 -26
  41. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +5 -5
  42. data/spec/geo_ruby/simple_features/geometry_spec.rb +8 -8
  43. data/spec/geo_ruby/simple_features/line_string_spec.rb +102 -102
  44. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +7 -7
  45. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +20 -20
  46. data/spec/geo_ruby/simple_features/multi_point_spec.rb +16 -16
  47. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +19 -19
  48. data/spec/geo_ruby/simple_features/point_spec.rb +180 -174
  49. data/spec/geo_ruby/simple_features/polygon_spec.rb +55 -56
  50. data/spec/geo_ruby_spec.rb +4 -4
  51. data/spec/spec_helper.rb +19 -13
  52. metadata +10 -9
  53. data/lib/geo_ruby/gpx4r/gpx.rb +0 -118
  54. data/lib/geo_ruby/simple_features/ewkt_parser.rb +0 -336
@@ -0,0 +1,321 @@
1
+ require 'strscan'
2
+
3
+ module GeoRuby
4
+ module SimpleFeatures
5
+ # Raised when an error in the EWKT string is detected
6
+ class EWKTFormatError < StandardError
7
+ end
8
+
9
+ # 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.
10
+ #
11
+ # =Example
12
+ # factory = GeometryFactory::new
13
+ # ewkt_parser = EWKTParser::new(factory)
14
+ # ewkt_parser.parse(<EWKT String>)
15
+ # geometry = @factory.geometry
16
+ #
17
+ # You can also use directly the static method Geometry.from_ewkt
18
+ class EWKTParser
19
+ def initialize(factory)
20
+ @factory = factory
21
+ @parse_options = {
22
+ 'POINT' => method(:parse_point),
23
+ 'LINESTRING' => method(:parse_line_string),
24
+ 'POLYGON' => method(:parse_polygon),
25
+ 'MULTIPOINT' => method(:parse_multi_point),
26
+ 'MULTILINESTRING' => method(:parse_multi_line_string),
27
+ 'MULTIPOLYGON' => method(:parse_multi_polygon),
28
+ 'GEOMETRYCOLLECTION' => method(:parse_geometry_collection)
29
+ }
30
+ end
31
+
32
+ # Parses the ewkt string passed as argument and notifies the factory of events
33
+ def parse(ewkt)
34
+ @factory.reset
35
+ @tokenizer_structure = TokenizerStructure.new(ewkt)
36
+ @with_z = false
37
+ @with_m = false
38
+ @is_3dm = false
39
+ parse_geometry(true)
40
+ @srid = nil
41
+ end
42
+
43
+ private
44
+
45
+ def parse_geometry(srid_allowed)
46
+ token = @tokenizer_structure.get_next_token
47
+ if token == 'SRID'
48
+ # SRID present
49
+ fail EWKTFormatError.new('SRID not allowed at this position') unless srid_allowed
50
+ if @tokenizer_structure.get_next_token != '='
51
+ fail EWKTFormatError.new('Invalid SRID expression')
52
+ else
53
+ @srid = @tokenizer_structure.get_next_token.to_i
54
+ fail EWKTFormatError.new('Invalid SRID separator') if @tokenizer_structure.get_next_token != ';'
55
+ geom_type = @tokenizer_structure.get_next_token
56
+ end
57
+
58
+ else
59
+ # 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
60
+ @srid = @srid || DEFAULT_SRID
61
+ geom_type = token
62
+ end
63
+
64
+ if geom_type[-1] == 'M'
65
+ @is_3dm = true
66
+ @with_m = true
67
+ geom_type.chop! # remove the M
68
+ end
69
+
70
+ if @parse_options.key?(geom_type)
71
+ @parse_options[geom_type].call
72
+ else
73
+ fail EWKTFormatError.new("Urecognized geometry type: #{geom_type}")
74
+ end
75
+ end
76
+
77
+ def parse_geometry_collection
78
+ if @tokenizer_structure.get_next_token != '('
79
+ fail EWKTFormatError.new('Invalid GeometryCollection')
80
+ end
81
+
82
+ @factory.begin_geometry(GeometryCollection, @srid)
83
+
84
+ token = ''
85
+ while token != ')'
86
+ parse_geometry(false)
87
+ token = @tokenizer_structure.get_next_token
88
+ if token.nil?
89
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
90
+ end
91
+ end
92
+
93
+ @factory.end_geometry(@with_z, @with_m)
94
+ end
95
+
96
+ def parse_multi_polygon
97
+ if @tokenizer_structure.get_next_token != '('
98
+ fail EWKTFormatError.new('Invalid MultiLineString')
99
+ end
100
+
101
+ @factory.begin_geometry(MultiPolygon, @srid)
102
+ token = ''
103
+ while token != ')'
104
+ parse_polygon
105
+ token = @tokenizer_structure.get_next_token
106
+ if token.nil?
107
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
108
+ end
109
+ end
110
+
111
+ @factory.end_geometry(@with_z, @with_m)
112
+ end
113
+
114
+ def parse_multi_line_string
115
+ if @tokenizer_structure.get_next_token != '('
116
+ fail EWKTFormatError.new('Invalid MultiLineString')
117
+ end
118
+
119
+ @factory.begin_geometry(MultiLineString, @srid)
120
+
121
+ token = ''
122
+ while token != ')'
123
+ parse_line_string
124
+ token = @tokenizer_structure.get_next_token
125
+ if token.nil?
126
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
127
+ end
128
+ end
129
+
130
+ @factory.end_geometry(@with_z, @with_m)
131
+ end
132
+
133
+ def parse_polygon
134
+ if @tokenizer_structure.get_next_token != '('
135
+ fail EWKTFormatError.new('Invalid Polygon')
136
+ end
137
+
138
+ @factory.begin_geometry(Polygon, @srid)
139
+
140
+ token = ''
141
+ while token != ')'
142
+ parse_linear_ring
143
+ token = @tokenizer_structure.get_next_token
144
+ if token.nil?
145
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
146
+ end
147
+ end
148
+
149
+ @factory.end_geometry(@with_z, @with_m)
150
+ end
151
+
152
+ # must support the PostGIS form and the one in the specification
153
+ def parse_multi_point
154
+ if @tokenizer_structure.get_next_token != '('
155
+ fail EWKTFormatError.new('Invalid MultiPoint')
156
+ end
157
+
158
+ token = @tokenizer_structure.check_next_token
159
+ if token == '('
160
+ # specification
161
+ @factory.begin_geometry(MultiPoint, @srid)
162
+
163
+ token = ''
164
+ while token != ')'
165
+ parse_point
166
+ token = @tokenizer_structure.get_next_token
167
+ if token.nil?
168
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
169
+ end
170
+ end
171
+
172
+ @factory.end_geometry(@with_z, @with_m)
173
+ else
174
+ # postgis
175
+ parse_point_list(MultiPoint)
176
+ end
177
+ end
178
+
179
+ def parse_linear_ring
180
+ if @tokenizer_structure.get_next_token != '('
181
+ fail EWKTFormatError.new('Invalid Linear ring')
182
+ end
183
+
184
+ parse_point_list(LinearRing)
185
+ end
186
+
187
+ def parse_line_string
188
+ if @tokenizer_structure.get_next_token != '('
189
+ fail EWKTFormatError.new('Invalid Line string')
190
+ end
191
+
192
+ parse_point_list(LineString)
193
+ end
194
+
195
+ # used to parse line_strings and linear_rings and the PostGIS form of multi_points
196
+ def parse_point_list(geometry_type)
197
+ @factory.begin_geometry(geometry_type, @srid)
198
+
199
+ token = ''
200
+ while token != ')'
201
+ @factory.begin_geometry(Point, @srid)
202
+ token = parse_coords
203
+ if token.nil?
204
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
205
+ end
206
+ @factory.end_geometry(@with_z, @with_m)
207
+ end
208
+
209
+ @factory.end_geometry(@with_z, @with_m)
210
+ end
211
+
212
+ def parse_point
213
+ if @tokenizer_structure.get_next_token != '('
214
+ fail EWKTFormatError.new('Invalid Point')
215
+ end
216
+
217
+ @factory.begin_geometry(Point, @srid)
218
+
219
+ token = parse_coords
220
+
221
+ if token != ')'
222
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
223
+ end
224
+
225
+ @factory.end_geometry(@with_z, @with_m)
226
+ end
227
+
228
+ def parse_coords
229
+ coords = []
230
+ x = @tokenizer_structure.get_next_token
231
+ y = @tokenizer_structure.get_next_token
232
+
233
+ if x.nil? || y.nil?
234
+ fail EWKTFormatError.new('Bad Point format')
235
+ end
236
+
237
+ if @is_3dm
238
+ m = @tokenizer_structure.get_next_token
239
+
240
+ if m.nil? || m == ',' || m == ')'
241
+ fail EWKTFormatError.new('No M dimension found')
242
+ else
243
+ @factory.add_point_x_y_m(x.to_f, y.to_f, m.to_f)
244
+ @tokenizer_structure.get_next_token
245
+ end
246
+ else
247
+ z = @tokenizer_structure.get_next_token
248
+
249
+ if z.nil?
250
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
251
+ end
252
+
253
+ if z == ',' || z == ')'
254
+ # 2D : no z no m
255
+ @factory.add_point_x_y(x.to_f, y.to_f)
256
+ z
257
+ else
258
+ m = @tokenizer_structure.get_next_token
259
+ if m.nil?
260
+ fail EWKTFormatError.new('EWKT string not correctly terminated')
261
+ end
262
+
263
+ if m == ',' || m == ')'
264
+ # 3Dz : no m
265
+ @with_z = true
266
+ @factory.add_point_x_y_z(x.to_f, y.to_f, z.to_f)
267
+ m
268
+ else
269
+ # 4D
270
+ @with_z = true
271
+ @with_m = true
272
+ @factory.add_point_x_y_z_m(x.to_f, y.to_f, z.to_f, m.to_f)
273
+ @tokenizer_structure.get_next_token
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ class TokenizerStructure
281
+ def initialize(ewkt)
282
+ @ewkt = ewkt
283
+ @scanner = StringScanner.new(ewkt)
284
+ @regex = /\s*([\w.-]+)s*/
285
+ end
286
+
287
+ def get_next_token
288
+ if @scanner.scan(@regex).nil?
289
+ if @scanner.eos?
290
+ nil
291
+ else
292
+ ch = @scanner.getch
293
+ while ch == ' '
294
+ ch = @scanner.getch
295
+ end
296
+ ch
297
+ end
298
+ else
299
+ @scanner[1]
300
+ end
301
+ end
302
+
303
+ def check_next_token
304
+ check = @scanner.check(@regex)
305
+ if check.nil?
306
+ if @scanner.eos?
307
+ nil
308
+ else
309
+ pos = @scanner.pos
310
+ while @ewkt[pos].chr == ' '
311
+ pos += 1
312
+ end
313
+ @ewkt[pos].chr
314
+ end
315
+ else
316
+ check
317
+ end
318
+ end
319
+ end
320
+ end
321
+ end
@@ -8,13 +8,12 @@ rescue LoadError
8
8
  end
9
9
 
10
10
  module GeoRuby
11
-
12
- #Raised when an error in the GeoJSON string is detected
13
- class GeojsonFormatError < StandardError
11
+ # Raised when an error in the GeoJSON string is detected
12
+ class GeoJSONFormatError < StandardError
14
13
  end
15
14
 
16
15
  # Class added to support geojson 'Feature' objects
17
- class GeojsonFeature
16
+ class GeoJSONFeature
18
17
  attr_accessor :geometry, :properties, :id
19
18
 
20
19
  def initialize(geometry, properties = {}, id = nil)
@@ -27,11 +26,13 @@ module GeoRuby
27
26
  if (self.class != other.class)
28
27
  false
29
28
  else
30
- (self.id == other.id) && (self.geometry == other.geometry) && (self.properties == other.properties)
29
+ (id == other.id) &&
30
+ (geometry == other.geometry) &&
31
+ (properties == other.properties)
31
32
  end
32
33
  end
33
34
 
34
- def as_json(options={})
35
+ def to_json(options = {})
35
36
  output = {}
36
37
  output[:type] = 'Feature'
37
38
  output[:geometry] = geometry
@@ -39,15 +40,11 @@ module GeoRuby
39
40
  output[:id] = id unless id.nil?
40
41
  output.to_json(options)
41
42
  end
42
-
43
- def to_json(options = {})
44
- as_json(options).to_json
45
- end
46
- alias :as_geojson :to_json
43
+ alias_method :as_geojson, :to_json
47
44
  end
48
45
 
49
46
  # Class added to support geojson 'Feature Collection' objects
50
- class GeojsonFeatureCollection
47
+ class GeoJSONFeatureCollection
51
48
  attr_accessor :features
52
49
 
53
50
  def initialize(features)
@@ -59,25 +56,24 @@ module GeoRuby
59
56
  return false
60
57
  else
61
58
  features.each_index do |index|
62
- return false if self.features[index] != other.features[index]
59
+ return false if features[index] != other.features[index]
63
60
  end
64
61
  end
65
62
  true
66
63
  end
67
64
 
68
- def as_json(options = {})
69
- {:type => 'FeatureCollection',
70
- :features => features}.to_json(options)
65
+ def to_json(options = {})
66
+ ({ type: 'FeatureCollection', features: features }).to_json
71
67
  end
72
- alias :as_geojson :as_json
68
+ alias_method :as_geojson, :to_json
73
69
  end
74
70
 
75
-
76
- class GeojsonParser
71
+ # GeoJSON main parser
72
+ class GeoJSONParser
77
73
  include GeoRuby::SimpleFeatures
78
74
  attr_reader :geometry
79
75
 
80
- def parse(geojson, srid=DEFAULT_SRID)
76
+ def parse(geojson, srid = DEFAULT_SRID)
81
77
  @geometry = nil
82
78
  geohash = JSON.parse(geojson)
83
79
  parse_geohash(geohash, srid)
@@ -88,14 +84,15 @@ module GeoRuby
88
84
  def parse_geohash(geohash, srid)
89
85
  srid = srid_from_crs(geohash['crs']) || srid
90
86
  case geohash['type']
91
- when 'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'
87
+ when 'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon',
88
+ 'MultiPolygon', 'GeometryCollection'
92
89
  @geometry = parse_geometry(geohash, srid)
93
90
  when 'Feature'
94
91
  @geometry = parse_geojson_feature(geohash, srid)
95
92
  when 'FeatureCollection'
96
93
  @geometry = parse_geojson_feature_collection(geohash, srid)
97
94
  else
98
- GeojsonFormatError.new('Unknown GeoJSON type')
95
+ fail GeoJSONFormatError, 'Unknown GeoJSON type'
99
96
  end
100
97
  end
101
98
 
@@ -111,14 +108,14 @@ module GeoRuby
111
108
 
112
109
  def parse_geometry_collection(geohash, srid)
113
110
  srid = srid_from_crs(geohash['crs']) || srid
114
- geometries = geohash['geometries'].map{|g| parse_geometry(g,srid)}
115
- GeometryCollection.from_geometries(geometries,srid)
111
+ geometries = geohash['geometries'].map { |g| parse_geometry(g, srid) }
112
+ GeometryCollection.from_geometries(geometries, srid)
116
113
  end
117
114
 
118
115
  def parse_geojson_feature(geohash, srid)
119
116
  srid = srid_from_crs(geohash['crs']) || srid
120
- geometry = parse_geometry(geohash['geometry'],srid)
121
- GeojsonFeature.new(geometry, geohash['properties'], geohash['id'])
117
+ geometry = parse_geometry(geohash['geometry'], srid)
118
+ GeoJSONFeature.new(geometry, geohash['properties'], geohash['id'])
122
119
  end
123
120
 
124
121
  def parse_geojson_feature_collection(geohash, srid)
@@ -127,7 +124,7 @@ module GeoRuby
127
124
  geohash['features'].each do |feature|
128
125
  features << parse_geojson_feature(feature, srid)
129
126
  end
130
- GeojsonFeatureCollection.new(features)
127
+ GeoJSONFeatureCollection.new(features)
131
128
  end
132
129
 
133
130
  def srid_from_crs(crs)
@@ -136,9 +133,7 @@ module GeoRuby
136
133
  urn = crs['properties']['urn'].split(':')
137
134
  return urn.last if urn[4] == 'EPSG'
138
135
  end
139
- return nil
136
+ nil
140
137
  end
141
-
142
- end #GeojsonParser
143
-
144
- end #GeoRuby
138
+ end # GeoJSONParser
139
+ end # GeoRuby