Class: RGeo::Geos::FFIFactory

Inherits:
Object
  • Object
show all
Includes:
Feature::Factory::Instance, ImplHelper::Utils
Defined in:
lib/rgeo/geos/ffi_factory.rb

Overview

This the FFI-GEOS implementation of RGeo::Feature::Factory.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from ImplHelper::Utils

setup_coord_sys

Constructor Details

#initialize(opts = {}) ⇒ FFIFactory

Create a new factory. Returns nil if the FFI-GEOS implementation is not supported.

See RGeo::Geos.factory for a list of supported options.


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rgeo/geos/ffi_factory.rb', line 33

def initialize(opts = {})
  @has_z = opts[:has_z_coordinate] ? true : false
  @has_m = opts[:has_m_coordinate] ? true : false

  if @has_z && @has_m
    raise Error::UnsupportedOperation, "GEOS cannot support both Z and M coordinates at the same time."
  end

  @coordinate_dimension = 2
  @coordinate_dimension += 1 if @has_z
  @coordinate_dimension += 1 if @has_m
  @spatial_dimension = @has_z ? 3 : 2

  @_has_3d = @has_z || @has_m
  @buffer_resolution = opts[:buffer_resolution].to_i
  @buffer_resolution = 1 if @buffer_resolution < 1
  @_auto_prepare = opts[:auto_prepare] != :disabled

  # Interpret the generator options
  wkt_generator = opts[:wkt_generator]
  case wkt_generator
  when Hash
    @wkt_generator = WKRep::WKTGenerator.new(wkt_generator)
    @wkt_writer = nil
  else
    @wkt_writer = ::Geos::WktWriter.new
    @wkt_writer.trim = true
    @wkt_generator = nil
  end
  wkb_generator = opts[:wkb_generator]
  case wkb_generator
  when Hash
    @wkb_generator = WKRep::WKBGenerator.new(wkb_generator)
    @wkb_writer = nil
  else
    @wkb_writer = ::Geos::WkbWriter.new
    @wkb_generator = nil
  end

  # Coordinate system (srid and coord_sys)
  coord_sys_info = ImplHelper::Utils.setup_coord_sys(opts[:srid], opts[:coord_sys], opts[:coord_sys_class])
  @srid = coord_sys_info[:srid]
  @coord_sys = coord_sys_info[:coord_sys]

  # Interpret parser options
  wkt_parser = opts[:wkt_parser]
  case wkt_parser
  when Hash
    @wkt_parser = WKRep::WKTParser.new(self, wkt_parser)
    @wkt_reader = nil
  else
    @wkt_reader = ::Geos::WktReader.new
    @wkt_parser = nil
  end
  wkb_parser = opts[:wkb_parser]
  case wkb_parser
  when Hash
    @wkb_parser = WKRep::WKBParser.new(self, wkb_parser)
    @wkb_reader = nil
  else
    @wkb_reader = ::Geos::WkbReader.new
    @wkb_parser = nil
  end
end

Instance Attribute Details

#_auto_prepareObject (readonly)

Returns the value of attribute _auto_prepare.


16
17
18
# File 'lib/rgeo/geos/ffi_factory.rb', line 16

def _auto_prepare
  @_auto_prepare
end

#_has_3dObject (readonly)

Returns the value of attribute _has_3d.


16
17
18
# File 'lib/rgeo/geos/ffi_factory.rb', line 16

def _has_3d
  @_has_3d
end

#buffer_resolutionObject (readonly)

Returns the resolution used by buffer calculations on geometries created by this factory


23
24
25
# File 'lib/rgeo/geos/ffi_factory.rb', line 23

def buffer_resolution
  @buffer_resolution
end

#coord_sysObject (readonly)

See RGeo::Feature::Factory#coord_sys


26
27
28
# File 'lib/rgeo/geos/ffi_factory.rb', line 26

def coord_sys
  @coord_sys
end

#coordinate_dimensionObject (readonly)

Returns the value of attribute coordinate_dimension.


16
17
18
# File 'lib/rgeo/geos/ffi_factory.rb', line 16

def coordinate_dimension
  @coordinate_dimension
end

#spatial_dimensionObject (readonly)

Returns the value of attribute spatial_dimension.


16
17
18
# File 'lib/rgeo/geos/ffi_factory.rb', line 16

def spatial_dimension
  @spatial_dimension
end

#sridObject (readonly)

Returns the SRID of geometries created by this factory.


