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

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.


22
23
24
25
26
27
28
29
30
31
32
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
99
100
101
102
103
104
105
106
# File 'lib/rgeo/geos/ffi_factory.rb', line 22

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 ? false : true

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

  # Coordinate system (srid, proj4, and coord_sys)
  @srid = opts[:srid]
  @proj4 = opts[:proj4]
  if @proj4 && CoordSys.check!(:proj4)
    if @proj4.is_a?(String) || @proj4.is_a?(Hash)
      @proj4 = CoordSys::Proj4.create(@proj4)
    end
  else
    @proj4 = nil
  end
  @coord_sys = opts[:coord_sys]
  if @coord_sys.is_a?(String)
    @coord_sys = CoordSys::CS.create_from_wkt(@coord_sys)
  end
  @srid ||= @coord_sys.authority_code if @coord_sys
  @srid = @srid.to_i

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

Instance Attribute Details

#_auto_prepareObject (readonly)

:nodoc:


466
467
468
# File 'lib/rgeo/geos/ffi_factory.rb', line 466

def _auto_prepare
  @_auto_prepare
end

#_has_3dObject (readonly)

:nodoc:


465
466
467
# File 'lib/rgeo/geos/ffi_factory.rb', line 465

def _has_3d
  @_has_3d
end

#buffer_resolutionObject (readonly)

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


235
236
237
# File 'lib/rgeo/geos/ffi_factory.rb', line 235

def buffer_resolution
  @buffer_resolution
end

#coord_sysObject (readonly)

See RGeo::Feature::Factory#coord_sys


413
414
415
# File 'lib/rgeo/geos/ffi_factory.rb', line 413

def coord_sys
  @coord_sys
end

#coordinate_dimensionObject (readonly)

Returns the value of attribute coordinate_dimension.


107
108
109
# File 'lib/rgeo/geos/ffi_factory.rb', line 107

def coordinate_dimension
  @coordinate_dimension
end

#proj4Object (readonly)

See RGeo::Feature::Factory#proj4


409
410
411
# File 'lib/rgeo/geos/ffi_factory.rb', line 409

def proj4
  @proj4
end

#spatial_dimensionObject (readonly)

Returns the value of attribute spatial_dimension.


107
108
109
# File 'lib/rgeo/geos/ffi_factory.rb', line 107

def spatial_dimension
  @spatial_dimension
end

#sridObject (readonly)

Returns the SRID of geometries created by this factory.


230
231
232
# File 'lib/rgeo/geos/ffi_factory.rb', line 230

def srid
  @srid
end

Instance Method Details

#collection(elems) ⇒ Object

See RGeo::Feature::Factory#collection


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

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


468
469
470
471
472
473
# File 'lib/rgeo/geos/ffi_factory.rb', line 468

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

#encode_with(coder) ⇒ Object

Psych support


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/rgeo/geos/ffi_factory.rb', line 180

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"
  if @proj4
    str = @proj4.original_str || @proj4.canonical_str
    coder["proj4"] = @proj4.radians? ? { "proj4" => str, "radians" => true } : str
  end
  coder["coord_sys"] = @coord_sys.to_wkt if @coord_sys
end

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

Factory equivalence test.

Returns:

  • (Boolean)

117
118
119
120
121
122
123
# File 'lib/rgeo/geos/ffi_factory.rb', line 117

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

#generate_wkb(geom) ⇒ Object


483
484
485
486
487
488
489
# File 'lib/rgeo/geos/ffi_factory.rb', line 483

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

#generate_wkt(geom) ⇒ Object


475
476
477
478
479
480
481
# File 'lib/rgeo/geos/ffi_factory.rb', line 475

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

#hashObject

Standard hash code


128
129
130
# File 'lib/rgeo/geos/ffi_factory.rb', line 128

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

#init_with(coder) ⇒ Object

:nodoc:


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
# File 'lib/rgeo/geos/ffi_factory.rb', line 197

