How do I calculate the area of a 3 dimensional projection? - matlab

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

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:
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;
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.

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.

Related

Values do not match data in matlab geographic plot

I am plotting trajectories in Matlab using contourf. I am having an issue with the colors matching the data. I have posted my current image below. All areas that do not have data should be white as they are zero which I specifically specified in the script(they are currently blue-ish which is the the 0.1 to 1 range). In addition, values that are yellow, should be in the blue range(<1). Any suggestions?
Here is the part of my script where I do the plotting:
axesm('mercator', 'MapLatLim', latlim, 'MapLonLim', lonlim,...
'Frame', 'on', 'Grid', 'on', 'MeridianLabel', 'on', 'ParallelLabel', 'on')
setm(gca,'mlabelparallel',-20)
load coastlines
Contours = [0.001 0.01 0.1 1 10 100];
[c,h] = contourfm(latlim, lonlim, u, log(Contours));
colorbar('YTick', log(Contours), 'YTickLabel', Contours);
myColorMap = jet(256).^.3;
myColorMap(1,:) = [1];
colormap(myColorMap)
colorbar
caxis(log([Contours(1) Contours(length(Contours))]));
colorbar('FontSize', 12, 'YTick', log(Contours), 'YTickLabel', Contours);
geoshow(coastlat, coastlon,'Color', 'k')
Contour level, V, in contourfm(lat,lon,Z, V) does not scale your data or colour. It works in a different way than what you thought.
Let's see one example first:
u = rand(8)+0.1; u(1:2,:) = 0; u(5:6,:) = 10; u(7:8,:) = 100;
V = [0,1,40,100];
contourfm([0,1], [0,1], u, V);
mycm = jet(256).^.3; mycm(1,:) = 1;
colormap(mycm)
contourcbar('FontSize', 12, 'YTick', V, 'YTickLabel', V);
where u is
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0.1947 0.6616 0.2413 0.6511 0.4403 0.9112 1.0016 0.5654
0.8422 0.3159 0.5695 0.6478 0.9933 0.1686 0.8387 0.5362
10 10 10 10 10 10 10 10
10 10 10 10 10 10 10 10
100 100 100 100 100 100 100 100
100 100 100 100 100 100 100 100
As you can see, for V = [0,1,40,100] all values from 0 to 1 are white, values from 1 to 40 are cyan and above are red.
Therefore, you must scale your u then assign appropriate contour level. Use contourcbar instead of colorbar to check the colours first.
Apart from the problem with contour level, I suspect the u parameter contains negative values. The colour at the bottom of the colour bar is always assigned to the minimum z value. You must ensure 0 is the minimum value in u, i.e. remove the negative values.

how to apply transform to 2d points in matlab?

Using matlab, I want to apply transform contain of rotate and translate to 2d points.
for example my points are:
points.x=[1 5 7 100 52];
points.y=[42 96 71 3 17];
points.angle=[2 6 7 9 4];
the value of rotate is:30 degree
the value of x_translate is 5.
the value of y_translate is 54.
can any body help me to write matlab code for apply this transform to my points and calculate new coordinate of points after transform?
I don't know what you mean by points.angle since the angle of the points with respect to origin (in a trigonometric sense) is already defined by atand2(y,x)
Here is the code:
clear;clc
oldCoord = [1 5 7 100 52;42 96 71 3 17];
newCoord = zeros(size(oldCoord));
theta = 30 * pi/180;
T = #(theta) [cos(theta), -sin(theta); sin(theta) , cos(theta)];
trans = [5;54];
for m = 1:size(oldCoord,2)
newCoord(:,m) = T(theta) * oldCoord(:,m) + trans;
end
Result:
oldCoord =
1 5 7 100 52
42 96 71 3 17
newCoord =
-15.1340 -38.6699 -24.4378 90.1025 41.5333
90.8731 139.6384 118.9878 106.5981 94.7224

Matlab 3D polar plot