19
20
21
# File 'lib/rgeo/geos/ffi_factory.rb', line 19

def srid
  @srid
end

Instance Method Details

#collection(elems) ⇒ Object

See RGeo::Feature::Factory#collection


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/rgeo/geos/ffi_factory.rb', line 309

def collection(elems)
  elems = elems.to_a unless elems.is_a?(Array)
  klasses = []
  my_fg_geoms = []
  elems.each do |elem|
    k = elem._klasses if elem.factory.is_a?(FFIFactory)
    elem = RGeo::Feature.cast(elem, self, :force_new, :keep_subtype)
    if elem
      klasses << (k || elem.class)
      my_fg_geoms << elem.detach_fg_geom
    end
  end
  fg_geom = ::Geos::Utils.create_collection(::Geos::GeomTypes::GEOS_GEOMETRYCOLLECTION, my_fg_geoms)
  FFIGeometryCollectionImpl.new(self, fg_geom, klasses)
end

#convert_to_fg_geometry(obj, type = nil) ⇒ Object


424
425
426
427
428
429
430
431
# File 'lib/rgeo/geos/ffi_factory.rb', line 424

def convert_to_fg_geometry(obj, type = nil)
  obj = Feature.cast(obj, self, type) if type && obj.factory != self

  geom = obj&.fg_geom
  raise RGeo::Error::InvalidGeometry, "Unable to cast the geometry to the FFI Factory" if geom.nil?

  geom
end

#encode_with(coder) ⇒ Object

Psych support


159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/rgeo/geos/ffi_factory.rb', line 159

def encode_with(coder) # :nodoc:
  coder["has_z_coordinate"] = @has_z
  coder["has_m_coordinate"] = @has_m
  coder["srid"] = @srid
  coder["buffer_resolution"] = @buffer_resolution
  coder["wkt_generator"] = @wkt_generator&.properties
  coder["wkb_generator"] = @wkb_generator&.properties
  coder["wkt_parser"] = @wkt_parser&.properties
  coder["wkb_parser"] = @wkb_parser&.properties
  coder["auto_prepare"] = @_auto_prepare ? "simple" : "disabled"
  coder["coord_sys"] = @coord_sys.to_wkt if @coord_sys
end

#eql?(other) ⇒ Boolean Also known as: ==

Factory equivalence test.

Returns:

  • (Boolean)

106
107
108
109
110
111
112
# File 'lib/rgeo/geos/ffi_factory.rb', line 106

def eql?(other)
  other.is_a?(self.class) && @srid == other.srid &&
    @has_z == other.property(:has_z_coordinate) &&
    @has_m == other.property(:has_m_coordinate) &&
    @buffer_resolution == other.property(:buffer_resolution) &&
    @coord_sys.eql?(other.coord_sys)
end

#generate_wkb(geom) ⇒ Object


441
442
443
444
445
446
447
# File 'lib/rgeo/geos/ffi_factory.rb', line 441

def generate_wkb(geom)
  if @wkb_writer
    @wkb_writer.write(geom.fg_geom)
  else
    @wkb_generator.generate(geom)
  end
end

#generate_wkt(geom) ⇒ Object


433
434
435
436
437
438
439
# File 'lib/rgeo/geos/ffi_factory.rb', line 433

def generate_wkt(geom)
  if @wkt_writer
    @wkt_writer.write(geom.fg_geom)
  else
    @wkt_generator.generate(geom)
  end
end

#hashObject

Standard hash code


117
118
119
# File 'lib/rgeo/geos/ffi_factory.rb', line 117

def hash
  @hash ||= [@srid, @has_z, @has_m, @buffer_resolution, @coord_sys].hash
end

#init_with(coder) ⇒ Object

:nodoc:


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/rgeo/geos/ffi_factory.rb', line 172

def init_with(coder) # :nodoc:
  cs_class = CoordSys::CONFIG.default_coord_sys_class
  coord_sys = coder["cs"]&.then { |cs| cs_class.create_from_wkt(cs) }

  initialize(
    has_z_coordinate: coder["has_z_coordinate"],
    has_m_coordinate: coder["has_m_coordinate"],
    srid: coder["srid"],
    buffer_resolution: coder["buffer_resolution"],
    wkt_generator: coder["wkt_generator"] && symbolize_hash(coder["wkt_generator"]),
    wkb_generator: coder["wkb_generator"] && symbolize_hash(coder["wkb_generator"]),
    wkt_parser: coder["wkt_parser"] && symbolize_hash(coder["wkt_parser"]),
    wkb_parser: coder["wkb_parser"] && symbolize_hash(coder["wkb_parser"]),
    auto_prepare: coder["auto_prepare"] == "disabled" ? :disabled : :simple,
    coord_sys: coord_sys
  )
