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
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 90

def initialize(factory_generator_=nil, opts_={})
  self.factory_generator = factory_generator_
  @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:



205
206
207
208
209
210
211
212
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 205

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:



454
455
456
457
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 454

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

#_ensure_factoryObject

:nodoc:



215
216
217
218
219
220
221
222
223
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 215

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:



460
461
462
463
464
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 460

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:



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 467

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:



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 283

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:



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

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:



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

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:



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

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:



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

def _parse_multi_point  # :nodoc:
  points_ = []
  if @cur_token != 'empty'
    _expect_token_type(:begin)
    _next_token
    loop do
      points_ << _parse_point
      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:



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 431

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:



329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 329

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:



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

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:



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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 226

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_
    else 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

#_start_scanner(str_) ⇒ Object

:nodoc:



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

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.



107
108
109
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 107

def exact_factory
  @exact_factory
end

#factory_generatorObject

Returns the factory generator. See WKTParser for details.



101
102
103
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 101

def factory_generator
  @factory_generator
end

#factory_generator=(value_) ⇒ Object

Sets the factory_generator. See WKTParser for details.



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 112

def factory_generator=(value_)
  if value_.kind_of?(Feature::Factory::Instance)
    @factory_generator = Feature::FactoryGenerator.single(value_)
    @exact_factory = value_
  elsif value_.respond_to?(:call)
    @factory_generator = value_
    @exact_factory = nil
  else
    @factory_generator = Cartesian.method(:preferred_factory)
    @exact_factory = nil
  end
end

#ignore_extra_tokens=(value_) ⇒ Object

Sets the the ignore_extra_tokens flag. See WKTParser for details.



171
172
173
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 171

def ignore_extra_tokens=(value_)
  @ignore_extra_tokens = value_ ? true : false
end

#ignore_extra_tokens?Boolean

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

Returns:

  • (Boolean)


166
167
168
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 166

def ignore_extra_tokens?
  @ignore_extra_tokens
end

#parse(str_) ⇒ Object

Parse the given string, and return a geometry object.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 178

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=(value_) ⇒ Object

Sets the the strict_wkt11 flag. See WKTParser for details.



160
161
162
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 160

def strict_wkt11=(value_)
  @strict_wkt11 = value_ ? true : false
end

#strict_wkt11?Boolean

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

Returns:

  • (Boolean)


155
156
157
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 155

def strict_wkt11?
  @strict_wkt11
end

#support_ewkt=(value_) ⇒ Object

Sets the the support_ewkt flag. See WKTParser for details.



138
139
140
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 138

def support_ewkt=(value_)
  @support_ewkt = value_ ? true : false
end

#support_ewkt?Boolean

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

Returns:

  • (Boolean)


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

def support_ewkt?
  @support_ewkt
end

#support_wkt12=(value_) ⇒ Object

Sets the the support_wkt12 flag. See WKTParser for details.



149
150
151
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 149

def support_wkt12=(value_)
  @support_wkt12 = value_ ? true : false
end

#support_wkt12?Boolean

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

Returns:

  • (Boolean)


144
145
146
# File 'lib/rgeo/wkrep/wkt_parser.rb', line 144

def support_wkt12?
  @support_wkt12
end

#to_generate_factory(&block_) ⇒ Object

Sets the factory_generator to the given block. See WKTParser for details.



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

def to_generate_factory(&block_)
  self.factory_generator = block_
end