geo2d 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/geo2d.gemspec +2 -2
- data/lib/geo2d.rb +57 -66
- data/test/test_geo2d.rb +4 -4
- metadata +2 -2
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/geo2d.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{geo2d}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Javier Goizueta"]
|
12
|
-
s.date = %q{2009-11-
|
12
|
+
s.date = %q{2009-11-22}
|
13
13
|
s.description = %q{Geo2D provides basic Planar Geometry functions for line-strings (poly-lines.)}
|
14
14
|
s.email = %q{jgoizueta@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/geo2d.rb
CHANGED
@@ -100,6 +100,11 @@ module Geo2D
|
|
100
100
|
x, y = self.x, self.y
|
101
101
|
Vector.new(a11*x + a12*y, a21*x + a22*y)
|
102
102
|
end
|
103
|
+
|
104
|
+
# vector rotation (center at origin); for a general rotation use Geo2D.rotation
|
105
|
+
def rotate(angle)
|
106
|
+
transform(*Geo2D.rotation_transform(angle))
|
107
|
+
end
|
103
108
|
|
104
109
|
# Apply arbitrary transformation (passed as a Proc or as a block)
|
105
110
|
def apply(prc, &blk)
|
@@ -200,7 +205,7 @@ module Geo2D
|
|
200
205
|
|
201
206
|
def contains?(point)
|
202
207
|
if self.aligned_with?(point)
|
203
|
-
l,d = self.locate_point(point)
|
208
|
+
l,d,r = self.locate_point(point)
|
204
209
|
l>=0 && l<=self.length # => d==0
|
205
210
|
else
|
206
211
|
false
|
@@ -213,46 +218,58 @@ module Geo2D
|
|
213
218
|
|
214
219
|
# Returns the position in the segment (distance from the start node along the line) of the nearest line point
|
215
220
|
# to the point (point projected on the line) and the perpendicular separation of the point from the line (the
|
216
|
-
# distance from the point to the line
|
221
|
+
# distance from the point to the line, but signed: negative when the point is on the right side of the line.
|
217
222
|
# If the last parameter is true, the resulting point is forced to lie in the segment (so the distance along
|
218
223
|
# the line is between 0 and the segment's length) and the second result is the distance from the point to the
|
219
|
-
# segment (i.e. to the closest end of the segment if the projected point lies out of the
|
224
|
+
# segment (i.e. to the closest end of the segment if the projected point lies out of the segment)
|
225
|
+
# The third returned value is a rotation that must be applied to the perpendicular vector; it is nonzero
|
226
|
+
# only when the last parameter is true and the point is closer to a segment end than to any other segment point.
|
220
227
|
def locate_point(point, corrected=false)
|
221
228
|
point = Geo2D.Vector(point)
|
222
229
|
v = point - @start
|
223
230
|
l = v.dot(direction)
|
224
231
|
d = direction.cross_z(v) # == (v-l*direction).length == v.length*Math.sin(v.angle_to(direction))
|
232
|
+
rotation = 0
|
225
233
|
|
226
234
|
if corrected
|
235
|
+
# rotation = atan2(-(l.modulo(length))*d.sign, d.abs)
|
227
236
|
if l<0
|
237
|
+
rotation = Math.atan2(d < 0 ? -l : l, d.abs)
|
228
238
|
l = 0
|
229
|
-
d = (point-@start).length
|
230
|
-
elsif l>
|
239
|
+
d = d/Math.cos(rotation) # d.sign*(point-@start).length
|
240
|
+
elsif l>self.length
|
241
|
+
l -= self.length
|
242
|
+
rotation = Math.atan2(d < 0 ? -l : l, d.abs)
|
231
243
|
l = self.length
|
232
|
-
d = (point-@end).length
|
244
|
+
d = d/Math.cos(rotation) # d = d.sign*(point-@end).length
|
233
245
|
end
|
234
246
|
end
|
235
247
|
|
236
|
-
[l, d]
|
248
|
+
[l, d, rotation]
|
237
249
|
end
|
238
250
|
|
239
251
|
# Computes the position of a point in the line given the distance along the line from the starting node.
|
240
252
|
# If a second parameter is passed it indicates the separation of the computed point in the direction
|
241
253
|
# perpendicular to the line; the point is on the left side of the line if the separation is > 0.
|
242
|
-
|
254
|
+
# The third parameter is a rotation applied to the vector from the line to the point.
|
255
|
+
def interpolate_point(parallel_distance, separation=0, rotation=0)
|
243
256
|
p = @start + self.direction*parallel_distance
|
244
|
-
|
257
|
+
unless separation==0
|
258
|
+
d = direction.ortho*separation
|
259
|
+
d = d.rotate(rotation) unless rotation==0
|
260
|
+
p += d
|
261
|
+
end
|
245
262
|
p
|
246
263
|
end
|
247
264
|
|
248
265
|
# Distance from the segment to a point
|
249
266
|
def distance_to(point)
|
250
|
-
locate_point(point, true).
|
267
|
+
locate_point(point, true)[1].abs
|
251
268
|
end
|
252
269
|
|
253
270
|
# Distance from the line that contains the segment to the point
|
254
271
|
def line_distance_to(point)
|
255
|
-
locate_point(point, false).
|
272
|
+
locate_point(point, false)[1].abs
|
256
273
|
end
|
257
274
|
|
258
275
|
def length_to(point)
|
@@ -348,17 +365,21 @@ module Geo2D
|
|
348
365
|
end
|
349
366
|
|
350
367
|
def distance_to(point)
|
351
|
-
locate_point(point, true).
|
368
|
+
locate_point(point, true)[1].abs
|
352
369
|
end
|
353
370
|
|
354
371
|
def length_to(point)
|
355
372
|
locate_point(point, true).first
|
356
373
|
end
|
357
374
|
|
358
|
-
#
|
359
|
-
#
|
360
|
-
#
|
361
|
-
|
375
|
+
# Return the position of a point in relation to the line: parallalel distance along the line,
|
376
|
+
# separation and rotation of the separation (from the perpendicular.)
|
377
|
+
# Parallalel distance is in [0,length].
|
378
|
+
# If we have l,d,r = line.locate_point(point), then the closest line point to point is:
|
379
|
+
# line.interpolate_point(l) and line.interpolate_point(l,d,r) is point; d.abs is the distance from line to point;
|
380
|
+
# point is on the left of line if d>0; on the right if d<0 and on the line if d==0.
|
381
|
+
# If we want to align a text with the line at point, we would use the angle line.angle_at(l,r)
|
382
|
+
def locate_point(point)
|
362
383
|
best = nil
|
363
384
|
|
364
385
|
total_l = 0
|
@@ -367,67 +388,33 @@ module Geo2D
|
|
367
388
|
seg = segment(i)
|
368
389
|
seg_l = seg.length
|
369
390
|
|
370
|
-
l,d = seg.locate_point(point,
|
371
|
-
max_i = n_segments-1
|
391
|
+
l,d,rotation = seg.locate_point(point, true)
|
372
392
|
|
373
|
-
if
|
374
|
-
|
375
|
-
best = [total_l+l, d]
|
376
|
-
end
|
393
|
+
if best.nil? || d.abs<best[1].abs
|
394
|
+
best = [total_l+l, d, rotation]
|
377
395
|
end
|
378
396
|
|
379
397
|
total_l += seg_l
|
380
398
|
end
|
381
399
|
|
382
|
-
if best && corrected
|
383
|
-
l, d = best
|
384
|
-
if l<0
|
385
|
-
l = 0
|
386
|
-
d = (point-points.first).length
|
387
|
-
elsif l>total_l
|
388
|
-
l = total_l
|
389
|
-
d = (point-points.last).length
|
390
|
-
end
|
391
|
-
best = [l, d]
|
392
|
-
end
|
393
|
-
|
394
400
|
best
|
395
401
|
|
396
402
|
end
|
397
403
|
|
398
|
-
|
404
|
+
# Compute a point position given the relative position in the line as returned by locate_point().
|
405
|
+
def interpolate_point(parallel_distance, separation=0, rotation=0)
|
399
406
|
# separation>0 => left side of line in direction of travel
|
400
407
|
i, l = segment_position_of(parallel_distance)
|
401
|
-
|
402
|
-
sweep = 0.0 unless sweep.kind_of?(Numeric)
|
403
|
-
if i>0 && l<sweep
|
404
|
-
a = 0.5*(segment(i-1).angle+segment(i).angle) + Math::PI/2
|
405
|
-
@vertices[i] + separation*Geo2D.Vector(Math.cos(a), Math.sin(a))
|
406
|
-
elsif i<(n_segments-1) && l>=(segment_length(i)-sweep)
|
407
|
-
a = 0.5*(segment(i).angle+segment(i+1).angle) + Math::PI/2
|
408
|
-
@vertices[i+1] + separation*Geo2D.Vector(Math.cos(a), Math.sin(a))
|
409
|
-
else
|
410
|
-
segment(i).interpolate_point(l, separation)
|
411
|
-
end
|
412
|
-
else
|
413
|
-
segment(i).interpolate_point(l, separation)
|
414
|
-
end
|
408
|
+
segment(i).interpolate_point(l, separation, rotation)
|
415
409
|
end
|
416
410
|
|
417
|
-
|
411
|
+
# Angle of the line at a point (in radians, from the X axis, counter-clockwise.)
|
412
|
+
# The rotation parameter is added to this angle, if the rotation obtained from locate_point is used,
|
413
|
+
# points external to the line have a natural orientation parallel to the line (than can be use to rotate
|
414
|
+
# texts or symbols to be aligned with the line.)
|
415
|
+
def angle_at(parallel_distance, rotation=0)
|
418
416
|
i,l = segment_position_of(parallel_distance)
|
419
|
-
|
420
|
-
sweep = 0.0 unless sweep.kind_of?(Numeric)
|
421
|
-
if i>0 && l<sweep
|
422
|
-
0.5*(segment(i-1).angle+segment(i).angle)
|
423
|
-
elsif i<(n_segments-1) && l>=(segment_length(i)-sweep)
|
424
|
-
0.5*(segment(i).angle+segment(i+1).angle)
|
425
|
-
else
|
426
|
-
segment(i).angle
|
427
|
-
end
|
428
|
-
else
|
429
|
-
segment(i).angle
|
430
|
-
end
|
417
|
+
segment(i).angle + rotation
|
431
418
|
end
|
432
419
|
|
433
420
|
# multiply by matrix [[a11, a12], [a21, a22]]
|
@@ -441,7 +428,7 @@ module Geo2D
|
|
441
428
|
end
|
442
429
|
|
443
430
|
def contains?(point)
|
444
|
-
self.locate_point(point
|
431
|
+
self.locate_point(point)[1] == 0
|
445
432
|
end
|
446
433
|
|
447
434
|
def bounds
|
@@ -506,13 +493,17 @@ module Geo2D
|
|
506
493
|
#end
|
507
494
|
end
|
508
495
|
|
496
|
+
def rotation_transform(angle)
|
497
|
+
sn = Math.sin(angle)
|
498
|
+
cs = Math.cos(angle)
|
499
|
+
[cs, sn, -sn, cs]
|
500
|
+
end
|
501
|
+
|
509
502
|
# Rotation transformation; given the center of rotation (a point, i.e. a Vector) and the angle
|
510
503
|
# this returns a procedure that can be used to apply the rotation to points.
|
511
504
|
def rotation(center, angle)
|
512
505
|
center = Vector(center)
|
513
|
-
|
514
|
-
cs = Math.cos(angle)
|
515
|
-
lambda{|p| center + (p-center).transform(cs, sn, -sn, cs)}
|
506
|
+
lambda{|p| center + (p-center).rotate(angle)}
|
516
507
|
end
|
517
508
|
|
518
509
|
end
|
data/test/test_geo2d.rb
CHANGED
@@ -63,8 +63,8 @@ class TestGeo2d < Test::Unit::TestCase
|
|
63
63
|
should "reference the points in the line consistently" do
|
64
64
|
seg_max = @seg.points.map{|p| p.length}.max
|
65
65
|
@pnts.each do |pnt|
|
66
|
-
l,d = @seg.locate_point(pnt)
|
67
|
-
pnt2 = @seg.interpolate_point(l,d)
|
66
|
+
l,d,r = @seg.locate_point(pnt)
|
67
|
+
pnt2 = @seg.interpolate_point(l,d,r)
|
68
68
|
tolerance = [pnt.modulus, seg_max].max * Float::EPSILON * 2
|
69
69
|
assert (pnt2-pnt).length < tolerance, "Point #{pnt} yields #{pnt2} [#{(pnt2-pnt).length} / #{tolerance}]"
|
70
70
|
end
|
@@ -88,8 +88,8 @@ class TestGeo2d < Test::Unit::TestCase
|
|
88
88
|
should "reference the points in the line consistently" do
|
89
89
|
seg_max = @seg.points.map{|p| p.length}.max
|
90
90
|
@pnts.each do |pnt|
|
91
|
-
l,d = @seg.locate_point(pnt)
|
92
|
-
pnt2 = @seg.interpolate_point(l,d)
|
91
|
+
l,d,r = @seg.locate_point(pnt)
|
92
|
+
pnt2 = @seg.interpolate_point(l,d,r)
|
93
93
|
tolerance = [pnt.modulus, seg_max].max * Float::EPSILON * 2
|
94
94
|
assert (pnt2-pnt).length < tolerance, "Point #{pnt} yields #{pnt2} [#{(pnt2-pnt).length} / #{tolerance}]"
|
95
95
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: geo2d
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Javier Goizueta
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-22 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|