def init_with(coder) # :nodoc:
  if (proj4_data = coder["proj4"])
    CoordSys.check!(:proj4)
    if proj4_data.is_a?(Hash)
      proj4 = CoordSys::Proj4.create(proj4_data["proj4"], radians: proj4_data["radians"])
    else
      proj4 = CoordSys::Proj4.create(proj4_data.to_s)
    end
  else
    proj4 = nil
  end
  if (coord_sys_data = coder["cs"])
    coord_sys = CoordSys::CS.create_from_wkt(coord_sys_data.to_s)
  else
    coord_sys = nil
  end
  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: symbolize_hash(coder["wkt_generator"]),
    wkb_generator: symbolize_hash(coder["wkb_generator"]),
    wkt_parser: symbolize_hash(coder["wkt_parser"]),
    wkb_parser: symbolize_hash(coder["wkb_parser"]),
    auto_prepare: coder["auto_prepare"] == "disabled" ? :disabled : :simple,
    proj4: proj4,
    coord_sys: coord_sys
  )
end

#inspectObject

Standard object inspection output


111
112
113
# File 'lib/rgeo/geos/ffi_factory.rb', line 111

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

#line(start, stop) ⇒ Object

See RGeo::Feature::Factory#line


304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/rgeo/geos/ffi_factory.rb', line 304

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


284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/rgeo/geos/ffi_factory.rb', line 284

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


324
325
326
327
328
# File 'lib/rgeo/geos/ffi_factory.rb', line 324

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


134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/rgeo/geos/ffi_factory.rb', line 134

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["proj4"] = @proj4.marshal_dump if @proj4
  hash["cs"] = @coord_sys.to_wkt if @coord_sys
  hash
end

#marshal_load(data) ⇒ Object

:nodoc:


151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/rgeo/geos/ffi_factory.rb', line 151

def marshal_load(data) # :nodoc:
  if (proj4_data = data["proj4"]) && CoordSys.check!(:proj4)
    proj4 = CoordSys::Proj4.allocate
    proj4.marshal_load(proj4_data)
  else
    proj4 = nil
  end
  if (coord_sys_data = data["cs"])
    coord_sys = CoordSys::CS.create_from_wkt(coord_sys_data)
  else
    coord_sys = nil
  end
  initialize(
    has_z_coordinate: data["hasz"],
    has_m_coordinate: data["hasm"],
    srid: data["srid"],
    buffer_resolution: data["bufr"],
    wkt_generator: symbolize_hash(data["wktg"]),
    wkb_generator: symbolize_hash(data["wkbg"]),
    wkt_parser: symbolize_hash(data["wktp"]),
    wkb_parser: symbolize_hash(data["wkbp"]),
    auto_prepare: (data["apre"] ? :simple : :disabled),
    proj4: proj4,
    coord_sys: coord_sys
  )
end

#multi_line_string(elems) ⇒ Object

See RGeo::Feature::Factory#multi_line_string


380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/rgeo/geos/ffi_factory.rb', line 380

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


365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/rgeo/geos/ffi_factory.rb', line 365

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


395
396
397
398
399
400
401
402
403
404
405
# File 'lib/rgeo/geos/ffi_factory.rb', line 395

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


417
418
419
420
# File 'lib/rgeo/geos/ffi_factory.rb', line 417

def override_cast(original, ntype, flags)
  false
  # TODO
end

#parse_wkb(str) ⇒ Object

See RGeo::Feature::Factory#parse_wkb


264
265
266
267
268
269
270
# File 'lib/rgeo/geos/ffi_factory.rb', line 264

def parse_wkb(str)
  if @wkb_reader
    wrap_fg_geom(@wkb_reader.read(str), nil)
  else
    @wkb_parser.parse(str)
  end
end

#parse_wkt(str) ⇒ Object

See RGeo::Feature::Factory#parse_wkt


254
255
256
257
258
259
260
# File 'lib/rgeo/geos/ffi_factory.rb', line 254

def parse_wkt(str)
  if @wkt_reader
    wrap_fg_geom(@wkt_reader.read(str), nil)
  else
    @wkt_parser.parse(str)
  end
end

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

See RGeo::Feature::Factory#point


274
275
276
277
278
279
280
# File 'lib/rgeo/geos/ffi_factory.rb', line 274

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


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

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


238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/rgeo/geos/ffi_factory.rb', line 238

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


501
502
503
# File 'lib/rgeo/geos/ffi_factory.rb', line 501

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

#read_for_psych(str) ⇒ Object


515
516
517
# File 'lib/rgeo/geos/ffi_factory.rb', line 515

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


423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
# File 'lib/rgeo/geos/ffi_factory.rb', line 423

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


491
492
493
494
495
496
497
498
499
# File 'lib/rgeo/geos/ffi_factory.rb', line 491

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


505
506
507
508
509
510
511
512
513
# File 'lib/rgeo/geos/ffi_factory.rb', line 505

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