laptimer-geometry 0.0.1
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/spec/lap_counter_spec.rb +8 -0
- data/spec/line_spec.rb +137 -0
- data/src/geometry.rb +129 -0
- data/src/lap_counter.rb +2 -0
- metadata +69 -0
data/spec/line_spec.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require File.expand_path('../../src/geometry', __FILE__)
|
3
|
+
|
4
|
+
describe Line do
|
5
|
+
before do
|
6
|
+
# first semi-line
|
7
|
+
@pointA = {:x => 2, :y => 1}
|
8
|
+
@pointB = {:x => 4, :y => 3}
|
9
|
+
#second semi-line
|
10
|
+
@pointC = {:x => 3, :y => 1}
|
11
|
+
@pointD = {:x => 2, :y => 4}
|
12
|
+
#third semi-line
|
13
|
+
@pointE = {:x => 3, :y => 0}
|
14
|
+
@pointF = {:x => 1, :y => 2}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find the gradient of the straight line" do
|
18
|
+
sl = Line.new(@pointA, @pointB)
|
19
|
+
sl.gradient.should be_eql(1.to_f)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should find the linear touch of the line" do
|
23
|
+
sl = Line.new(@pointA, @pointB)
|
24
|
+
sl.linear_touch.should be_eql(-1.to_f)
|
25
|
+
|
26
|
+
# Double Test
|
27
|
+
sl2 = Line.new(@pointE, @pointF)
|
28
|
+
sl2.linear_touch.should be_eql(3.to_f)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should include anotther point from the line" do
|
32
|
+
pointC = {:x => 3, :y => 2}
|
33
|
+
sl = Line.new(@pointA, @pointB)
|
34
|
+
sl.include?(pointC).should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should intercept with the other line (3,0) and (1,2)" do
|
38
|
+
sl = Line.new(@pointA, @pointB)
|
39
|
+
sl2 = Line.new(@pointC, @pointD)
|
40
|
+
sl.intercept?(sl2).should be_true
|
41
|
+
sl3 = Line.new(@pointE, @pointF)
|
42
|
+
sl.intercept?(sl3).should be_true
|
43
|
+
end
|
44
|
+
|
45
|
+
it "two concurrent lines should intercept at (2,2)" do
|
46
|
+
pA = {:x => 1, :y => 1}
|
47
|
+
pB = {:x => 3, :y => 3}
|
48
|
+
pC = {:x => 1, :y => 3}
|
49
|
+
pD = {:x => 3, :y => 1}
|
50
|
+
sl = Line.new(pA, pB)
|
51
|
+
sl2 = Line.new(pC, pD)
|
52
|
+
sl.intercept?(sl2).should be_true
|
53
|
+
sl.intercept_at(sl2).should be_eql({:x => 2.0, :y => 2.0})
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not intercept with a parallel line" do
|
57
|
+
pA = {:x => 1, :y => 1}
|
58
|
+
pB = {:x => 3, :y => 3}
|
59
|
+
pC = {:x => 2, :y => 1}
|
60
|
+
pD = {:x => 3, :y => 2}
|
61
|
+
sl = Line.new(pA, pB)
|
62
|
+
sl2 = Line.new(pC, pD)
|
63
|
+
sl.intercept?(sl2).should be_false
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should intercept between the 2 original points of the first line" do
|
67
|
+
pA = {:x => 1, :y => 1}
|
68
|
+
pB = {:x => 3, :y => 3}
|
69
|
+
pC = {:x => 1, :y => 3}
|
70
|
+
pD = {:x => 3, :y => 1}
|
71
|
+
sl = Line.new(pA, pB)
|
72
|
+
sl2 = Line.new(pC, pD)
|
73
|
+
sl.intercept_line_segment?(sl2).should be_true
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not find an interception between the two segments" do
|
77
|
+
pA = {:x => 1, :y => 1}
|
78
|
+
pB = {:x => 3, :y => 3}
|
79
|
+
pC = {:x => 4, :y => 3}
|
80
|
+
pD = {:x => 6, :y => 1}
|
81
|
+
sl = Line.new(pA, pB)
|
82
|
+
sl2 = Line.new(pC, pD)
|
83
|
+
sl.intercept_line_segment?(sl2).should be_false
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should intercept event in the negative Y quadrant" do
|
87
|
+
flag_line = Line.new({:x => 1, :y => -1, }, {:x => 3, :y => -4})
|
88
|
+
segment1 = Line.new({:x => 1, :y => -3}, {:x => 3, :y => -1})
|
89
|
+
|
90
|
+
flag_line.intercept_line_segment?(segment1).should be_true
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
it "should intercept event in the negative X quadrant" do
|
95
|
+
flag_line = Line.new({:x => -1, :y => 1, }, {:x => -3, :y => 4})
|
96
|
+
segment1 = Line.new({:x => -3, :y => 1}, {:x => -1, :y => 3})
|
97
|
+
|
98
|
+
flag_line.intercept_line_segment?(segment1).should be_true
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should intercept event in the all negative quadrant" do
|
102
|
+
flag_line = Line.new({:x => -1, :y => -1, }, {:x => -3, :y => -3})
|
103
|
+
segment1 = Line.new({:x => -1, :y => -3}, {:x => -2, :y => -2})
|
104
|
+
|
105
|
+
flag_line.intercept_line_segment?(segment1).should be_true
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should intercept when segments are in different quadrants" do
|
109
|
+
flag_line = Line.new({:x => -1, :y => 1, }, {:x => -3, :y => 4})
|
110
|
+
segment1 = Line.new({:x => 1, :y => 3}, {:x => 3, :y => 1})
|
111
|
+
|
112
|
+
flag_line.intercept_line_segment?(segment1).should be_false
|
113
|
+
end
|
114
|
+
|
115
|
+
it "should calculate the distance using the Earth radius Nelson Piquet" do
|
116
|
+
point1 = {:x => -47.900648, :y => -15.772954}
|
117
|
+
point2 = {:x => -47.900365, :y => -15.772869}
|
118
|
+
point3 = {:x => -47.90025, :y => -15.772835}
|
119
|
+
Geometry.calculate_distance(point1, point2).should be_true
|
120
|
+
Geometry.calculate_distance(point1, point3).should be_true
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should find the crossing segment with some real coordinates - Aut. Nelson Piquet" do
|
124
|
+
flag_line = Line.new({:x => -47.900333, :y => -15.772656, }, {:x => -47.900148, :y => -15.773056})
|
125
|
+
segment1 = Line.new({:x => -47.900648, :y => -15.772954}, {:x =>-47.899923, :y => -15.772683})
|
126
|
+
|
127
|
+
flag_line.intercept_line_segment?(segment1).should be_true
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should not accept the crossing form real segments with they do not touch - Aut. Nelson Piquet" do
|
131
|
+
flag_line = Line.new({:x => -47.900333, :y => -15.772656, }, {:x => -47.900148, :y => -15.773056})
|
132
|
+
segment1 = Line.new({:x => -47.900648, :y => -15.772954}, {:x =>-47.900365, :y => -15.772869})
|
133
|
+
|
134
|
+
flag_line.intercept_line_segment?(segment1).should be_false
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
data/src/geometry.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
module Geometry
|
2
|
+
include Math
|
3
|
+
extend Math
|
4
|
+
|
5
|
+
def distance(point1, point2)
|
6
|
+
x1 = (point1[:x] > 0) ? point1[:x] : (point1[:x]*-1 )
|
7
|
+
x2 = (point2[:x] > 0) ? point2[:x] : (point2[:x]*-1 )
|
8
|
+
y1 = (point1[:y] > 0) ? point1[:y] : (point1[:y]*-1 )
|
9
|
+
y2 = (point2[:y] > 0) ? point2[:y] : (point2[:y]*-1 )
|
10
|
+
hypot x1 - x2, y1 - y2
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate_distance(point1, point2)
|
14
|
+
x1 = point1[:x] * Math::PI / 180
|
15
|
+
x2 = point2[:x] * Math::PI / 180
|
16
|
+
y1 = point1[:y] * Math::PI / 180
|
17
|
+
y2 = point2[:y] * Math::PI / 180
|
18
|
+
|
19
|
+
s = sin(y2)*sin(y1)+(cos(y2)*cos(y1)*cos(x2 - x1))
|
20
|
+
rad_distance = acos(s)
|
21
|
+
degree_distance = rad_distance/(Math::PI/180)
|
22
|
+
earth_radius = 6378
|
23
|
+
distance_km = earth_radius*degree_distance
|
24
|
+
distance_km
|
25
|
+
end
|
26
|
+
|
27
|
+
module_function :distance
|
28
|
+
module_function :calculate_distance
|
29
|
+
end
|
30
|
+
|
31
|
+
class Line
|
32
|
+
include Math
|
33
|
+
def initialize(pointA, pointB)
|
34
|
+
@a, @b, @c = nil, nil, nil
|
35
|
+
@gradient = nil
|
36
|
+
@linear = nil
|
37
|
+
@pointA = pointA
|
38
|
+
@pointB = pointB
|
39
|
+
calculate_gradient
|
40
|
+
calculate_linear
|
41
|
+
end
|
42
|
+
|
43
|
+
def pointA
|
44
|
+
@pointA
|
45
|
+
end
|
46
|
+
|
47
|
+
def pointB
|
48
|
+
@pointB
|
49
|
+
end
|
50
|
+
|
51
|
+
def gradient(ac = nil)
|
52
|
+
if ac.nil?
|
53
|
+
@gradient
|
54
|
+
else
|
55
|
+
@gradient = ac
|
56
|
+
end
|
57
|
+
|
58
|
+
@gradient
|
59
|
+
end
|
60
|
+
|
61
|
+
# m(x2-x1) - y2 + y1 = 0
|
62
|
+
def include?(point)
|
63
|
+
result = @gradient * (point[:x] - @pointA[:x]) - point[:y] + @pointA[:y]
|
64
|
+
if(result == 0)
|
65
|
+
true
|
66
|
+
else
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def intercept?(line)
|
72
|
+
(intercept_at(line).nil? == true) ? false : true
|
73
|
+
end
|
74
|
+
|
75
|
+
def intercept_at(line)
|
76
|
+
if @gradient != line.gradient
|
77
|
+
x = calculate_x_from_two_lines(self, line)
|
78
|
+
y = (@gradient*x.to_f) + @linear
|
79
|
+
|
80
|
+
new_point = { :x => sprintf('%.6f',x).to_f, :y => sprintf('%.6f',y).to_f}
|
81
|
+
return new_point
|
82
|
+
end
|
83
|
+
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def linear_touch
|
88
|
+
@linear
|
89
|
+
end
|
90
|
+
|
91
|
+
def intercept_line_segment?(line)
|
92
|
+
|
93
|
+
# verify the distance
|
94
|
+
if intercept?(line)
|
95
|
+
interception_point = intercept_at(line)
|
96
|
+
|
97
|
+
distance_segment = Geometry.calculate_distance(line.pointA, line.pointB)
|
98
|
+
distance_A_line_segment = Geometry.calculate_distance(line.pointA, interception_point)
|
99
|
+
distance_B_line_segment = Geometry.calculate_distance(line.pointB, interception_point)
|
100
|
+
|
101
|
+
if distance_A_line_segment > distance_segment
|
102
|
+
return false
|
103
|
+
elsif distance_B_line_segment > distance_segment
|
104
|
+
return false
|
105
|
+
else
|
106
|
+
return true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def calculate_gradient
|
115
|
+
@gradient = (@pointB[:y] - @pointA[:y]) / (@pointB[:x] - @pointA[:x]).to_f
|
116
|
+
end
|
117
|
+
|
118
|
+
def calculate_linear
|
119
|
+
@linear = nil
|
120
|
+
if not @gradient.nil?
|
121
|
+
@linear = @pointA[:y].to_f - (@gradient * @pointA[:x].to_f )
|
122
|
+
end
|
123
|
+
@linear
|
124
|
+
end
|
125
|
+
|
126
|
+
def calculate_x_from_two_lines(line1, line2)
|
127
|
+
(line2.linear_touch - line1.linear_touch) / (line1.gradient - line2.gradient)
|
128
|
+
end
|
129
|
+
end
|
data/src/lap_counter.rb
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: laptimer-geometry
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Eduardo Marques
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-10-03 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: " laptimer-geometry is a gem created to help the coordinates calculations used in a laptimer software.\n"
|
22
|
+
email: edhana@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- src/geometry.rb
|
31
|
+
- src/lap_counter.rb
|
32
|
+
- spec/lap_counter_spec.rb
|
33
|
+
- spec/line_spec.rb
|
34
|
+
homepage: http://github.com/edhana/laptimer-geometry
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
|
40
|
+
require_paths:
|
41
|
+
- src
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
hash: 3
|
48
|
+
segments:
|
49
|
+
- 0
|
50
|
+
version: "0"
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.5
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Implementation of basic 2D geometry algorithms, to help with a lap timer system written in Ruby
|
67
|
+
test_files:
|
68
|
+
- spec/lap_counter_spec.rb
|
69
|
+
- spec/line_spec.rb
|