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.
- checksums.yaml +4 -4
- data/README.md +16 -9
- data/Rakefile +13 -14
- data/lib/geo_ruby/ewk.rb +2 -0
- data/lib/geo_ruby/{simple_features → ewk}/ewkb_parser.rb +206 -218
- data/lib/geo_ruby/ewk/ewkt_parser.rb +321 -0
- data/lib/geo_ruby/geojson.rb +27 -32
- data/lib/geo_ruby/georss.rb +88 -66
- data/lib/geo_ruby/gpx.rb +113 -1
- data/lib/geo_ruby/kml.rb +43 -31
- data/lib/geo_ruby/shp.rb +1 -0
- data/lib/geo_ruby/shp4r/dbf.rb +7 -3
- data/lib/geo_ruby/shp4r/shp.rb +297 -284
- data/lib/geo_ruby/simple_features.rb +2 -6
- data/lib/geo_ruby/simple_features/circle.rb +15 -13
- data/lib/geo_ruby/simple_features/envelope.rb +84 -77
- data/lib/geo_ruby/simple_features/geometry.rb +89 -69
- data/lib/geo_ruby/simple_features/geometry_collection.rb +46 -43
- data/lib/geo_ruby/simple_features/geometry_factory.rb +50 -47
- data/lib/geo_ruby/simple_features/helper.rb +14 -14
- data/lib/geo_ruby/simple_features/line_string.rb +94 -97
- data/lib/geo_ruby/simple_features/linear_ring.rb +4 -9
- data/lib/geo_ruby/simple_features/multi_line_string.rb +18 -21
- data/lib/geo_ruby/simple_features/multi_point.rb +18 -20
- data/lib/geo_ruby/simple_features/multi_polygon.rb +19 -25
- data/lib/geo_ruby/simple_features/point.rb +134 -128
- data/lib/geo_ruby/simple_features/polygon.rb +60 -59
- data/lib/geo_ruby/version.rb +1 -1
- data/spec/data/geojson/feature.json +9 -0
- data/spec/data/geojson/feature_collection.json +3 -4
- data/spec/geo_ruby/{simple_features → ewk}/ewkb_parser_spec.rb +56 -57
- data/spec/geo_ruby/{simple_features → ewk}/ewkt_parser_spec.rb +62 -63
- data/spec/geo_ruby/geojson_spec.rb +34 -17
- data/spec/geo_ruby/georss_spec.rb +76 -64
- data/spec/geo_ruby/{gpx4r/gpx_spec.rb → gpx_spec.rb} +25 -25
- data/spec/geo_ruby/kml_spec.rb +40 -36
- data/spec/geo_ruby/shp4r/shp_spec.rb +51 -51
- data/spec/geo_ruby/simple_features/circle_spec.rb +2 -2
- data/spec/geo_ruby/simple_features/envelope_spec.rb +8 -8
- data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +26 -26
- data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +5 -5
- data/spec/geo_ruby/simple_features/geometry_spec.rb +8 -8
- data/spec/geo_ruby/simple_features/line_string_spec.rb +102 -102
- data/spec/geo_ruby/simple_features/linear_ring_spec.rb +7 -7
- data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +20 -20
- data/spec/geo_ruby/simple_features/multi_point_spec.rb +16 -16
- data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +19 -19
- data/spec/geo_ruby/simple_features/point_spec.rb +180 -174
- data/spec/geo_ruby/simple_features/polygon_spec.rb +55 -56
- data/spec/geo_ruby_spec.rb +4 -4
- data/spec/spec_helper.rb +19 -13
- metadata +10 -9
- data/lib/geo_ruby/gpx4r/gpx.rb +0 -118
- 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
|
data/lib/geo_ruby/geojson.rb
CHANGED
@@ -8,13 +8,12 @@ rescue LoadError
|
|
8
8
|
end
|
9
9
|
|
10
10
|
module GeoRuby
|
11
|
-
|
12
|
-
|
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
|
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
|
-
(
|
29
|
+
(id == other.id) &&
|
30
|
+
(geometry == other.geometry) &&
|
31
|
+
(properties == other.properties)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
|
34
|
-
def
|
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
|
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
|
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
|
69
|
-
{:
|
70
|
-
:features => features}.to_json(options)
|
65
|
+
def to_json(options = {})
|
66
|
+
({ type: 'FeatureCollection', features: features }).to_json
|
71
67
|
end
|
72
|
-
|
68
|
+
alias_method :as_geojson, :to_json
|
73
69
|
end
|
74
70
|
|
75
|
-
|
76
|
-
class
|
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',
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
136
|
+
nil
|
140
137
|
end
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end #GeoRuby
|
138
|
+
end # GeoJSONParser
|
139
|
+
end # GeoRuby
|