end

#inspectObject

Standard object inspection output


100
101
102
# File 'lib/rgeo/geos/ffi_factory.rb', line 100

def inspect
  "#<#{self.class}:0x#{object_id.to_s(16)} srid=#{srid}>"
end

#line(start, stop) ⇒ Object

See RGeo::Feature::Factory#line


266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/rgeo/geos/ffi_factory.rb', line 266

def line(start, stop)
  return unless RGeo::Feature::Point.check_type(start) &&
    RGeo::Feature::Point.check_type(stop)
  cs = ::Geos::CoordinateSequence.new(2, 3)
  cs.set_x(0, start.x)
  cs.set_x(1, stop.x)
  cs.set_y(0, start.y)
  cs.set_y(1, stop.y)
  if @has_z
    cs.set_z(0, start.z)
    cs.set_z(1, stop.z)
  elsif @has_m
    cs.set_z(0, start.m)
    cs.set_z(1, stop.m)
  end
  FFILineImpl.new(self, ::Geos::Utils.create_line_string(cs), nil)
end

#line_string(points) ⇒ Object

See RGeo::Feature::Factory#line_string


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/rgeo/geos/ffi_factory.rb', line 246

def line_string(points)
  points = points.to_a unless points.is_a?(Array)
  size = points.size
  raise(Error::InvalidGeometry, "Must have more than one point") if size == 1
  cs = ::Geos::CoordinateSequence.new(size, 3)
  points.each_with_index do |p, i|
    raise(Error::InvalidGeometry, "Invalid point: #{p}") unless RGeo::Feature::Point.check_type(p)
    cs.set_x(i, p.x)
    cs.set_y(i, p.y)
    if @has_z
      cs.set_z(i, p.z)
    elsif @has_m
      cs.set_z(i, p.m)
    end
  end
  FFILineStringImpl.new(self, ::Geos::Utils.create_line_string(cs), nil)
end

#linear_ring(points) ⇒ Object

See RGeo::Feature::Factory#linear_ring


286
287
288
289
290
# File 'lib/rgeo/geos/ffi_factory.rb', line 286

def linear_ring(points)
  points = points.to_a unless points.is_a?(Array)
  fg_geom = create_fg_linear_ring(points)
  FFILinearRingImpl.new(self, fg_geom, nil)
end

#marshal_dumpObject

Marshal support


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/rgeo/geos/ffi_factory.rb', line 123

def marshal_dump # :nodoc:
  hash = {
    "hasz" => @has_z,
    "hasm" => @has_m,
    "srid" => @srid,
    "bufr" => @buffer_resolution,
    "wktg" => @wkt_generator&.properties,
    "wkbg" => @wkb_generator&.properties,
    "wktp" => @wkt_parser&.properties,
    "wkbp" => @wkb_parser&.properties,
    "apre" => @_auto_prepare
  }
  hash["cs"] = @coord_sys.to_wkt if @coord_sys
  hash
end

#marshal_load(data) ⇒ Object

:nodoc:


139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/rgeo/geos/ffi_factory.rb', line 139

def marshal_load(data) # :nodoc:
  cs_class = CoordSys::CONFIG.default_coord_sys_class
  coord_sys = data["cs"]&.then { |cs| cs_class.create_from_wkt(cs) }

  initialize(
    has_z_coordinate: data["hasz"],
    has_m_coordinate: data["hasm"],
    srid: data["srid"],
    buffer_resolution: data["bufr"],
    wkt_generator: data["wktg"] && symbolize_hash(data["wktg"]),
    wkb_generator: data["wkbg"] && symbolize_hash(data["wkbg"]),
    wkt_parser: data["wktp"] && symbolize_hash(data["wktp"]),
    wkb_parser: data["wkbp"] && symbolize_hash(data["wkbp"]),
    auto_prepare: (data["apre"] ? :simple : :disabled),
    coord_sys: coord_sys
  )
end

#multi_line_string(elems) ⇒ Object

See RGeo::Feature::Factory#multi_line_string


347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/rgeo/geos/ffi_factory.rb', line 347

