Class: RGeo::WKRep::WKTParser

Inherits:
Object
  • Object
show all
Defined in:
lib/rgeo/wkrep/wkt_parser.rb

Overview

This class provides the functionality of parsing a geometry from WKT (well-known text) format. You may also customize the parser to recognize PostGIS EWKT extensions to the input, or Simple Features Specification 1.2 extensions for Z and M coordinates.

To use this class, create an instance with the desired settings and customizations, and call the parse method.

Configuration options

You must provide each parser with an RGeo::Feature::FactoryGenerator. It should understand the configuration options :srid, :has_z_coordinate, and :has_m_coordinate. You may also pass a specific RGeo::Feature::Factory, or nil to specify the default Cartesian FactoryGenerator.

The following additional options are recognized. These can be passed to the constructor, or set on the object afterwards.

:support_ewkt

Activate support for PostGIS EWKT type tags, which appends an “M” to tags to indicate the presence of M but not Z, and also recognizes the SRID prefix. Default is false.

:support_wkt12

Activate support for SFS 1.2 extensions to the type codes, which use a “M”, “Z”, or “ZM” token to signal the presence of Z and M values in the data. SFS 1.2 types such as triangle, tin, and polyhedralsurface are NOT yet supported. Default is false.

:strict_wkt11

If true, parsing will proceed in SFS 1.1 strict mode, which disallows any values other than X or Y. This has no effect if support_ewkt or support_wkt12 are active. Default is false.

:ignore_extra_tokens

If true, extra tokens at the end of the data are ignored. If false (the default), extra tokens will trigger a parse error.

:default_srid

