georuby 2.3.0 → 2.5.1

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 (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