def multi_line_string(elems)
  elems = elems.to_a unless elems.is_a?(Array)
  klasses = []
  elems = elems.map do |elem|
    elem = RGeo::Feature.cast(elem, self, RGeo::Feature::LineString, :force_new, :keep_subtype)
    raise(RGeo::Error::InvalidGeometry, "Parse error") unless elem
    klasses << elem.class
    elem.detach_fg_geom
  end
  fg_geom = ::Geos::Utils.create_collection(::Geos::GeomTypes::GEOS_MULTILINESTRING, elems)
  FFIMultiLineStringImpl.new(self, fg_geom, klasses)
end

#multi_point(elems) ⇒ Object

See RGeo::Feature::Factory#multi_point


327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/rgeo/geos/ffi_factory.rb', line 327

def multi_point(elems)
  elems = elems.to_a unless elems.is_a?(Array)
  elems = elems.map do |elem|
    elem = RGeo::Feature.cast(
      elem,
      self,
      RGeo::Feature::Point,
      :force_new,
      :keep_subtype
    )
    return unless elem
    elem.detach_fg_geom
  end
  klasses = Array.new(elems.size, FFIPointImpl)
  fg_geom = ::Geos::Utils.create_collection(::Geos::GeomTypes::GEOS_MULTIPOINT, elems)
  FFIMultiPointImpl.new(self, fg_geom, klasses)
end

#multi_polygon(elems) ⇒ Object

See RGeo::Feature::Factory#multi_polygon


362
363
364
365
366
367
368
369
370
371
372
# File 'lib/rgeo/geos/ffi_factory.rb', line 362

def multi_polygon(elems)
  elems = elems.to_a unless elems.is_a?(Array)
  elems = elems.map do |elem|
    elem = RGeo::Feature.cast(elem, self, RGeo::Feature::Polygon, :force_new, :keep_subtype)
    raise(RGeo::Error::InvalidGeometry, "Could not cast to polygon: #{elem}") unless elem
    elem.detach_fg_geom
  end
  klasses = Array.new(elems.size, FFIPolygonImpl)
  fg_geom = ::Geos::Utils.create_collection(::Geos::GeomTypes::GEOS_MULTIPOLYGON, elems)
  FFIMultiPolygonImpl.new(self, fg_geom, klasses)
end

#override_cast(_original, _ntype, _flags) ⇒ Object

See RGeo::Feature::Factory#override_cast


376
377
378
379
# File 'lib/rgeo/geos/ffi_factory.rb', line 376

def override_cast(_original, _ntype, _flags)
  false
  # TODO
end

#parse_wkb(str) ⇒ Object

See RGeo::Feature::Factory#parse_wkb


221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rgeo/geos/ffi_factory.rb', line 221

def parse_wkb(str)
  if @wkb_reader
    begin
      meth = str[0].match?(/[0-9a-fA-F]/) ? :read_hex : :read
      wrap_fg_geom(@wkb_reader.public_send(meth, str), nil)
    rescue ::Geos::WkbReader::ParseError => e
      raise RGeo::Error::ParseError, e.message.partition(":").last
    end
  else
    @wkb_parser.parse(str)
  end
end

#parse_wkt(str) ⇒ Object

See RGeo::Feature::Factory#parse_wkt


207
208
209
210
211
212
213
214
215
216
217
# File 'lib/rgeo/geos/ffi_factory.rb', line 207

def parse_wkt(str)
  if @wkt_reader
    begin
      wrap_fg_geom(@wkt_reader.read(str), nil)
    rescue ::Geos::WktReader::ParseError => e
      raise RGeo::Error::ParseError, e.message.partition(":").last
    end
  else
    @wkt_parser.parse(str)
  end
end

#point(x, y, z = 0) ⇒ Object

See RGeo::Feature::Factory#point


236
237
238
239
240
241
242
# File 'lib/rgeo/geos/ffi_factory.rb', line 236

def point(x, y, z = 0)
  cs = ::Geos::CoordinateSequence.new(1, 3)
  cs.set_x(0, x)
  cs.set_y(0, y)
  cs.set_z(0, z)
  FFIPointImpl.new(self, ::Geos::Utils.create_point(cs), nil)
end

#polygon(outer_ring, inner_rings = nil) ⇒ Object

See RGeo::Feature::Factory#polygon


294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/rgeo/geos/ffi_factory.rb', line 294

