geo2d 0.1.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Javier Goizueta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = Geo2D
2
+
3
+ Basic planar geometry functions, dealing with vectors, points, lines and line-strings.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2009 Javier Goizueta. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "geo2d"
8
+ gem.summary = %Q{Planar Geometry functions}
9
+ gem.description = %Q{Geo2D provides basic Planar Geometry functions for line-strings (poly-lines.)}
10
+ gem.email = "jgoizueta@gmail.com"
11
+ gem.homepage = "http://github.com/jgoizueta/Geo2D"
12
+ gem.authors = ["Javier Goizueta"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "Geo2D #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/geo2d.gemspec ADDED
@@ -0,0 +1,54 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{geo2d}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Javier Goizueta"]
12
+ s.date = %q{2009-11-20}
13
+ s.description = %q{Geo2D provides basic Planar Geometry functions for line-strings (poly-lines.)}
14
+ s.email = %q{jgoizueta@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "geo2d.gemspec",
27
+ "lib/geo2d.rb",
28
+ "test/helper.rb",
29
+ "test/test_geo2d.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/jgoizueta/Geo2D}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.5}
35
+ s.summary = %q{Planar Geometry functions}
36
+ s.test_files = [
37
+ "test/helper.rb",
38
+ "test/test_geo2d.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
47
+ else
48
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
+ end
53
+ end
54
+
data/lib/geo2d.rb ADDED
@@ -0,0 +1,494 @@
1
+ # Planar geometry of points and line-strings
2
+ module Geo2D
3
+
4
+ # Planar vectors; used also to represent points of the plane
5
+ class Vector
6
+
7
+ def initialize(x=0, y=0)
8
+ @x = x.to_f
9
+ @y = y.to_f
10
+ end
11
+
12
+ attr_accessor :x, :y
13
+
14
+ def modulus
15
+ Math.hypot(self.x, self.y)
16
+ end
17
+
18
+ def length
19
+ modulus
20
+ end
21
+
22
+ def argument
23
+ Math.atan2(self.y, self.x)
24
+ end
25
+
26
+ def +(other)
27
+ other = Geo2D.Vector(other)
28
+ Vector.new(self.x+other.x, self.y+other.y)
29
+ end
30
+
31
+ def -(other)
32
+ other = Geo2D.Vector(other)
33
+ Vector.new(self.x-other.x, self.y-other.y)
34
+ end
35
+
36
+ def *(scalar_or_vector)
37
+ if Numeric===scalar_or_vector
38
+ # scalar product
39
+ Vector.new(scalar_or_vector*self.x, scalar_or_vector*self.y)
40
+ else
41
+ # dot product
42
+ other = Geo2D.Vector(scalar_or_vector)
43
+ self.x*other.x + self.y*other.y
44
+ end
45
+ end
46
+
47
+ def /(scalar)
48
+ # self * 1.0/scalar
49
+ Vector.new(self.x/scalar, self.y/scalar)
50
+ end
51
+
52
+ # z coordinate of cross product
53
+ def cross_z(other)
54
+ self.x*other.y - other.x*self.y
55
+ end
56
+
57
+ def dot(other)
58
+ self.x*other.x + self.y*other.y
59
+ end
60
+
61
+ def ==(other)
62
+ self.x == other.x && self.y == other.y
63
+ end
64
+
65
+ def to_a
66
+ [self.x, self.y]
67
+ end
68
+
69
+ def to_s
70
+ "(#{self.x}, #{self.y})"
71
+ end
72
+
73
+
74
+ def split
75
+ to_a
76
+ end
77
+
78
+ # unitary vector in the direction of self
79
+ def unitary
80
+ self / self.modulus
81
+ end
82
+
83
+ # vector rotated 90 degrees counter-clockwise
84
+ def ortho
85
+ Vector.new(-self.y, self.x)
86
+ end
87
+
88
+ # angle between two vectors
89
+ def angle_to(other)
90
+ Math.atan2(cross_z(other), dot(other))
91
+ end
92
+
93
+ def aligned_with?(other)
94
+ cross_z == 0
95
+ end
96
+
97
+ # multiply by matrix [[a11, a12], [a21, a22]]
98
+ def transform(*t)
99
+ a11, a12, a21, a22 = t.flatten
100
+ x, y = self.x, self.y
101
+ Vector.new(a11*x + a12*y, a21*x + a22*y)
102
+ end
103
+
104
+ # Apply arbitrary transformation (passed as a Proc or as a block)
105
+ def apply(prc, &blk)
106
+ prc ||= blk
107
+ prc[self]
108
+ end
109
+
110
+ def coerce(scalar)
111
+ if scalar.kind_of?(Numeric)
112
+ [self, scalar]
113
+ else
114
+ raise ArgumentError, "Vector: cannot coerce #{scalar.class}"
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ module_function
121
+
122
+ # Vector constructor
123
+ def Vector(*args)
124
+ case args.size
125
+ when 2
126
+ x, y = args
127
+ when 1
128
+ arg = args.first
129
+ if arg.is_a?(Vector)
130
+ return arg
131
+ elsif arg.kind_of?(Array) && arg.size==2
132
+ x, y = arg
133
+ elsif arg.kind_of?(Hash)
134
+ if arg.has_key?(:x) && arg.has_key?(:y)
135
+ x, y = arg[:x], arg[:y]
136
+ end
137
+ else
138
+ if arg.respond_to?(:x) && arg.respond_to?(:y)
139
+ x, y = arg.x, arg.y
140
+ else
141
+ raise ArgumentError,"Invalid point definition"
142
+ end
143
+ end
144
+ else
145
+ raise ArgumentError,"Invalid number of parameters for a point"
146
+ end
147
+ Vector.new(x,y)
148
+ end
149
+
150
+ # Line segment between two points (defined by Vectors)
151
+ class LineSegment
152
+
153
+ def initialize(p1, p2)
154
+ @start = p1
155
+ @end = p2
156
+ raise ArgumentError,"Degenerate LineSegment" if p1==p2
157
+ end
158
+
159
+ attr_reader :start, :end
160
+
161
+ def points
162
+ [@start, @end]
163
+ end
164
+
165
+ def n_points
166
+ 2
167
+ end
168
+
169
+ def vector
170
+ @vector ||= (@end-@start)
171
+ end
172
+
173
+ def length
174
+ @length ||= vector.modulus
175
+ end
176
+
177
+ def angle
178
+ vector.argument
179
+ end
180
+
181
+ def angle_at(parallel_distance)
182
+ angle
183
+ end
184
+
185
+ def aligned_with?(point)
186
+ vector.aligned_width?(point-@start)
187
+ end
188
+
189
+ def contains?(point)
190
+ if self.aligned_with?(point)
191
+ l,d = self.locate_point(point)
192
+ l>=0 && l<=self.length # => d==0
193
+ else
194
+ false
195
+ end
196
+ end
197
+
198
+ def direction
199
+ @u ||= vector.unitary
200
+ end
201
+
202
+ # Returns the position in the segment (distance from the start node along the line) of the nearest line point
203
+ # to the point (point projected on the line) and the perpendicular separation of the point from the line (the
204
+ # distance from the point to the line).
205
+ # If the last parameter is true, the resulting point is forced to lie in the segment (so the distance along
206
+ # the line is between 0 and the segment's length) and the second result is the distance from the point to the
207
+ # segment (i.e. to the closest end of the segment if the projected point lies out of the segmen)
208
+ def locate_point(point, corrected=false)
209
+ point = Vector(point)
210
+ v = point - @start
211
+ l = v.dot(direction)
212
+ d = direction.cross_z(v) # == (v-l*direction).length == v.length*Math.sin(v.angle_to(direction))
213
+
214
+ if corrected
215
+ if l<0
216
+ l = 0
217
+ d = (point-@start).length
218
+ elsif l>total_l
219
+ l = self.length
220
+ d = (point-@end).length
221
+ end
222
+ end
223
+
224
+ [l, d]
225
+ end
226
+
227
+ # Computes the position of a point in the line given the distance along the line from the starting node.
228
+ # If a second parameter is passed it indicates the separation of the computed point in the direction
229
+ # perpendicular to the line; the point is on the left side of the line if the separation is > 0.
230
+ def interpolate_point(parallel_distance, separation=0)
231
+ p = @start + self.direction*parallel_distance
232
+ p += direction.ortho*separation unless separation==0
233
+ p
234
+ end
235
+
236
+ # Distance from the segment to a point
237
+ def distance_to(point)
238
+ locate_point(point, true).last
239
+ end
240
+
241
+ # Distance from the line that contains the segment to the point
242
+ def line_distance_to(point)
243
+ locate_point(point, false).last
244
+ end
245
+
246
+ def length_to(point)
247
+ locate_point(point, true).first
248
+ end
249
+
250
+ # multiply by matrix [[a11, a12], [a21, a22]]
251
+ def transform(*t)
252
+ LineSegment.new(@start.transform(*t), @end = @end.transform(*t))
253
+ end
254
+
255
+ # Apply arbitrary transformation (passed as a Proc or as a block)
256
+ def apply(prc, &blk)
257
+ prc ||= blk
258
+ LineSegment.new(prc[@start], prc[@end])
259
+ end
260
+
261
+ # Returns the side of the line that contains the segment in which the point lies:
262
+ # * +1 the point is to the left of the line (as seen from the orientation of the segment)
263
+ # * -1 is in the right side
264
+ # * 0 the point is on the line
265
+ def side_of(point)
266
+ v = vector.cross_z(point-@start)
267
+ v < 0 ? -1 : (v > 0 ? +1 : 0)
268
+ end
269
+
270
+ end
271
+
272
+ class LineString
273
+
274
+ def initialize(*vertices)
275
+ @vertices = vertices
276
+
277
+ to_remove = []
278
+ prev = nil
279
+ @vertices.each_with_index do |v, i|
280
+ to_remove << i if prev && prev==v
281
+ prev = v
282
+ end
283
+ to_remove.each do |i|
284
+ @vertices.delete_at i
285
+ end
286
+
287
+ end
288
+
289
+ def start
290
+ @vertices.first
291
+ end
292
+
293
+ def end
294
+ @vertices.last
295
+ end
296
+
297
+ def length
298
+ @length ||= total_length
299
+ end
300
+
301
+ def n_points
302
+ @vertices.size
303
+ end
304
+ def points
305
+ @vertices
306
+ end
307
+ def each_point
308
+ @vertices.each do |v|
309
+ yield v
310
+ end
311
+ end
312
+
313
+ def n_segments
314
+ [n_points - 1,0].max
315
+ end
316
+
317
+ def segments
318
+ (0...n_segments).to_a.map{|i| segment(i)}
319
+ end
320
+
321
+ def each_segment
322
+ (0...n_segments).each do |i|
323
+ yield segment(i)
324
+ end
325
+ end
326
+
327
+ def segment(i)
328
+ raise ArgumentError, "Invalid segment index #{i}" unless i>=0 && i<n_segments
329
+ LineSegment.new(@vertices[i],@vertices[i+1])
330
+ end
331
+
332
+ def distance_to(point)
333
+ locate_point(point, true).last
334
+ end
335
+
336
+ def length_to(point)
337
+ locate_point(point, true).first
338
+ end
339
+
340
+ # return parallalel distance and separation;
341
+ # if corrected, then parallalel distance is in [0,length] (the point is inside the line)
342
+ # parallel distance in [0,length] , separation
343
+ def locate_point(point, corrected=false)
344
+ best = nil
345
+
346
+ total_l = 0
347
+ (0...n_segments).each do |i|
348
+
349
+ seg = segment(i)
350
+ seg_l = seg.length
351
+
352
+ l,d = seg.locate_point(point, false)
353
+ max_i = n_segments-1
354
+
355
+ if (l>0 || i==0) && (l<=seg_l || i==max_i)
356
+ if best.nil? || d<best.last
357
+ best = [total_l+l, d]
358
+ end
359
+ end
360
+
361
+ total_l += seg_l
362
+ end
363
+
364
+ if best && corrected
365
+ l, d = best
366
+ if l<0
367
+ l = 0
368
+ d = (point-points.first).length
369
+ elsif l>total_l
370
+ l = total_l
371
+ d = (point-points.last).length
372
+ end
373
+ best = [l, d]
374
+ end
375
+
376
+ best
377
+
378
+ end
379
+
380
+ def interpolate_point(parallel_distance, separation=0, sweep=nil)
381
+ # separation>0 => left side of line in direction of travel
382
+ i, l = segment_position_of(parallel_distance)
383
+ if sweep && separation!=0
384
+ sweep = 0.0 unless sweep.kind_of?(Numeric)
385
+ if i>0 && l<sweep
386
+ a = 0.5*(segment(i-1).angle+segment(i).angle) + Math::PI/2
387
+ @vertices[i] + separation*Vector(Math.cos(a), Math.sin(a))
388
+ elsif i<(n_segments-1) && l>=(segment_length(i)-sweep)
389
+ a = 0.5*(segment(i).angle+segment(i+1).angle) + Math::PI/2
390
+ @vertices[i+1] + separation*Vector(Math.cos(a), Math.sin(a))
391
+ else
392
+ segment(i).interpolate_point(l, separation)
393
+ end
394
+ else
395
+ segment(i).interpolate_point(l, separation)
396
+ end
397
+ end
398
+
399
+ def angle_at(parallel_distance, sweep=false)
400
+ i,l = segment_position_of(parallel_distance)
401
+ if sweep
402
+ sweep = 0.0 unless sweep.kind_of?(Numeric)
403
+ if i>0 && l<sweep
404
+ 0.5*(segment(i-1).angle+segment(i).angle)
405
+ elsif i<(n_segments-1) && l>=(segment_length(i)-sweep)
406
+ 0.5*(segment(i).angle+segment(i+1).angle)
407
+ else
408
+ segment(i).angle
409
+ end
410
+ else
411
+ segment(i).angle
412
+ end
413
+ end
414
+
415
+ # multiply by matrix [[a11, a12], [a21, a22]]
416
+ def transform(*t)
417
+ LineString.new(*@vertices.map{|v| v.transforme(*t)})
418
+ end
419
+
420
+ def apply(prc=nil, &blk)
421
+ prc = prc || blk
422
+ LineString.new(*@vertices.map{|v| prc[v]})
423
+ end
424
+
425
+ def contains?(point)
426
+ self.locate_point(point, true).last == 0
427
+ end
428
+
429
+ private
430
+
431
+ def segment_length(i)
432
+ raise ArgumentError, "Invalid segment index #{i}" unless i>=0 && i<n_segments
433
+ @segment_lengths ||= [nil]*n_segments
434
+ @segment_lengths[i] ||= (@vertices[i+1]-@vertices[i]).modulus
435
+ end
436
+
437
+ def total_length
438
+ l = 0
439
+ (0...n_segments).each do |i|
440
+ l += segment_length(i)
441
+ end
442
+ l
443
+ end
444
+
445
+ # find segment and distance in segment corresponding to total parallel distance TODO: rename
446
+ def segment_position_of(l)
447
+ i = 0
448
+ max_i = n_segments-1
449
+ while l>(s=segment_length(i)) && i<max_i
450
+ l -= s
451
+ i += 1
452
+ end
453
+ return i, l
454
+ end
455
+
456
+ # compute parallel distance of position in segment TODO: rename
457
+ def distance_along_line_of(segment_i, distance_in_segment)
458
+ l = 0
459
+ (0...segment_i).each do |i|
460
+ l += segment_length(i)
461
+ end
462
+ l + distance_in_segment
463
+ end
464
+
465
+ end
466
+
467
+ def Point(*args)
468
+ Vector(*args)
469
+ end
470
+
471
+ # Segment constructor
472
+ def LineSegment(start_point, end_point)
473
+ LineSegment.new(Vector(start_point), Vector(end_point))
474
+ end
475
+
476
+ # Line-string constructor
477
+ def Line(*args)
478
+ #if args.size<3
479
+ # LineSegment.new(*args.map{|arg| Vector(arg)})
480
+ #else
481
+ LineString.new(*args.map{|arg| Vector(arg)})
482
+ #end
483
+ end
484
+
485
+ # Rotation transformation; given the center of rotation (a point, i.e. a Vector) and the angle
486
+ # this returns a procedure that can be used to apply the rotation to points.
487
+ def rotation(center, angle)
488
+ center = Vector(center)
489
+ sn = Math.sin(angle)
490
+ cs = Math.cos(angle)
491
+ lambda{|p| center + (p-center).transform(cs, sn, -sn, cs)}
492
+ end
493
+
494
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'geo2d'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,51 @@
1
+ require 'helper'
2
+
3
+ class TestGeo2d < Test::Unit::TestCase
4
+ include Geo2D
5
+ context "A Vector" do
6
+ setup do
7
+ @vector = Vector.new(10,20)
8
+ end
9
+ should "be constructible by components" do
10
+ assert_equal @vector, Vector(10,20)
11
+ end
12
+ should "be constructible by an array" do
13
+ assert_equal @vector, Vector([10,20])
14
+ end
15
+ should "be constructible by a vector" do
16
+ assert_equal @vector, Vector(@vector)
17
+ end
18
+ should "be constructible by a hash" do
19
+ assert_equal @vector, Vector(:x=>10, :y=>20)
20
+ end
21
+ should "be not constructible by any number of components other than 2" do
22
+ assert_raise(ArgumentError){Vector(10,20,30)}
23
+ assert_raise(ArgumentError){Vector(10)}
24
+ assert_raise(ArgumentError){Vector([10,20,30])}
25
+ assert_raise(ArgumentError){Vector([10])}
26
+ end
27
+ should "have a modulus" do
28
+ assert_equal Math.hypot(10,20), @vector.modulus
29
+ end
30
+ should "have an argument" do
31
+ assert_equal Math.atan2(20,10), @vector.argument
32
+ end
33
+ should "have a conmutative scalar product" do
34
+ assert_equal Vector.new(20,40), 2*@vector
35
+ assert_equal Vector.new(20,40), @vector*2
36
+ end
37
+ should "have a conmutative dot product" do
38
+ assert_equal 70, Vector(10,20)*Vector(3,2)
39
+ assert_equal 70, Vector(3,2)*Vector(10,20)
40
+ end
41
+ should "have equality and inequality operators" do
42
+ assert @vector==Vector(10,20)
43
+ assert @vector!=Vector(20,10)
44
+ end
45
+ should "be splittable" do
46
+ assert [10,20] == @vector.split
47
+ assert [10,20] == @vector.to_a
48
+ end
49
+ end
50
+ end
51
+
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geo2d
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Javier Goizueta
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-20 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Geo2D provides basic Planar Geometry functions for line-strings (poly-lines.)
26
+ email: jgoizueta@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - .document
36
+ - .gitignore
37
+ - LICENSE
38
+ - README.rdoc
39
+ - Rakefile
40
+ - VERSION
41
+ - geo2d.gemspec
42
+ - lib/geo2d.rb
43
+ - test/helper.rb
44
+ - test/test_geo2d.rb
45
+ has_rdoc: true
46
+ homepage: http://github.com/jgoizueta/Geo2D
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options:
51
+ - --charset=UTF-8
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Planar Geometry functions
73
+ test_files:
74
+ - test/helper.rb
75
+ - test/test_geo2d.rb