ffi-geos 1.2.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +4851 -0
  3. data/.travis.yml +24 -9
  4. data/FUNDING.yml +2 -0
  5. data/Gemfile +12 -16
  6. data/Guardfile +6 -8
  7. data/MIT-LICENSE +1 -1
  8. data/README.rdoc +2 -20
  9. data/Rakefile +4 -2
  10. data/ffi-geos.gemspec +13 -14
  11. data/lib/ffi-geos.rb +342 -244
  12. data/lib/ffi-geos/buffer_params.rb +9 -20
  13. data/lib/ffi-geos/coordinate_sequence.rb +351 -65
  14. data/lib/ffi-geos/geometry.rb +267 -191
  15. data/lib/ffi-geos/geometry_collection.rb +74 -12
  16. data/lib/ffi-geos/interrupt.rb +11 -16
  17. data/lib/ffi-geos/line_string.rb +157 -33
  18. data/lib/ffi-geos/linear_ring.rb +2 -3
  19. data/lib/ffi-geos/multi_line_string.rb +1 -2
  20. data/lib/ffi-geos/multi_point.rb +0 -1
  21. data/lib/ffi-geos/multi_polygon.rb +0 -1
  22. data/lib/ffi-geos/point.rb +70 -15
  23. data/lib/ffi-geos/polygon.rb +124 -21
  24. data/lib/ffi-geos/prepared_geometry.rb +11 -12
  25. data/lib/ffi-geos/strtree.rb +64 -77
  26. data/lib/ffi-geos/tools.rb +16 -19
  27. data/lib/ffi-geos/utils.rb +36 -60
  28. data/lib/ffi-geos/version.rb +1 -3
  29. data/lib/ffi-geos/wkb_reader.rb +4 -9
  30. data/lib/ffi-geos/wkb_writer.rb +15 -20
  31. data/lib/ffi-geos/wkt_reader.rb +2 -5
  32. data/lib/ffi-geos/wkt_writer.rb +20 -31
  33. data/sonar-project.properties +16 -0
  34. data/test/.rubocop.yml +36 -0
  35. data/test/coordinate_sequence_tests.rb +322 -52
  36. data/test/geometry_collection_tests.rb +388 -4
  37. data/test/geometry_tests.rb +466 -121
  38. data/test/interrupt_tests.rb +9 -12
  39. data/test/line_string_tests.rb +213 -25
  40. data/test/linear_ring_tests.rb +1 -3
  41. data/test/misc_tests.rb +28 -30
  42. data/test/multi_line_string_tests.rb +0 -2
  43. data/test/point_tests.rb +158 -2
  44. data/test/polygon_tests.rb +283 -2
  45. data/test/prepared_geometry_tests.rb +8 -11
  46. data/test/strtree_tests.rb +14 -15
  47. data/test/test_helper.rb +75 -51
  48. data/test/tools_tests.rb +1 -4
  49. data/test/utils_tests.rb +85 -76
  50. data/test/wkb_reader_tests.rb +18 -18
  51. data/test/wkb_writer_tests.rb +15 -22
  52. data/test/wkt_reader_tests.rb +1 -4
  53. data/test/wkt_writer_tests.rb +8 -17
  54. metadata +11 -7
@@ -1,4 +1,3 @@
1
- # encoding: UTF-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module Geos
@@ -33,7 +32,7 @@ module Geos
33
32
 
34
33
  @params = {}
35
34
  VALID_PARAMETERS.each do |param|
36
- self.send("#{param}=", params[param])
35
+ send("#{param}=", params[param])
37
36
  end
38
37
  end
39
38
 
@@ -44,43 +43,33 @@ module Geos
44
43
  def endcap=(value)
45
44
  check_enum_value(Geos::BufferCapStyles, value)
46
45
 
47
- if bool_result(FFIGeos.GEOSBufferParams_setEndCapStyle_r(Geos.current_handle_pointer, ptr, value))
48
- @params[:endcap] = symbol_for_enum(Geos::BufferCapStyles, value)
49
- end
46
+ @params[:endcap] = symbol_for_enum(Geos::BufferCapStyles, value) if bool_result(FFIGeos.GEOSBufferParams_setEndCapStyle_r(Geos.current_handle_pointer, ptr, value))
50
47
  end