def polygon(outer_ring, inner_rings = nil)
  inner_rings = inner_rings.to_a unless inner_rings.is_a?(Array)
  return unless RGeo::Feature::LineString.check_type(outer_ring)
  outer_ring = create_fg_linear_ring(outer_ring.points)
  inner_rings = inner_rings.map do |r|
    return unless RGeo::Feature::LineString.check_type(r)
    create_fg_linear_ring(r.points)
  end
  inner_rings.compact!
  fg_geom = ::Geos::Utils.create_polygon(outer_ring, *inner_rings)
  FFIPolygonImpl.new(self, fg_geom, nil)
end

#property(name_) ⇒ Object

See RGeo::Feature::Factory#property


191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/rgeo/geos/ffi_factory.rb', line 191

def property(name_)
  case name_
  when :has_z_coordinate
    @has_z
  when :has_m_coordinate
    @has_m
  when :is_cartesian
    true
  when :buffer_resolution
    @buffer_resolution
  when :auto_prepare
    @_auto_prepare ? :simple : :disabled
  end
end

#read_for_marshal(str) ⇒ Object


459
460
461
# File 'lib/rgeo/geos/ffi_factory.rb', line 459

def read_for_marshal(str)
  ::Geos::WkbReader.new.read(str)
end

#read_for_psych(str) ⇒ Object


474
475
476
# File 'lib/rgeo/geos/ffi_factory.rb', line 474

def read_for_psych(str)
  ::Geos::WktReader.new.read(str)
end

#wrap_fg_geom(fg_geom, klass = nil) ⇒ Object

Create a feature that wraps the given ffi-geos geometry object


382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'lib/rgeo/geos/ffi_factory.rb', line 382

def wrap_fg_geom(fg_geom, klass = nil)
  klasses = nil

  # We don't allow "empty" points, so replace such objects with
  # an empty collection.
  if fg_geom.type_id == ::Geos::GeomTypes::GEOS_POINT && fg_geom.empty?
    fg_geom = ::Geos::Utils.create_geometry_collection
    klass = FFIGeometryCollectionImpl
  end

  unless klass.is_a?(::Class)
    is_collection = false
    case fg_geom.type_id
    when ::Geos::GeomTypes::GEOS_POINT
      inferred_klass = FFIPointImpl
    when ::Geos::GeomTypes::GEOS_MULTIPOINT
      inferred_klass = FFIMultiPointImpl
      is_collection = true
    when ::Geos::GeomTypes::GEOS_LINESTRING
      inferred_klass = FFILineStringImpl
    when ::Geos::GeomTypes::GEOS_LINEARRING
      inferred_klass = FFILinearRingImpl
    when ::Geos::GeomTypes::GEOS_MULTILINESTRING
      inferred_klass = FFIMultiLineStringImpl
      is_collection = true
    when ::Geos::GeomTypes::GEOS_POLYGON
      inferred_klass = FFIPolygonImpl
    when ::Geos::GeomTypes::GEOS_MULTIPOLYGON
      inferred_klass = FFIMultiPolygonImpl
      is_collection = true
    when ::Geos::GeomTypes::GEOS_GEOMETRYCOLLECTION
      inferred_klass = FFIGeometryCollectionImpl
      is_collection = true
    else
      inferred_klass = FFIGeometryImpl
    end
    klasses = klass if is_collection && klass.is_a?(Array)
    klass = inferred_klass
  end
  klass.new(self, fg_geom, klasses)
end

#write_for_marshal(geom) ⇒ Object


449
450
451
452
453
454
455
456
457
# File 'lib/rgeo/geos/ffi_factory.rb', line 449

def write_for_marshal(geom)
  if Utils.ffi_supports_set_output_dimension || !@_has_3d
    wkb_writer = ::Geos::WkbWriter.new
    wkb_writer.output_dimensions = 3 if @_has_3d
    wkb_writer.write(geom.fg_geom)
  else
    Utils.marshal_wkb_generator.generate(geom)
  end
end

#write_for_psych(geom) ⇒ Object


463
464
465
466
467
468
469
470
471
472
# File 'lib/rgeo/geos/ffi_factory.rb', line 463

def write_for_psych(geom)
  if Utils.ffi_supports_set_output_dimension || !@_has_3d
    wkt_writer = ::Geos::WktWriter.new
    wkt_writer.trim = true
    wkt_writer.output_dimensions = 3 if @_has_3d
    wkt_writer.write(geom.fg_geom)
  else
    Utils.psych_wkt_generator.generate(geom)
  end
end