2

I need to implement the following logic: Given a set of 2d sample points (given as x-y-coordinate pairs) and a set of line segments (also x-y-coordinate pairs). Edit 1: How to calculate (vectorized) the distance of the points pi to the lines Li?

Sample data (red) and model data (blue).

The points are approximately near the lines and I want to get the distance from each of the sample points to the nearest line segment. The may be points, which are a bit "off" (see p6 in the first picture) and these could be found by the following algorithm:

  1. Case: The sample points projection to the line segment is "left outside". In this case I need the euclidean distance from p1 to x1.
  2. Case: The sample points projection to the line segment is "inside" the line segment. In this case I need the distance from p2 to the line from x1 to x2.
  3. Case: The sample points projection to the line segment is "right outside". In this case I need the euclidean distance from p3 to x2.

enter image description here

There is a vectorized solution (thanks to User Andy) which uses the projection "globally", always assuming case 2 for each point-linesegment-pair. However, this returns the distance [1 1 1] for p1 ... p3 , where the desired distance would be [1.4142 1 1.4142]. Can this code be modified to these needs?

ptsx = [1 3 5];
ptsy= [1 1 1];

linesx = [2 4];
linesy = [0 0];

points = [ptsx;ptsy];
lines = [linesx;linesy];

% start of lines
l = [linesx(1:end-1);linesy(1:end-1)];
% vector perpendicular on line
v = [diff(linesy);-diff(linesx)];
% make unit vector
v = v ./ hypot (v(1,:),v(2,:));
v = repmat (v, 1, 1, size (points, 2));
% vector from points (in third dimension) to start of lines (second dimension)
r = bsxfun (@minus, permute (points, [1 3 2]), l);
d = abs (dot (v, r));
dist = squeeze (min (d, [], 2))

Mathematically speaking, the cases can be separated by looking at the length of the projection of vec(pi-x1) onto vec(x2-x1). If this length factor is < 0, then the point is "left" of the line segment, if it is between 0 and 1, perpendicular projection is possible, if it is > 1, then the point is "right" of the line segment.

Edit 1: I will add a pseudocode to explain how this could be solved with a double for-loop, but since I have around 6000 samples and 10000 lines, the loop solution is not an option for me.

for each sample point pi
  for each linesegment Li
    a = vector from start of Li to end of Li
    b = vector from pi to start of Li
    relLength = dot(a,b)/norm(a)^2
    if relLength < 0: distance = euclidean distance from start of Li to pi
    if relLength > 1: distance = euclidean distance from end of Li to pi
    else: distance = perpendicular distance from pi to Li
  endfor 
endfor

Edit 2 / 2017-09-07: I managed to vectorize the first part of this algorithm. relLength now contains the relative length of the projection of each pi-startOfLi onto each line segment.

ptsx = [0.5 2 3 5.5 8 11];
ptsy= [1  2 -1.5 0.5 4 5];

linesx = [0 2 4 6 10 10 0 0];
linesy = [0 0 0 0  0  4 4 0];

points = [ptsx;ptsy];
lines = [linesx;linesy];

% Start of all lines
L1 = [linesx(1:end-1); linesy(1:end-1)];
% Vector of each line segment
a = [diff(linesx); diff(linesy)];
b = bsxfun(@minus, permute(points, [1 3 2]), L1);
aRep = repmat(a, 1, 1, length(b(1,1,:)));
relLength = dot(aRep,b)./norm(a, 'cols').^2
  • You only show cases where there is only one line with start and end. What if you have a "V" shaped polygon where points can be "projected" to more than one line? As far as I have understand your previous question, case 1 and case 3 are only border cases for the first and last line segment, isn't it. By the way, this sounds a lot like a XY-Problem. If you haven't heard it http://xyproblem.info/ is really worth to read – Andy Sep 06 '17 at 17:08
  • And even more important: What have you tried so far? The code is documented and apparently you know the math – Andy Sep 07 '17 at 07:21
  • I have read xyproblem and based on that have given a simple "Task Description" as well as my solution approach to the problem. I hope I could clarify what I need to achieve. I have implemented the pseudocode that I edited into my opening post and it works, but I need a vectorized version of this. – Olbert Dämmerer Sep 07 '17 at 09:46
  • Do you always have a polygon (closed polyline)? Are the lines always horizonatl or vertical as in your case? As far as I have understand your question you always want the minimal distance from a point to the polyline (which is perpendicular to, if, you call it "within" a line) – Andy Sep 07 '17 at 13:53
  • I do not have any information about the lines. The lines can be arbitrarily distributed in space and do not have to be horizontal or vertical (hence the different cases). The only thing I can say for sure is, that there are no "gaps" in the lines, e.g. the end of a line segment will always be that start of the next one (except for first and last one, obviously). And yes, I need the minimal distance. – Olbert Dämmerer Sep 07 '17 at 14:17

