1

For example, using the following code, I have a coordinate matrix with 3 cubical objects defined by 8 corners each, for a total of 24 coordinates. I apply a rotation to my coordinates, then delete the y coordinate to obtain a projection in the x-z plane. How do I calculate the area of these cubes in the x-z plane, ignoring gaps and accounting for overlap? I have tried using polyarea, but this doesn't seem to work.

clear all
clc
A=[-100 -40 50
-100    -40 0
-120    -40 50
-120    -40 0
-100    5   0
-100    5   50
-120    5   50
-120    5   0
-100    0   52
-100    0   52
20  0   5
20  0   5
-100    50  5
-100    50  5
20  50  52
20  50  52
-30 70  53
-30 70  0
5   70  0
5   70  53
-30 120 53
-30 120 0
5   120 53
5   120 0]; %3 Buildings Coordinate Matrix
theta=60; %Angle
rota = [cosd(theta) -sind(theta) 0; sind(theta) cosd(theta) 0; 0 0 1]; %Rotation matrix
R=A*rota; %rotates the matrix
R(:,2)=[];%deletes the y column
gnovice
  • 125,304
  • 15
  • 256
  • 359
g0dspl4y
  • 27
  • 1
  • 4

2 Answers2

3

The first step will be to use convhull (as yar suggests) to get an outline of each projected polygonal region. It should be noted that a convex hull is appropriate to use here since you are dealing with cuboids, which are convex objects. I think you have an error in the coordinates for your second cuboid (located in A(9:16, :)), so I modified your code to the following:

A = [-100   -40    50
     -100   -40     0
     -120   -40    50
     -120   -40     0
     -100     5     0
     -100     5    50
     -120     5    50
     -120     5     0
     -100     0    52
     -100     0     5
       20     0    52
       20     0     5
     -100    50     5
     -100    50    52
       20    50     5
       20    50    52
      -30    70    53
      -30    70     0
        5    70     0
        5    70    53
      -30   120    53
      -30   120     0
        5   120    53
        5   120     0];
theta = 60;
rota = [cosd(theta) -sind(theta) 0; sind(theta) cosd(theta) 0; 0 0 1];
R = A*rota;

And you can generate the polygonal outlines and visualize them like so:

nPerPoly = 8;
nPoly = size(R, 1)/nPerPoly;
xPoly = mat2cell(R(:, 1), nPerPoly.*ones(1, nPoly));
zPoly = mat2cell(R(:, 3), nPerPoly.*ones(1, nPoly));
C = cell(1, nPoly);
for iPoly = 1:nPoly
  P = convhull(xPoly{iPoly}, zPoly{iPoly});
  xPoly{iPoly} = xPoly{iPoly}(P);
  zPoly{iPoly} = zPoly{iPoly}(P);
  C{iPoly} = P([1:end-1; 2:end].')+nPerPoly.*(iPoly-1);  % Constrained edges, needed later
end

figure();
colorOrder = get(gca, 'ColorOrder');
nColors = size(colorOrder, 1);
for iPoly = 1:nPoly
  faceColor = colorOrder(rem(iPoly-1, nColors)+1, :);
  patch(xPoly{iPoly}, zPoly{iPoly}, faceColor, 'EdgeColor', faceColor, 'FaceAlpha', 0.6);
  hold on;
end
axis equal;
axis off;

And here's the plot:

enter image description here

If you wanted to calculate the area of each polygonal projection and add them up it would be very easy: just change the above loop to capture and sum the second output from the calls to convexhull:

totalArea = 0;
for iPoly = 1:nPoly
  [~, cuboidArea] = convhull(xPoly{iPoly}, zPoly{iPoly});
  totalArea = totalArea+cuboidArea;
end

However, if you want the area of the union of the polygons, you have to account for the overlap. You have a few alternatives. If you have the Mapping Toolbox then you could use the function polybool to get the outline, then use polyarea to compute its area. There are also utilities you can find on the MathWorks File Exchange (such as this and this). I'll show you another alternative here that uses delaunayTriangulation. First we can take the edge constraints C created above to use when creating a triangulation of the projected points:

oldState = warning('off', 'all');
DT = delaunayTriangulation(R(:, [1 3]), vertcat(C{:}));
warning(oldState);

This will automatically create new vertices where the constrained edges intersect. Unfortunately, it will also perform the triangulation on the convex hull of all the points, filling in spots that we don't want filled. Here's what the triangulation looks like:

figure();
triplot(DT, 'Color', 'k');
axis equal;
axis off;

enter image description here

We now have to identify the extra triangles we don't want and remove them. We can do this by finding the centroids of each triangle and using inpolygon to test if they are outside all 3 of our individual cuboid projections. We can then compute the areas of the remaining triangles and sum them up using polyarea, giving us the total area of the projection:

dtFaces = DT.ConnectivityList;
dtVertices = DT.Points;
meanX = mean(reshape(dtVertices(dtFaces, 1), size(dtFaces)), 2);
meanZ = mean(reshape(dtVertices(dtFaces, 2), size(dtFaces)), 2);
index = inpolygon(meanX, meanZ, xPoly{1}, zPoly{1});
for iPoly = 2:nPoly
  index = index | inpolygon(meanX, meanZ, xPoly{iPoly}, zPoly{iPoly});
end
dtFaces = dtFaces(index, :);
xUnion = reshape(dtVertices(dtFaces, 1), size(dtFaces)).';
yUnion = reshape(dtVertices(dtFaces, 2), size(dtFaces)).';
totalArea = sum(polyarea(xUnion, yUnion));

And the total area for this example is:

totalArea =

     9.970392341143055e+03

NOTE: The above code has been generalized for an arbitrary number of cuboids.

gnovice
  • 125,304
  • 15
  • 256
  • 359
  • I'm trying to extend this code to multiple cuboids with a for loop, but I am getting a difference in area when testing it for the A matrix with 3 buildings. What am I doing wrong? – g0dspl4y Nov 23 '17 at 16:19
  • for i=1:length(R)/8 x(i,:) = R(1+8*(i-1):8*i, 1); z(i,:) = R(1+8*(i-1):8*i, 3); P(:,i)=convhull(x(i,:),z(i,:)); P=P(:,i); C{i} = P([1:end-1; 2:end].')+8*(i-1); end combine=[C{1}; C{2}; C{3}]; oldState = warning('off', 'MATLAB:delaunayTriangulation:ConsConsSplitWarnId'); DT = delaunayTriangulation(R(:, [1 3]), combine); warning(oldState); dtVertices = DT.Points; xUnion = dtVertices(P(:, 1), 1); yUnion = dtVertices(P(:, 1), 2); totalArea = polyarea(xUnion, yUnion) – g0dspl4y Nov 23 '17 at 16:19
  • 1
    @g0dspl4y: I added a generalized version to the end of my answer. – gnovice Nov 25 '17 at 07:33
  • Thank you so much for your help. However, I want to ignore the gaps and account for overlap. The union succeeds in accounting for overlap, but the gaps are also included in the area calculation. For example, A=[0 0 1; 0 1 1; 1 1 0; 0 1 0; 1 0 0; 1 0 1; 1 1 1; 0 0 0; 50 50 1; 50 51 1; 51 51 0; 50 51 0; 51 50 0; 51 50 1; 51 51 1; 50 50 0] should give me 2 but it gives me 50. – g0dspl4y Nov 27 '17 at 14:09
  • 1
    @g0dspl4y: I fixed the last step of the code. It was an issue with `freeBoundary` connecting the separate regions. The new approach is simpler and should account for gaps now. – gnovice Nov 27 '17 at 15:33
  • 1
    Wonderful answer. +1. – rayryeng Nov 27 '17 at 15:35
  • Thank you again, but when I plug in the fix, I get a value of 6850 for the area of the original A matrix in the problem. – g0dspl4y Nov 27 '17 at 19:12
  • 1
    @g0dspl4y: That sounds right for the unrotated data. Did you forget to apply the rotation first? – gnovice Nov 27 '17 at 19:21
  • Yes! Thank you very much – g0dspl4y Nov 27 '17 at 21:34
1

polyarea is the right way to go, but you need to call it on the convex hull of each projection. If not, you will have points in the centers of your projections and the result is not a "simple" polygon.

rayryeng
  • 102,964
  • 22
  • 184
  • 193
yar
  • 1,855
  • 13
  • 26