I am struggling with the concepts behind plotting a surface polar plot.
I am trying to plot the values measured by a sensor at a combination of different angles over a hemisphere.
I have an array containing the following information:
A(:,1) = azimuth values from 0 to 360º
A(:,2) = zenith values from 0 to 90º
A(:,3) = values measured at the combination of angles of A(:,1) and A(:,2)
For example, here is a snippet:
0 15 0.489502132167206
0 30 0.452957556748497
0 45 0.468147850273115
0 60 0.471115818950192
0 65 0.352532182508945
30 15 0.424997863795610
30 30 0.477814980942155
30 45 0.383999653859467
30 60 0.509625464595446
30 75 0.440940431784788
60 15 0.445028058361392
60 30 0.522388502880219
60 45 0.428092266657885
60 60 0.429315072676194
60 75 0.358172892912138
90 15 0.493704001125912
90 30 0.508762762699997
90 45 0.450598496609200
90 58 0.468523071441297
120 15 0.501619699042408
120 30 0.561755273071577
120 45 0.489660355057938
120 60 0.475478615354648
120 75 0.482572226928475
150 15 0.423716506205776
150 30 0.426735372570756
150 45 0.448548968227972
150 60 0.478055144126694
150 75 0.437389584937356
To clarify, here is a piece of code that shows the measurement points on a polar plot.
th = A(:,1)*pi/180
polar(th,A(:,2))
view([180 90])
This gives me the following plot:
I would like now to plot the same thing, but instead of the points, use the values of these points stored in A(:,3). Then, I would like to interpolate the data to get a colored surface.
After some research, I found that I need to interpolate my values over a grid, then translate to Cartesian coordinates. From there I do not know how to proceed. Could someone point me in the right direction?
I have trouble getting the concept of the interpolation, but this is what I have attempted:
x1 = linspace(0,2*pi,100)
x2 = linspace(0,90,100)
[XX,YY] = meshgrid(x1,x2)
[x,y] = pol2cart(th,A(:,2))
gr=griddata(x,y,A(:,3),XX,YY,'linear')
With this piece of code, your example data points are converted into cartesian coords, and then plotted as "lines". The two tips of a line are one data point and the origin.
az = bsxfun(#times, A(:,1), pi/180);
el = bsxfun(#times, A(:,2), pi/180);
r = A(:,3);
[x,y,z] = sph2cart(az,el,r);
cx = 0; % center of the sphere
cy = 0;
cz = 0;
X = [repmat(cx,1,length(x));x'];
Y = [repmat(cy,1,length(y));y'];
Z = [repmat(cz,1,length(z));z'];
Still thinking how to interpolate the data so you can draw a sphere. See my comments to your question.

Plot Zero Value on Contour Plot in MATLAB

I'm plotting z values, 0 to 10, on a contour plot.
When I include data 1 or greater, I obtain a contour plot. Like the following:
longitude = [80 82 95]
latitude = [30 32 35]
temp = [1 4 6; 1 2 7; 3 5 7]
contourf(longitude,latitude,temp)
Now, I want to plot the ZERO VALUE also on the contour plot. While I was expecting one color representing the zero value, instead I obtained a white square.
longitude = [80 82 95]
latitude = [30 32 35]
temp = [0 0 0; 0 0 0; 0 0 0]
contourf(longitude,latitude,temp)
Thanks a lot,
Amanda
As Issac mentioned. To plot a constant data in a contourf is not possible.
When you try to do so you will obtain this warning from Matlab:
temp =
0 0 0
0 0 0
0 0 0
Warning: Contour not rendered for constant ZData
> In contourf>parseargs at 458
In contourf at 63
In TESTrandom at 45
However, if you put some numbers as 0, the contourf works fine:
longitude = [80 82 95];
latitude = [30 32 35];
temp = [0 4 6; 1 0 7; 0 5 9];
contourf(longitude,latitude,temp);
hcb = colorbar('horiz'); % colour bar
set(get(hcb,'Xlabel'),'String','Contourf Bar.')

how to visualize a 3D matrix in MATLAB

I am trying to visualize the data contained a 3D array in MATLAB.
The array is has the dimension of 20*20*40 which all except some of the elements being zero.
I am looking for a way to plot these non-zero points in a scatter plot so that the non-zero points are connected to each other.
here is what I've done so far:
b=zeros(6,6,3);
a=[1 1 1;2 2 1;2 1 1;1 2 1;
1 1 2;1 2 2;2 1 2;2 2 2;
6 6 3;6 5 3;5 6 3;5 5 3;
6 6 2;6 5 2;5 6 2;5 5 2];
[r c]=size(a);
for i = 1:r
b(a(i,c-2),a(i,c-1),a(i,c)) = 1;
end
[m n o]=size(b);
figure (1)
[x,y,z] = meshgrid(1:m,1:n,1:o);
scatter3(x(:),y(:),z(:),90,b(:),'filled')
So, what I am after is being able to connect each of the eight points two form to cubic lattices. Any thoughts is much appreciated.
EDIT:
Many thanks to all the experts who helped a lot. Now, I am facing another issue with memory.
The b matrix for my real case would be 1000*1000*2000 and I have the "a" matrix of the size 4,4700,000*3 . All the elements of "a" matrix are integer values.
Although I have up to 48GB of memory availabe. However, in the "for loop" above the program bounces back an "out of memory" error.
Any ideas to make this more memory efficient is greatly appreciated.
Consider the following (based on a previous answer):
%# adjacency matrix
adj = false(numel(b));
%# extract first cube, and connect all its points
bb = b;
bb(:,1:3,:) = false;
idx = find(bb);
adj(idx,idx) = true;
%# extract second cube, and connect all its points
bb = b;
bb(:,4:6,:) = false;
idx = find(bb);
adj(idx,idx) = true;
%# points indices
[r c] = find(adj);
p = [r c]';
%# plot
plot3(x(p), y(p), z(p), 'LineWidth',2, 'Color',[.4 .4 1], ...
'Marker','o', 'MarkerSize',6, ...
'MarkerFaceColor','g', 'MarkerEdgeColor','g')
axis equal, axis vis3d, grid on, view(3)
xlabel x, ylabel y, zlabel z
EDIT:
It would be easier to just manually connect the points:
%# edges: connecting points indices
p = [
1 2; 2 8; 8 7; 7 1;
37 38; 38 44; 44 43; 43 37;
1 37; 2 38; 7 43; 8 44;
65 66; 66 72; 72 71; 71 65;
101 102; 102 108; 108 107; 107 101;
65 101; 66 102; 71 107; 72 108
]';
%# plot
figure
plot3(x(p), y(p), z(p), 'LineWidth',2, 'Color',[.4 .4 1], ...
'Marker','o', 'MarkerSize',6, ...
'MarkerFaceColor','g', 'MarkerEdgeColor','g')
axis equal, axis vis3d, grid on, view(3)
xlabel x, ylabel y, zlabel z
%# label points
labels = strtrim(cellstr( num2str((1:numel(b))','%d') ));
idx = find(b);
text(x(idx(:)), y(idx(:)), z(idx(:)), labels(idx(:)), ...
'Color','m', ...
'VerticalAlignment','bottom', 'HorizontalAlignment','left')
EDIT#2: (credit goes to #tmpearce)
You can semi-automate the process of builing the edges list. Just replace the manually constructed matrx p in the above code with the following:
%# compute edges: pairs of vertex indices
yIdx = {1:3 4:6}; %# hack to separate each cube points
p = cell(numel(yIdx),1);
for i=1:numel(yIdx) %# for each cube
%# find indices of vertices in this cube
bb = b;
bb(:,yIdx{i},:) = false;
idx = find(bb);
%# compute L1-distance between all pairs of vertices,
%# and find pairs which are unit length apart
[r,c] = find(triu(squareform(pdist([x(idx) y(idx) z(idx)],'cityblock'))==1));
%# store the edges
p{i} = [idx(r) idx(c)]';
end
p = cat(2,p{:}); %# merge all edges found
The idea is, for each cube, we compute the city-block distance between all vertices. The "side" edges will have a distance of 1, while the diagonals have a distance greater than or equal to 2.
This process still assume that the list of vertices belonging to each cube is provided to us, or easily extracted as we did in the code above...
I thought I'd answer the follow-up to Amro's answer, and show how you can automate the connection of edges along a cubic matrix. This answer is intended to be a supplement to Amro's, and I would recommend incorporating it into that solution so it is easier to read for future viewers.
This starts with idx from Amro's solution, which contains the linear indices of the corners of the cubes. Below I've done it with only one cube so the matrices aren't grossly large and we can see them here.
>> idx' %# transposed to save space in the printed output
ans =
1 2 7 8 37 38 43 44 '
%# now get the subindex of each point in 3D
>> [sx sy sz] = ind2sub([6 6 3],idx);
%# calculate the "edge distance" between each corner (not euclidean distance)
>> dist = abs(bsxfun(#minus,sx,sx')) +...
abs(bsxfun(#minus,sy,sy')) +...
abs(bsxfun(#minus,sz,sz'))
dist =
0 1 1 2 1 2 2 3
1 0 2 1 2 1 3 2
1 2 0 1 2 3 1 2
2 1 1 0 3 2 2 1
1 2 2 3 0 1 1 2
2 1 3 2 1 0 2 1
2 3 1 2 1 2 0 1
3 2 2 1 2 1 1 0 '
%# find row and column index of points one edge apart
>> [r c]=find(triu(dist==1)); %# triu means don't duplicate edges
%# create 'p' matrix like in Amro's answer
>> p = [idx(r) idx(c)]'
p =
1 1 2 7 1 2 37 7 37 8 38 43
2 7 8 8 37 38 38 43 43 44 44 44
To visualize, you can plot exactly like in Amro's answer.
Is this what you need?
b=logical(b);
scatter3(x(b(:)),y(b(:)),z(b(:)),90,'filled')