geo2d 0.1.0

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