2 Answers2

4

In GNU Octave:

points = [1 4.3 3.7 2.9;0.8 0.8 2.1 -0.5];
lines = [0 2 4 3.6;0 -1 1 1.75];

% plot them
hold off
plot (points(1,:), points(2,:), 'or')
hold on
plot (lines(1,:), lines(2,:), '-xb')
text (points(1,:), points(2,:),...
      arrayfun (@(x) sprintf('  p%i',x),...
                1:columns(points),'UniformOutput', false))
axis ('equal')
grid on
zoom (0.9);

% some intermediate vars
s_lines = lines (:,1:end-1);    % start of lines
d_lines = diff(lines, 1, 2);    % vectors between line points
l_lines = hypot (d_lines(1,:),
                 d_lines(2,:)); % length of lines

now do the "real" work:

% vectors perpendicular on lines
v = [0 1;-1 0] * d_lines;
vn = v ./ norm (v, 2, 'cols');   %make unit vector

% extend to number of points
vn = repmat (vn, 1, 1, columns (points));
points_3 = permute (points, [1 3 2]);

% vectors from points (in third dimension) to start of lines (second dimension)
d =  dot (vn, points_3 - s_lines);

% check if the projection is on line
tmp = dot (repmat (d_lines, 1, 1, columns (points)),...
           points_3 - s_lines)./l_lines.^2;
point_hits_line = tmp > 0 & tmp < 1;

% set othogonal distance to Inf if there is no hit
d(~ point_hits_line) = Inf;

dist = squeeze (min (abs(d), [], 2));

% calculate the euclidean distance from points to line start/stop
% if the projection doesn't hit the line
nh = isinf (dist);
tmp = points_3(:,:,nh) - lines;
tmp = hypot(tmp(1,:,:),tmp(2,:,:));
tmp = min (tmp, [], 2);
% store the result back
dist (nh) = tmp

plot the results as yellow circles around the points

% use for loops because this hasn't to be fast
t = linspace (0, 2*pi, 40);
for k=1:numel(dist)
  plot (points (1, k) + cos (t) * dist(k),
        points (2, k) + sin (t) * dist(k),
        '-y')
end

Result plot

Andy
  • 7,931
  • 4
  • 25
  • 45
  • Looks like this is a nice solution, appreciate the visual at the end. I think it might be helpful if you stuck a couple of lines in to explain the methodology though, as I'm not sure it's clear what's happening (speaking for myself of course) – Wolfie Sep 09 '17 at 21:04
1

Use octaves Geometry package

Octaves geometry package contains all necessary tools to solve the requested problem. There are two functions implementing the solution for your question:

The function distancePointPolyline and distancePointPolygon should both be able to calculate the requested distances. Polygones are closed polylines.

Example

The following script demonstrates the use of the functions. See figure for graphical result.

% Load octave geometry package (package is also available for matlab)
pkg load geometry
% Define points
points = [1,4.3,3.7,2.9; 0.8, 0.8, 2.1, -0.5]';
% Define polyline
lines  = [0, 2, 4, 3.6;   0, -1, 1, 1.75]';

% Calculate distance
d = distancePointPolyline (points,lines);

% Produce figure
figure('name','Distance from points to polyline');
hold all
drawPoint(points);
drawPolyline(lines);
drawCircle(points, d);
axis equal

Result

Graphical result of sample script

Georg W.
  • 1,292
  • 10
  • 27