Class: RGeo::WKRep::WKBParser

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

Overview

This class provides the functionality of parsing a geometry from WKB (well-known binary) format. You may also customize the parser to recognize PostGIS EWKB 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_ewkb

Activate support for PostGIS EWKB type codes, which use high order bits in the type code to signal the presence of Z, M, and SRID values in the data. Default is false.

:support_wkb12

Activate support for SFS 1.2 extensions to the type codes, which use values greater than 1000 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.

:ignore_extra_bytes

If true, extra bytes at the end of the data are ignored. If false (the default), extra bytes 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_ = {}) ⇒ WKBParser

Create and configure a WKB parser. See the WKBParser documentation for the options that can be passed.



83
84
85
86
87
88
89
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 83

def initialize(factory_generator_=nil, opts_={})
  self.factory_generator = factory_generator_
  @support_ewkb = opts_[:support_ewkb] ? true : false
  @support_wkb12 = opts_[:support_wkb12] ? true : false
  @ignore_extra_bytes = opts_[:ignore_extra_bytes] ? true : false
  @default_srid = opts_[:default_srid]
end

Instance Method Details

#_bytes_remainingObject

:nodoc:



274
275
276
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 274

def _bytes_remaining  # :nodoc:
  @_len - @_pos
end

#_clean_scannerObject

:nodoc:



269
270
271
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 269

def _clean_scanner  # :nodoc:
  @_data = nil
end

#_get_byteObject

:nodoc:



279
280
281
282
283
284
285
286
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 279

def _get_byte  # :nodoc:
  if @_pos + 1 > @_len
    raise Error::ParseError, "Not enough bytes left to fulfill 1 byte"
  end
  str_ = @_data[@_pos, 1]
  @_pos += 1
  str_.unpack("C").first
end

#_get_doubles(little_endian_, count_) ⇒ Object

:nodoc:



299
300
301
302
303
304
305
306
307
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 299

def _get_doubles(little_endian_, count_)  # :nodoc:
  len_ = 8 * count_
  if @_pos + len_ > @_len
    raise Error::ParseError, "Not enough bytes left to fulfill #{count_} doubles"
  end
  str_ = @_data[@_pos, len_]
  @_pos += len_
  str_.unpack("#{little_endian_ ? 'E' : 'G'}*")
end

#_get_integer(little_endian_) ⇒ Object

:nodoc:



289
290
291
292
293
294
295
296
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 289

def _get_integer(little_endian_)  # :nodoc:
  if @_pos + 4 > @_len
    raise Error::ParseError, "Not enough bytes left to fulfill 1 integer"
  end
  str_ = @_data[@_pos, 4]
  @_pos += 4
  str_.unpack("#{little_endian_ ? 'V' : 'N'}").first
end

#_parse_line_string(little_endian_) ⇒ Object

:nodoc:



255
256
257
258
259
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 255

def _parse_line_string(little_endian_)  # :nodoc:
  count_ = _get_integer(little_endian_)
  coords_ = _get_doubles(little_endian_, @cur_dims * count_)
  @cur_factory.line_string((0...count_).map{ |i_| @cur_factory.point(*coords_[@cur_dims*i_,@cur_dims]) })
end

#_parse_object(contained_) ⇒ Object

:nodoc:



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 188

