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
97
98
# 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.output_dimensions = 2
    @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_writer.output_dimensions = 2
    @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



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

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)


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

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



119
120
121
# File 'lib/rgeo/geos/ffi_factory.rb', line 119

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

#init_with(coder) ⇒ Object

:nodoc:



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

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



102
103
104
# File 'lib/rgeo/geos/ffi_factory.rb', line 102

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

#line(start, stop) ⇒ Object

See RGeo::Feature::Factory#line



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

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



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

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



288
289
290
291
292
# File 'lib/rgeo/geos/ffi_factory.rb', line 288

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



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

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:



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

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|
    RGeo::Feature.cast(
      elem,
      self,
      RGeo::Feature::Point,
      :force_new,
      :keep_subtype
    )
  end
  return unless elems.all?
  elems = elems.map(&:detach_fg_geom)
  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



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

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



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

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



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

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



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

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)
  return unless inner_rings.all? { |r| RGeo::Feature::LineString.check_type(r) }
  inner_rings = inner_rings.map { |r| create_fg_linear_ring(r.points) }
  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



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

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



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

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

#read_for_psych(str) ⇒ Object



476
477
478
# File 'lib/rgeo/geos/ffi_factory.rb', line 476

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
458
# 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 = 2
    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



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

def write_for_psych(geom)
  if Utils.ffi_supports_set_output_dimension || !@_has_3d
    wkt_writer = ::Geos::WktWriter.new
    wkt_writer.output_dimensions = 2
    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