ffi-geos 1.2.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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