geo2d 0.1.1 → 0.1.2
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/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
|