51
48
 
52
49
  def join=(value)
53
50
  check_enum_value(Geos::BufferJoinStyles, value)
54
51
 
55
- if bool_result(FFIGeos.GEOSBufferParams_setJoinStyle_r(Geos.current_handle_pointer, ptr, value))
56
- @params[:join] = symbol_for_enum(Geos::BufferJoinStyles, value)
57
- end
52
+ @params[:join] = symbol_for_enum(Geos::BufferJoinStyles, value) if bool_result(FFIGeos.GEOSBufferParams_setJoinStyle_r(Geos.current_handle_pointer, ptr, value))
58
53
  end
59
54
 
60
55
  def mitre_limit=(value)
61
- if bool_result(FFIGeos.GEOSBufferParams_setMitreLimit_r(Geos.current_handle_pointer, ptr, value))
62
- @params[:mitre_limit] = value
63
- end
56
+ @params[:mitre_limit] = value if bool_result(FFIGeos.GEOSBufferParams_setMitreLimit_r(Geos.current_handle_pointer, ptr, value))
64
57
  end
65
58
 
66
59
  def quad_segs=(value)
67
- if bool_result(FFIGeos.GEOSBufferParams_setQuadrantSegments_r(Geos.current_handle_pointer, ptr, value))
68
- @params[:quad_segs] = value
69
- end
60
+ @params[:quad_segs] = value if bool_result(FFIGeos.GEOSBufferParams_setQuadrantSegments_r(Geos.current_handle_pointer, ptr, value))
70
61
  end
71
62
 
72
63
  def single_sided=(value)
73
- if bool_result(FFIGeos.GEOSBufferParams_setSingleSided_r(Geos.current_handle_pointer, ptr, Geos::Tools.bool_to_int(value)))
74
- @params[:single_sided] = value
75
- end
64
+ @params[:single_sided] = value if bool_result(FFIGeos.GEOSBufferParams_setSingleSided_r(Geos.current_handle_pointer, ptr, Geos::Tools.bool_to_int(value)))
76
65
  end
77
66
 
78
67
  VALID_PARAMETERS.each do |param|
79
- self.class_eval(<<-EOF, __FILE__, __LINE__ + 1)
68
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
80
69
  def #{param}