A SRID to pass to the factory generator if no SRID is present in the input. Defaults to nil (i.e. don't specify a SRID).

Instance Method Summary collapse

Constructor Details

#initialize(factory_generator_ = nil, opts_ = {}) ⇒ WKTParser

Create and configure a WKT parser. See the WKTParser documentation for the options that can be passed.


90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 90

def initialize(factory_generator_=nil, opts_={})
  if factory_generator_.kind_of?(Feature::Factory::Instance)
    @factory_generator = Feature::FactoryGenerator.single(factory_generator_)
    @exact_factory = factory_generator_
  elsif factory_generator_.respond_to?(:call)
    @factory_generator = factory_generator_
    @exact_factory = nil
  else
    @factory_generator = Cartesian.method(:preferred_factory)
    @exact_factory = nil
  end
  @support_ewkt = opts_[:support_ewkt] ? true : false
  @support_wkt12 = opts_[:support_wkt12] ? true : false
  @strict_wkt11 = @support_ewkt || @support_wkt12 ? false : opts_[:strict_wkt11] ? true : false
  @ignore_extra_tokens = opts_[:ignore_extra_tokens] ? true : false
  @default_srid = opts_[:default_srid]
end

Instance Method Details

#_check_factory_supportObject

:nodoc:


185
186
187
188
189
190
191
192
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 185

def _check_factory_support  # :nodoc:
  if @cur_expect_z && !@cur_factory_support_z
    raise Error::ParseError, "Geometry calls for Z coordinate but factory doesn't support it."
  end
  if @cur_expect_m && !@cur_factory_support_m
    raise Error::ParseError, "Geometry calls for M coordinate but factory doesn't support it."
  end
end

#_clean_scannerObject

:nodoc:


440
441
442
443
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 440

def _clean_scanner  # :nodoc:
  @_scanner = nil
  @cur_token = nil
end

#_ensure_factoryObject

:nodoc:


195
196
197
198
199
200
201
202
203
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 195

def _ensure_factory  # :nodoc:
  unless @cur_factory
    @cur_factory = @factory_generator.call(:srid => @cur_srid, :has_z_coordinate => @cur_expect_z, :has_m_coordinate => @cur_expect_m)
    @cur_factory_support_z = @cur_factory.property(:has_z_coordinate) ? true : false
    @cur_factory_support_m = @cur_factory.property(:has_m_coordinate) ? true : false
    _check_factory_support unless @cur_expect_z.nil?
  end
  @cur_factory
end

#_expect_token_type(type_) ⇒ Object

:nodoc:


446
447
448
449
450
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 446

def _expect_token_type(type_)  # :nodoc:
  unless type_ === @cur_token
    raise Error::ParseError, "#{type_.inspect} expected but #{@cur_token.inspect} found."
  end
end

#_next_tokenObject

:nodoc:


453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 453

def _next_token  # :nodoc:
  if @_scanner.scan_until(/\(|\)|\[|\]|,|[^\s\(\)\[\],]+/)
    token_ = @_scanner.matched
    case token_
    when /^[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?$/
      @cur_token = token_.to_f
    when /^[a-z]+$/
      @cur_token = token_
    when ','
      @cur_token = :comma
    when '(','['
      @cur_token = :begin
    when ']',')'
      @cur_token = :end
    else
      raise Error::ParseError, "Bad token: #{token_.inspect}"
    end
  else
    @cur_token = nil
  end
  @cur_token
end

#_parse_coordsObject

:nodoc:


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 263

def _parse_coords  # :nodoc:
  _expect_token_type(::Numeric)
  x_ = @cur_token
  _next_token
  _expect_token_type(::Numeric)
  y_ = @cur_token
  _next_token
  extra_ = []
  if @cur_expect_z.nil?
    while ::Numeric === @cur_token
      extra_ << @cur_token
      _next_token
    end
    num_extras_ = extra_.size
    @cur_expect_z = num_extras_ > 0 && (!@cur_factory || @cur_factory_support_z) ? true : false
    num_extras_ -= 1 if @cur_expect_z
    @cur_expect_m = num_extras_ > 0 && (!@cur_factory || @cur_factory_support_m) ? true : false
    num_extras_ -= 1 if @cur_expect_m
    if num_extras_ > 0
      raise Error::ParseError, "Found #{extra_.size+2} coordinates, which is too many for this factory."
    end
    _ensure_factory
  else
    val_ = 0
    if @cur_expect_z
      _expect_token_type(::Numeric)
      val_ = @cur_token
      _next_token
    end
    if @cur_factory_support_z
      extra_ << val_
    end
    val_ = 0
    if @cur_expect_m
      _expect_token_type(::Numeric)
      val_ = @cur_token
      _next_token
    end
    if @cur_factory_support_m
      extra_ << val_
    end
  end
  @cur_factory.point(x_, y_, *extra_)
end

#_parse_geometry_collectionObject

:nodoc:


360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 360

def _parse_geometry_collection  # :nodoc:
  geometries_ = []
  if @cur_token != 'empty'
    _expect_token_type(:begin)
    _next_token
    loop do
      geometries_ << _parse_type_tag(true)
      break if @cur_token == :end
      _expect_token_type(:comma)
      _next_token
    end
  end
  _next_token
  _ensure_factory.collection(geometries_)
end

#_parse_line_stringObject

:nodoc:


323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 323

def _parse_line_string  # :nodoc:
  points_ = []
  if @cur_token != 'empty'
    _expect_token_type(:begin)
    _next_token
    loop do
      points_ << _parse_coords
      break if @cur_token == :end
      _expect_token_type(:comma)
      _next_token
    end
  end
  _next_token
  _ensure_factory.line_string(points_)
end

#_parse_multi_line_stringObject

:nodoc:


400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 400

def _parse_multi_line_string  # :nodoc:
  line_strings_ = []
  if @cur_token != 'empty'
    _expect_token_type(:begin)
    _next_token
    loop do
      line_strings_ << _parse_line_string
      break if @cur_token == :end
      _expect_token_type(:comma)
      _next_token
    end
  end
  _next_token
  _ensure_factory.multi_line_string(line_strings_)
end

#_parse_multi_pointObject

:nodoc:


377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 377

def _parse_multi_point  # :nodoc:
  points_ = []
  if @cur_token != 'empty'
    _expect_token_type(:begin)
    _next_token
    loop do
      uses_paren_ = @cur_token == :begin
      _next_token if uses_paren_
      points_ << _parse_coords
      if uses_paren_
        _expect_token_type(:end)
        _next_token
      end
      break if @cur_token == :end
      _expect_token_type(:comma)
      _next_token
    end
  end
  _next_token
  _ensure_factory.multi_point(points_)
end

#_parse_multi_polygonObject

:nodoc:


417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 417

def _parse_multi_polygon  # :nodoc:
  polygons_ = []
  if @cur_token != 'empty'
    _expect_token_type(:begin)
    _next_token
    loop do
      polygons_ << _parse_polygon
      break if @cur_token == :end
      _expect_token_type(:comma)
      _next_token
    end
  end
  _next_token
  _ensure_factory.multi_polygon(polygons_)
end

#_parse_point(convert_empty_ = false) ⇒ Object

:nodoc:


309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 309

def _parse_point(convert_empty_=false)  # :nodoc:
  if convert_empty_ && @cur_token == 'empty'
    point_ = _ensure_factory.multi_point([])
  else
    _expect_token_type(:begin)
    _next_token
    point_ = _parse_coords
    _expect_token_type(:end)
  end
  _next_token
  point_
end

#_parse_polygonObject

:nodoc:


340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 340

def _parse_polygon  # :nodoc:
  inner_rings_ = []
  if @cur_token == 'empty'
    outer_ring_ = _ensure_factory.linear_ring([])
  else
    _expect_token_type(:begin)
    _next_token
    outer_ring_ = _parse_line_string
    loop do
      break if @cur_token == :end
      _expect_token_type(:comma)
      _next_token
      inner_rings_ << _parse_line_string
    end
  end
  _next_token
  _ensure_factory.polygon(outer_ring_, inner_rings_)
end

#_parse_type_tag(contained_) ⇒ Object

:nodoc:


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 206

def _parse_type_tag(contained_)  # :nodoc:
  _expect_token_type(::String)
  if @support_ewkt && @cur_token =~ /^(.+)(m)$/
    type_ = $1
    zm_ = $2
  else
    type_ = @cur_token
    zm_ = ''
  end
  _next_token
  if zm_.length == 0 && @support_wkt12 && @cur_token.kind_of?(::String) && @cur_token =~ /^z?m?$/
    zm_ = @cur_token
    _next_token
  end
  if zm_.length > 0 || @strict_wkt11
    creating_expectation_ = @cur_expect_z.nil?
    expect_z_ = zm_[0,1] == 'z' ? true : false
    if @cur_expect_z.nil?
      @cur_expect_z = expect_z_
    elsif expect_z_ != @cur_expect_z
      raise Error::ParseError, "Surrounding collection has Z but contained geometry doesn't."
    end
    expect_m_ = zm_[-1,1] == 'm' ? true : false
    if @cur_expect_m.nil?
      @cur_expect_m = expect_m_
    elsif expect_m_ != @cur_expect_m
      raise Error::ParseError, "Surrounding collection has M but contained geometry doesn't."
    end
    if creating_expectation_
      if @cur_factory
        _check_factory_support
      else
        _ensure_factory
      end
    end
  end
  case type_
  when 'point'
    _parse_point(true)
  when 'linestring'
    _parse_line_string
  when 'polygon'
    _parse_polygon
  when 'geometrycollection'
    _parse_geometry_collection
  when 'multipoint'
    _parse_multi_point
  when 'multilinestring'
    _parse_multi_line_string
  when 'multipolygon'
    _parse_multi_polygon
  else
    raise Error::ParseError, "Unknown type tag: #{type_.inspect}."
  end
end

#_propertiesObject

:nodoc:


145
146
147
148
149
150
151
152
153
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 145

def _properties  # :nodoc:
  {
    'support_ewkt' => @support_ewkt,
    'support_wkt12' => @support_wkt12,
    'strict_wkt11' => @strict_wkt11,
    'ignore_extra_tokens' => @ignore_extra_tokens,
    'default_srid' => @default_srid,
  }
end

#_start_scanner(str_) ⇒ Object

:nodoc:


434
435
436
437
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 434

def _start_scanner(str_)  # :nodoc:
  @_scanner = ::StringScanner.new(str_)
  _next_token
end

#exact_factoryObject

If this parser was given an exact factory, returns it; otherwise returns nil.


116
117
118
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 116

def exact_factory
  @exact_factory
end

#factory_generatorObject

Returns the factory generator. See WKTParser for details.


110
111
112
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 110

def factory_generator
  @factory_generator
end

#ignore_extra_tokens?Boolean

Returns true if this parser ignores extra tokens. See WKTParser for details.


140
141
142
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 140

def ignore_extra_tokens?
  @ignore_extra_tokens
end

#parse(str_) ⇒ Object

Parse the given string, and return a geometry object.


158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 158

def parse(str_)
  str_ = str_.downcase
  @cur_factory = @exact_factory
  if @cur_factory
    @cur_factory_support_z = @cur_factory.property(:has_z_coordinate) ? true : false
    @cur_factory_support_m = @cur_factory.property(:has_m_coordinate) ? true : false
  end
  @cur_expect_z = nil
  @cur_expect_m = nil
  @cur_srid = @default_srid
  if @support_ewkt && str_ =~ /^srid=(\d+);/i
    str_ = $'
    @cur_srid = $1.to_i
  end
  begin
    _start_scanner(str_)
    obj_ = _parse_type_tag(false)
    if @cur_token && !@ignore_extra_tokens
      raise Error::ParseError, "Extra tokens beginning with #{@cur_token.inspect}."
    end
  ensure
    _clean_scanner
  end
  obj_
end

#strict_wkt11?Boolean

Returns true if this parser strictly adheres to WKT 1.1. See WKTParser for details.


134
135
136
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 134

def strict_wkt11?
  @strict_wkt11
end

#support_ewkt?Boolean

Returns true if this parser supports EWKT. See WKTParser for details.


122
123
124
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 122

def support_ewkt?
  @support_ewkt
end

#support_wkt12?Boolean

Returns true if this parser supports SFS 1.2 extensions. See WKTParser for details.


128
129
130
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 128

def support_wkt12?
  @support_wkt12
end