def _parse_object(contained_)  # :nodoc:
  little_endian_ = _get_byte == 1
  type_code_ = _get_integer(little_endian_)
  has_z_ = false
  has_m_ = false
  srid_ = contained_ ? nil : @default_srid
  if @support_ewkb
    has_z_ ||= type_code_ & 0x80000000 != 0
    has_m_ ||= type_code_ & 0x40000000 != 0
    srid_ = _get_integer(little_endian_) if type_code_ & 0x20000000 != 0
    type_code_ &= 0x0fffffff
  end
  if @support_wkb12
    has_z_ ||= (type_code_ / 1000) & 1 != 0
    has_m_ ||= (type_code_ / 1000) & 2 != 0
    type_code_ %= 1000
  end
  if contained_
    if contained_ != true && contained_ != type_code_
      raise Error::ParseError, "Enclosed type=#{type_code_} is different from container constraint #{contained_}"
    end
    if has_z_ != @cur_has_z
      raise Error::ParseError, "Enclosed hasZ=#{has_z_} is different from toplevel hasZ=#{@cur_has_z}"
    end
    if has_m_ != @cur_has_m
      raise Error::ParseError, "Enclosed hasM=#{has_m_} is different from toplevel hasM=#{@cur_has_m}"
    end
    if srid_ && srid_ != @cur_srid
      raise Error::ParseError, "Enclosed SRID #{srid_} is different from toplevel srid #{@cur_srid || '(unspecified)'}"
    end
  else
    @cur_has_z = has_z_
    @cur_has_m = has_m_
    @cur_dims = 2 + (@cur_has_z ? 1 : 0) + (@cur_has_m ? 1 : 0)
    @cur_srid = srid_
    @cur_factory = @factory_generator.call(:srid => @cur_srid, :has_z_coordinate => has_z_, :has_m_coordinate => has_m_)
    if @cur_has_z && !@cur_factory.property(:has_z_coordinate)
      raise Error::ParseError, "Data has Z coordinates but the factory doesn't have Z coordinates"
    end
    if @cur_has_m && !@cur_factory.property(:has_m_coordinate)
      raise Error::ParseError, "Data has M coordinates but the factory doesn't have M coordinates"
    end
  end
  case type_code_
  when 1
    coords_ = _get_doubles(little_endian_, @cur_dims)
    @cur_factory.point(*coords_)
  when 2
    _parse_line_string(little_endian_)
  when 3
    interior_rings_ = (1.._get_integer(little_endian_)).map{ _parse_line_string(little_endian_) }
    exterior_ring_ = interior_rings_.shift || @cur_factory.linear_ring([])
    @cur_factory.polygon(exterior_ring_, interior_rings_)
  when 4
    @cur_factory.multi_point((1.._get_integer(little_endian_)).map{ _parse_object(1) })
  when 5
    @cur_factory.multi_line_string((1.._get_integer(little_endian_)).map{ _parse_object(2) })
  when 6
    @cur_factory.multi_polygon((1.._get_integer(little_endian_)).map{ _parse_object(3) })
  when 7
    @cur_factory.collection((1.._get_integer(little_endian_)).map{ _parse_object(true) })
  else
    raise Error::ParseError, "Unknown type value: #{type_code_}."
  end
end

#_start_scanner(data_) ⇒ Object

:nodoc:



262
263
264
265
266
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 262

def _start_scanner(data_)  # :nodoc:
  @_data = data_
  @_len = data_.length
  @_pos = 0
end

#exact_factoryObject

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



99
100
101
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 99

def exact_factory
  @exact_factory
end

#factory_generatorObject

Returns the factory generator. See WKBParser for details.



93
94
95
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 93

def factory_generator
  @factory_generator
end

#factory_generator=(value_) ⇒ Object

Sets the factory_generator. See WKBParser for details.



104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 104

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

Sets the the ignore_extra_bytes flag. See WKBParser for details.



152
153
154
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 152

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

#ignore_extra_bytes?Boolean

Returns true if this parser ignores extra bytes. See WKBParser for details.

Returns:

  • (Boolean)


147
148
149
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 147

def ignore_extra_bytes?
  @ignore_extra_bytes
end

#parse(data_) ⇒ Object

Parse the given binary data, and return a geometry object.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 166

def parse(data_)
  @cur_has_z = nil
  @cur_has_m = nil
  @cur_srid = nil
  @cur_dims = 2
  @cur_factory = nil
  begin
    _start_scanner(data_)
    obj_ = _parse_object(false)
    unless @ignore_extra_bytes
      bytes_ = _bytes_remaining
      if bytes_ > 0
        raise Error::ParseError, "Found #{bytes_} extra bytes at the end of the stream."
      end
    end
  ensure
    _clean_scanner
  end
  obj_
end

#parse_hex(str_) ⇒ Object

Parse the given hex string, and return a geometry object.



159
160
161
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 159

def parse_hex(str_)
  parse([str_].pack('H*'))
end

#support_ewkb=(value_) ⇒ Object

Sets the the support_ewkb flag. See WKBParser for details.



130
131
132
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 130

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

#support_ewkb?Boolean

Returns true if this parser supports EWKB. See WKBParser for details.

Returns:

  • (Boolean)


125
126
127
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 125

def support_ewkb?
  @support_ewkb
end

#support_wkb12=(value_) ⇒ Object

Sets the the support_wkb12 flag. See WKBParser for details.



141
142
143
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 141

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

#support_wkb12?Boolean

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

Returns:

  • (Boolean)


136
137
138
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 136

def support_wkb12?
  @support_wkb12
end

#to_generate_factory(&block_) ⇒ Object

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



119
120
121
# File 'lib/rgeo/wkrep/wkb_parser.rb', line 119

def to_generate_factory(&block_)
  @factory_generator = block_
end