81
70
  @params[:#{param}]
82
71
  end
83
- EOF
72
+ RUBY
84
73
  end
85
74
  else
86
75
  attr_accessor(*VALID_PARAMETERS)
@@ -89,7 +78,7 @@ module Geos
89
78
  params = Geos::Constants::BUFFER_PARAM_DEFAULTS.merge(params)
90
79
 
91
80
  VALID_PARAMETERS.each do |param|
92
- self.send("#{param}=", params[param])
81
+ send("#{param}=", params[param])
93
82
  end
94
83
  end
95
84
  end
@@ -1,8 +1,6 @@
1
- # encoding: UTF-8
2
1
  # frozen_string_literal: true
3
2
 
4
3
  module Geos
5
-
6
4
  # A CoordinateSequence is a list of coordinates in a Geometry.
7
5
  class CoordinateSequence
8
6
  class ParseError < Geos::ParseError
@@ -19,22 +17,22 @@ module Geos
19
17
  end
20
18
 
21
19
  def [](idx)
22
- parent.get_ordinate(idx, self.dimension)
20
+ parent.get_ordinate(idx, dimension)
23
21
  end
24
22
 
25
23
  def []=(idx, value)
26
- parent.set_ordinate(idx, self.dimension, value)
24
+ parent.set_ordinate(idx, dimension, value)
27
25
  end
28
26
 
29
27
  def each
30
28
  if block_given?
31
29
  parent.length.times do |n|
32
- yield parent.get_ordinate(n, self.dimension)
30
+ yield parent.get_ordinate(n, dimension)
33
31
  end
34
32
  self
35
33
  else
36
34
  parent.length.times.collect { |n|
37
- parent.get_ordinate(n, self.dimension)
35
+ parent.get_ordinate(n, dimension)
38
36
  }.to_enum
39
37
  end
40
38
  end
@@ -71,28 +69,26 @@ module Geos
71
69
  lengths = points.collect(&:length).uniq
72
70
 
73
71
  if lengths.empty?
74
- [ 0, 0 ]
72
+ [0, 0]
75
73
  elsif lengths.length != 1
76
- raise ParseError.new("Different sized points found in Array")
74
+ raise ParseError, 'Different sized points found in Array'
77
75
  elsif !lengths.first.between?(1, 3)
78
- raise ParseError.new("Expected points to contain 1-3 elements")
76
+ raise ParseError, 'Expected points to contain 1-3 elements'
79
77
  else
80
- [ points.length, points.first.length ]
78
+ [points.length, points.first.length]
81
79
  end
82
80
  elsif args.first.is_a?(Hash)
83
81
  args.first.values_at(:size, :dimensions)
82
+ elsif !args.length.between?(0, 2)
83
+ raise ArgumentError, "wrong number of arguments (#{args.length} for 0-2)"
84
84
  else
85
- if !args.length.between?(0, 2)
86
- raise ArgumentError.new("wrong number of arguments (#{args.length} for 0-2)")
87
- else
88
- [ args[0], args[1] ]
89
- end
85
+ [args[0], args[1]]
90
86
  end
91
87
 
92
88
  size ||= 0
93
89
  dimensions ||= 0
94
90
 
95
- [ FFIGeos.GEOSCoordSeq_create_r(Geos.current_handle_pointer, size, dimensions), true ]
91
+ [FFIGeos.GEOSCoordSeq_create_r(Geos.current_handle_pointer, size, dimensions), true]
96
92
  end
97
93
 
98
94
  @ptr = FFI::AutoPointer.new(
@@ -107,11 +103,11 @@ module Geos
107
103
  @y = CoordinateAccessor.new(self, 1)
108
104
  @z = CoordinateAccessor.new(self, 2)
109
105
 
110
- if points
111
- points.each_with_index do |point, idx|
112
- point.each_with_index do |val, dim|
113
- self.set_ordinate(idx, dim, val)
114
- end
106
+ return unless points
107
+
108
+ points.each_with_index do |point, idx|
109
+ point.each_with_index do |val, dim|
110
+ set_ordinate(idx, dim, val)
115
111
  end
116
112
  end
117
113
  end
@@ -135,13 +131,13 @@ module Geos
135
131
  # 2-dimensional CoordinateSequences.
136
132
  def each
137
133
  if block_given?
138
- self.length.times do |n|
139
- yield self.build_coordinate(n)
134
+ length.times do |n|
135
+ yield build_coordinate(n)
140
136
  end
141
137
  self
142
138
  else
143
- self.length.times.collect { |n|
144
- self.build_coordinate(n)
139
+ length.times.collect { |n|
140
+ build_coordinate(n)
145
141
  }.to_enum
146
142
  end
147
143
  end
@@ -149,82 +145,82 @@ module Geos
149
145
  def [](*args)
150
146
  if args.length == 1 && args.first.is_a?(Numeric) && args.first >= 0
151
147
  i = args.first
152
- ary = [ self.get_x(i), self.get_y(i) ]
153
- ary << self.get_z(i) if self.has_z?
148
+ ary = [get_x(i), get_y(i)]
149
+ ary << get_z(i) if has_z?
154
150
  ary
155
151
  else
156
- self.to_a[*args]
152
+ to_a[*args]
157
153
  end
158
154
  end
159
- alias_method :slice, :[]
155
+ alias slice []
160
156
 
161
157
  def has_z?
162
- self.dimensions == 3
158
+ dimensions == 3
163
159
  end
164
160
 
165
161
  # Sets the x value of a coordinate. Can also be set via #x[]=.
166
162
  def set_x(idx, val)
167
- self.check_bounds(idx)
168
- FFIGeos.GEOSCoordSeq_setX_r(Geos.current_handle_pointer, self.ptr, idx, val.to_f)
163
+ check_bounds(idx)
164
+ FFIGeos.GEOSCoordSeq_setX_r(Geos.current_handle_pointer, ptr, idx, val.to_f)
169
165
  end
170
166
 
171
167
  # Sets the y value of a coordinate. Can also be set via #y[]=.
172
168
  def set_y(idx, val)
173
- self.check_bounds(idx)
174
- FFIGeos.GEOSCoordSeq_setY_r(Geos.current_handle_pointer, self.ptr, idx, val.to_f)
169
+ check_bounds(idx)
170
+ FFIGeos.GEOSCoordSeq_setY_r(Geos.current_handle_pointer, ptr, idx, val.to_f)
175
171
  end
176
172
 
177
173
  # Sets the z value of a coordinate. Can also be set via #z[]=.
178
174
  def set_z(idx, val)
179
- self.check_bounds(idx)
180
- FFIGeos.GEOSCoordSeq_setZ_r(Geos.current_handle_pointer, self.ptr, idx, val.to_f)
175
+ check_bounds(idx)
176
+ FFIGeos.GEOSCoordSeq_setZ_r(Geos.current_handle_pointer, ptr, idx, val.to_f)
181
177
  end
182
178
 
183
179
  def set_ordinate(idx, dim, val)
184
- self.check_bounds(idx)
185
- FFIGeos.GEOSCoordSeq_setOrdinate_r(Geos.current_handle_pointer, self.ptr, idx, dim, val.to_f)
180
+ check_bounds(idx)
181
+ FFIGeos.GEOSCoordSeq_setOrdinate_r(Geos.current_handle_pointer, ptr, idx, dim, val.to_f)
186
182
  end
187
183
 
188
184
  # Gets the x value of a coordinate. Can also be retrieved via #x[].
189
185
  def get_x(idx)
190
- self.check_bounds(idx)
186
+ check_bounds(idx)
191
187
  double_ptr = FFI::MemoryPointer.new(:double)
192
- FFIGeos.GEOSCoordSeq_getX_r(Geos.current_handle_pointer, self.ptr, idx, double_ptr)
188
+ FFIGeos.GEOSCoordSeq_getX_r(Geos.current_handle_pointer, ptr, idx, double_ptr)
193
189
  double_ptr.read_double
194
190
  end
195
191
 
196
192
  # Gets the y value of a coordinate. Can also be retrieved via #y[].
197
193
  def get_y(idx)
198
- self.check_bounds(idx)
194
+ check_bounds(idx)
199
195
  double_ptr = FFI::MemoryPointer.new(:double)
200
- FFIGeos.GEOSCoordSeq_getY_r(Geos.current_handle_pointer, self.ptr, idx, double_ptr)
196
+ FFIGeos.GEOSCoordSeq_getY_r(Geos.current_handle_pointer, ptr, idx, double_ptr)
201
197
  double_ptr.read_double
202
198
  end
203
199
 
204
200
  # Gets the z value of a coordinate. Can also be retrieved via #z[].
205
201
  def get_z(idx)
206
- self.check_bounds(idx)
202
+ check_bounds(idx)
207
203
  double_ptr = FFI::MemoryPointer.new(:double)
208
- FFIGeos.GEOSCoordSeq_getZ_r(Geos.current_handle_pointer, self.ptr, idx, double_ptr)
204
+ FFIGeos.GEOSCoordSeq_getZ_r(Geos.current_handle_pointer, ptr, idx, double_ptr)
209
205
  double_ptr.read_double
210
206
  end
211
207
 
212
208
  def get_ordinate(idx, dim)
213
- self.check_bounds(idx)
209
+ check_bounds(idx)
214
210
  double_ptr = FFI::MemoryPointer.new(:double)
215
- FFIGeos.GEOSCoordSeq_getOrdinate_r(Geos.current_handle_pointer, self.ptr, idx, dim, double_ptr)
211
+ FFIGeos.GEOSCoordSeq_getOrdinate_r(Geos.current_handle_pointer, ptr, idx, dim, double_ptr)
216
212
  double_ptr.read_double
217
213
  end
218
214
 
219
215
  def length
220
216
  int_ptr = FFI::MemoryPointer.new(:int)
221
- FFIGeos.GEOSCoordSeq_getSize_r(Geos.current_handle_pointer, self.ptr, int_ptr)
217
+ FFIGeos.GEOSCoordSeq_getSize_r(Geos.current_handle_pointer, ptr, int_ptr)
222
218
  int_ptr.read_int
223
219
  end
224
- alias_method :size, :length
220
+ alias size length
225
221
 
226
222
  def empty?
227
- self.length == 0
223
+ length.zero?
228
224
  end
229
225
 
230
226
  def dimensions
@@ -232,47 +228,337 @@ module Geos
232
228
  @dimensions
233
229
  else
234
230
  int_ptr = FFI::MemoryPointer.new(:int)
235
- FFIGeos.GEOSCoordSeq_getDimensions_r(Geos.current_handle_pointer, self.ptr, int_ptr)
231
+ FFIGeos.GEOSCoordSeq_getDimensions_r(Geos.current_handle_pointer, ptr, int_ptr)
236
232
  @dimensions = int_ptr.read_int
237
233
  end
238
234
  end
239
235
 
236
+ if FFIGeos.respond_to?(:GEOSCoordSeq_isCCW_r)
237
+ # Available in GEOS 3.7+.
238
+ def counter_clockwise?
239
+ char_ptr = FFI::MemoryPointer.new(:char)
240
+ FFIGeos.GEOSCoordSeq_isCCW_r(Geos.current_handle_pointer, ptr, char_ptr)
241
+ Tools.bool_result(char_ptr.read_char)
242
+ end
243
+ alias ccw? counter_clockwise?
244
+ end
245
+
240
246
  def to_point(options = {})
241
- Geos.create_point(self, :srid => options[:srid])
247
+ Geos.create_point(self, srid: options[:srid])
242
248
  end
243
249
 
244
250
  def to_linear_ring(options = {})
245
- Geos.create_linear_ring(self, :srid => options[:srid])
251
+ Geos.create_linear_ring(self, srid: options[:srid])
246
252
  end
247
253
 
248
254
  def to_line_string(options = {})
249
- Geos.create_line_string(self, :srid => options[:srid])
255
+ Geos.create_line_string(self, srid: options[:srid])
250
256
  end
251
257
 
252
258
  def to_polygon(options = {})
253
- Geos.create_polygon(self, :srid => options[:srid])
259
+ Geos.create_polygon(self, srid: options[:srid])
254
260
  end
255
261
 
256
262
  def to_s
257
- self.entries.collect { |entry|
263
+ entries.collect { |entry|
258
264
  entry.join(' ')
259
265
  }.join(', ')
260
266
  end
261
267
 
262
- protected
268
+ %w{ x y z }.each do |m|
269
+ class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
270
+ def #{m}_max
271
+ ret = nil
272
+ length.times do |i|
273
+ value = get_#{m}(i)
274
+ ret = value if !ret || value >= ret
275
+ end
276
+ ret
277
+ end
278
+
279
+ def #{m}_min
280
+ ret = nil
281
+ length.times do |i|
282
+ value = get_#{m}(i)
283
+ ret = value if !ret || value <= ret
284
+ end
285
+ ret
286
+ end
287
+ RUBY
288
+ end
289
+
290
+ def snap_to_grid!(*args)
291
+ grid = {
292
+ offset_x: 0, # 1
293
+ offset_y: 0, # 2
294
+ offset_z: 0, # -
295
+ size_x: 0, # 3
296
+ size_y: 0, # 4
297
+ size_z: 0 # -
298
+ }
299
+
300
+ if args.length == 1 && args[0].is_a?(Numeric)
301
+ grid[:size_x] = grid[:size_y] = grid[:size_z] = args[0]
302
+ elsif args[0].is_a?(Hash)
303
+ grid.merge!(args[0])
304
+ end
305
+
306
+ grid[:size_x] = grid[:size_y] = grid[:size_z] = grid[:size] if grid[:size]
263
307
 
264
- def check_bounds(idx) #:nodoc:
265
- if idx < 0 || idx >= self.length
266
- raise Geos::IndexBoundsError.new("Index out of bounds")
308
+ if grid[:offset]
309
+ case grid[:offset]
310
+ when Geos::Geometry
311
+ point = grid[:offset].centroid
312
+
313
+ grid[:offset_x] = point.x
314
+ grid[:offset_y] = point.y
315
+ grid[:offset_z] = point.z
316
+ when Array
317
+ grid[:offset_x], grid[:offset_y], grid[:offset_z] = grid[:offset]
318
+ else
319
+ raise ArgumentError, 'Expected :offset option to be a Geos::Point'
320
+ end
267
321
  end
322
+
323
+ length.times do |i|
324
+ x[i] = ((x[i] - grid[:offset_x]) / grid[:size_x]).round * grid[:size_x] + grid[:offset_x] if grid[:size_x] != 0
325
+
326
+ y[i] = ((y[i] - grid[:offset_y]) / grid[:size_y]).round * grid[:size_y] + grid[:offset_y] if grid[:size_y] != 0
327
+
328
+ z[i] = ((z[i] - grid[:offset_z]) / grid[:size_z]).round * grid[:size_z] + grid[:offset_z] if has_z? && grid[:size_z] != 0
329
+ end
330
+
331
+ cs = remove_duplicate_coords
332
+ @ptr = cs.ptr
333
+
334
+ self
335
+ end
336
+
337
+ def snap_to_grid(*args)
338
+ dup.snap_to_grid!(*args)
339
+ end
340
+
341
+ def remove_duplicate_coords
342
+ Geos::CoordinateSequence.new(to_a.each_with_object([]) do |v, memo|
343
+ memo << v unless memo.last == v
344
+ end)
268
345
  end
269
346
 
270
- def build_coordinate(n) #:nodoc:
271
- [
272
- self.get_x(n),
273
- (self.dimensions >= 2 ? self.get_y(n) : nil),
274
- (self.dimensions >= 3 ? self.get_z(n) : nil)
275
- ].compact
347
+ def affine!(options)
348
+ options.default = 0.0
349
+
350
+ if has_z?
351
+ length.times do |i|
352
+ x = self.x[i]
353
+ y = self.y[i]
354
+ z = self.z[i]
355
+
356
+ self.x[i] = options[:afac] * x + options[:bfac] * y + options[:cfac] * z + options[:xoff]
357
+ self.y[i] = options[:dfac] * x + options[:efac] * y + options[:ffac] * z + options[:yoff]
358
+ self.z[i] = options[:gfac] * x + options[:hfac] * y + options[:ifac] * z + options[:zoff]
359
+ end
360
+ else
361
+ length.times do |i|
362
+ x = self.x[i]
363
+ y = self.y[i]
364
+
365
+ self.x[i] = options[:afac] * x + options[:bfac] * y + options[:xoff]
366
+ self.y[i] = options[:dfac] * x + options[:efac] * y + options[:yoff]
367
+ end
368
+ end
369
+
370
+ self
371
+ end
372
+
373
+ def affine(options)
374
+ dup.affine!(options)
375
+ end
376
+
377
+ def rotate!(radians, origin = [0.0, 0.0])
378
+ origin = case origin
379
+ when Array
380
+ origin
381
+ when Geos::Geometry
382
+ center = origin.centroid
383
+ [center.x, center.y]
384
+ else
385
+ raise ArgumentError, 'Expected an Array or a Geos::Geometry for the origin'
386
+ end
387
+
388
+ affine!(
389
+ afac: Math.cos(radians),
390
+ bfac: -Math.sin(radians),
391
+ cfac: 0,
392
+ dfac: Math.sin(radians),
393
+ efac: Math.cos(radians),
394
+ ffac: 0,
395
+ gfac: 0,
396
+ hfac: 0,
397
+ ifac: 1,
398
+ xoff: origin[0] - Math.cos(radians) * origin[0] + Math.sin(radians) * origin[1],
399
+ yoff: origin[1] - Math.sin(radians) * origin[0] - Math.cos(radians) * origin[1],
400
+ zoff: 0
401
+ )
276
402
  end
403
+
404
+ def rotate(radians, origin = [0.0, 0.0])
405
+ dup.rotate!(radians, origin)
406
+ end
407
+
408
+ def rotate_x!(radians)
409
+ affine!(
410
+ afac: 1,
411
+ bfac: 0,
412
+ cfac: 0,
413
+ dfac: 0,
414
+ efac: Math.cos(radians),
415
+ ffac: -Math.sin(radians),
416
+ gfac: 0,
417
+ hfac: Math.sin(radians),
418
+ ifac: Math.cos(radians),
419
+ xoff: 0,
420
+ yoff: 0,
421
+ zoff: 0
422
+ )
423
+ end
424
+
425
+ def rotate_x(radians)
426
+ dup.rotate_x!(radians)
427
+ end
428
+
429
+ def rotate_y!(radians)
430
+ affine!(
431
+ afac: Math.cos(radians),
432
+ bfac: 0,
433
+ cfac: Math.sin(radians),
434
+ dfac: 0,
435
+ efac: 1,
436
+ ffac: 0,
437
+ gfac: -Math.sin(radians),
438
+ hfac: 0,
439
+ ifac: Math.cos(radians),
440
+ xoff: 0,
441
+ yoff: 0,
442
+ zoff: 0
443
+ )
444
+ end
445
+
446
+ def rotate_y(radians)
447
+ dup.rotate_y!(radians)
448
+ end
449
+
450
+ def rotate_z!(radians)
451
+ rotate!(radians)
452
+ end
453
+
454
+ def rotate_z(radians)
455
+ dup.rotate!(radians)
456
+ end
457
+
458
+ def scale!(*args)
459
+ x, y, z = if args.length == 1 && args[0].is_a?(Hash)
460
+ args[0].values_at(:x, :y, :z)
461
+ elsif args.length.between?(1, 3)
462
+ args.values_at(0...3)
463
+ else
464
+ raise ArgumentError, "Wrong number of arguments #{args.length} for 1-3"
465
+ end
466
+
467
+ affine!(
468
+ afac: x || 1,
469
+ bfac: 0,
470
+ cfac: 0,
471
+ dfac: 0,
472
+ efac: y || 1,
473
+ ffac: 0,
474
+ gfac: 0,
475
+ hfac: 0,
476
+ ifac: z || 1,
477
+ xoff: 0,
478
+ yoff: 0,
479
+ zoff: 0
480
+ )
481
+ end
482
+
483
+ def scale(*args)
484
+ dup.scale!(*args)
485
+ end
486
+
487
+ def trans_scale!(*args)
488
+ delta_x, delta_y, x_factor, y_factor = if args.length == 1 && args[0].is_a?(Hash)
489
+ args[0].values_at(:delta_x, :delta_y, :x_factor, :y_factor)
490
+ elsif args.length.between?(1, 4)
491
+ args.values_at(0...4)
492
+ else
493
+ raise ArgumentError, "Wrong number of arguments #{args.length} for 1-4"
494
+ end
495
+
496
+ x_factor ||= 1
497
+ y_factor ||= 1
498
+ delta_x ||= 0
499
+ delta_y ||= 0
500
+
501
+ affine!(
502
+ afac: x_factor,
503
+ bfac: 0,
504
+ cfac: 0,
505
+ dfac: 0,
506
+ efac: y_factor,
507
+ ffac: 0,
508
+ gfac: 0,
509
+ hfac: 0,
510
+ ifac: 1,
511
+ xoff: delta_x * x_factor,
512
+ yoff: delta_y * y_factor,
513
+ zoff: 0
514
+ )
515
+ end
516
+
517
+ def trans_scale(*args)
518
+ dup.trans_scale!(*args)
519
+ end
520
+
521
+ def translate!(*args)
522
+ x, y, z = if args.length == 1 && args[0].is_a?(Hash)
523
+ args[0].values_at(:x, :y, :z)
524
+ elsif args.length.between?(1, 3)
525
+ args.values_at(0...3)
526
+ else
527
+ raise ArgumentError, "Wrong number of arguments #{args.length} for 1-3"
528
+ end
529
+
530
+ affine!(
531
+ afac: 1,
532
+ bfac: 0,
533
+ cfac: 0,
534
+ dfac: 0,
535
+ efac: 1,
536
+ ffac: 0,
537
+ gfac: 0,
538
+ hfac: 0,
539
+ ifac: 1,
540
+ xoff: x || 0,
541
+ yoff: y || 0,
542
+ zoff: z || 1
543
+ )
544
+ end
545
+
546
+ def translate(*args)
547
+ dup.translate!(*args)
548
+ end
549
+
550
+ protected
551
+
552
+ def check_bounds(idx) #:nodoc:
553
+ raise Geos::IndexBoundsError, 'Index out of bounds' if idx.negative? || idx >= length
554
+ end
555
+
556
+ def build_coordinate(n) #:nodoc:
557
+ [
558
+ get_x(n),
559
+ (dimensions >= 2 ? get_y(n) : nil),
560
+ (dimensions >= 3 ? get_z(n) : nil)
561
+ ].compact
562
+ end
277
563
  end
278
564
  end