I need to create a polygon or buffer along an irregular shaped coastline in a 2D array of gridded sea temperature data.

13 views (last 30 days)
I have a 578x235 grid of ocean temperature data in which the land pixels are NaN. I would like to create a 1 pixel buffer around the coastline so that all pixels adjacent to an existing NaN also become NaN. I'm unsure on the best way to do this. I thought I could create a logical array of NaNs and not NaNs but am not sure on the best way to extract a polygon and create the buffer after that step.
  5 Comments
Angus
Angus on 23 Jul 2024
Edited: Walter Roberson on 25 Jul 2024
Hi, heres my code
nan_mask = isnan(meanSST);
buffered_data=meanSST;
for i=2:size(meanSST,1)-1
for j=2:size(meanSST,2)-1
if meanSST(i,j) == 0
if any(any(nan_mask(i-1:i+1, j-1:j+1)))
buffered_data(i,j)=NaN;
end
end
end
end
subplot(2,1,1);
imagesc(lon,lat,buffered_data.');
colorbar;
subplot(2,1,2);
imagesc(lon,lat,meanSST.');
colorbar;
Umar
Umar on 23 Jul 2024
Edited: Walter Roberson on 25 Jul 2024
Hi Angus,
To achieve the desired 1-pixel buffer around the coastline, I had to update your code. Your code currently iterates through each pixel and checks if the pixel value is 0. If the pixel is 0 and any of its neighboring pixels are NaN, it sets the pixel value to NaN. However, I needed to extend this logic to create a buffer around NaN values. Here is the updated code that creates a 1-pixel buffer around the coastline:
nan_mask = isnan(meanSST);
buffered_data = meanSST;
for i = 2:size(meanSST, 1) - 1
for j = 2:size(meanSST, 2) - 1
if meanSST(i, j) == 0
if any(any(nan_mask(i-1:i+1, j-1:j+1)))
buffered_data(i, j) = NaN;
% Set neighboring pixels to NaN
buffered_data(i-1, j) = NaN;
buffered_data(i+1, j) = NaN;
buffered_data(i, j-1) = NaN;
buffered_data(i, j+1) = NaN;
end
end
end
end
So, In this updated code, it still checks if the current pixel is 0 and any of its neighbors are NaN. If the condition is met, we set the current pixel to NaN and also set its neighboring pixels (top, bottom, left, right) to NaN. Also, it makes sure This ensures that a 1-pixel buffer around the coastline is created, turning all adjacent pixels to NaN.
By incorporating these modifications, the code should now effectively create a 1-pixel buffer around the coastline by setting adjacent pixels to NaN based on the presence of NaN values in the neighborhood.
Now, to address Extracting a Polygon and Creating the Buffer
To extract a polygon representing the coastline and create a buffer around it, you can use the bwboundaries function to find the boundaries of the NaN regions in the buffered_data. For more information on this function, please refer to
Here's an example of how you can achieve this:
% Find boundaries of NaN regions
B = bwboundaries(isnan(buffered_data));
% Plot the original data with coastline buffer
subplot(2, 1, 1);
imagesc(lon, lat, buffered_data.');
hold on;
for k = 1:length(B)
boundary = B{k};
plot(boundary(:, 2), boundary(:, 1), 'r', 'LineWidth', 2);
end
colorbar;
title('Coastline with 1-Pixel Buffer');
% Plot the original data without buffer
subplot(2, 1, 2);
imagesc(lon, lat, meanSST.');
colorbar;
title('Original Coastline Data');
By incorporating the above modifications, you can update the code to create a 1-pixel buffer around the coastline and visualize the coastline with the buffer applied. Remember to adjust the code according to your specific data and requirements for accurate results.

Sign in to comment.

Accepted Answer

Angus
Angus on 25 Jul 2024
Hi thanks for your help.
I made the following changes to Umars code to get it to work.
nan_mask = isnan(meanSST);
buffered_data=meanSST;
for i = 2:size(meanSST, 1) - 1
for j = 2:size(meanSST, 2) - 1
if nan_mask(i, j) == 0
if any(any(nan_mask(i-1:i+1, j-1:j+1)))
buffered_data(i, j) = NaN;
% Set neighboring pixels to NaN
buffered_data(i-1, j) = NaN;
buffered_data(i+1, j) = NaN;
buffered_data(i, j-1) = NaN;
buffered_data(i, j+1) = NaN;
end
end
end
end

More Answers (1)

John D'Errico
John D'Errico on 23 Jul 2024
Edited: John D'Errico on 23 Jul 2024
Easy enough. Almost trivial, in fact. Of course, making a buffer zone of width exactly one pixel is alays going to be slightly problematic. But This will be quite good.
Firtst, I'll create a region, filled with NaNs. This one will be pretty simple.
A = ones(1000,1000);
[I,J] = meshgrid(1:1000);
A(sqrt((I - 500).^2 + (J - 500).^2) < 100) = NaN;
H = pcolor(isnan(A));
H.LineStyle = 'none';
axis equal
Ok, so a yellow, circular country. We can find the pixels that touch a NaN easily enough, using conv2, and xor.
bordermask = xor(isnan(conv2(A,ones(3),'same')),isnan(A));
spy(bordermask)
The definition of touching a NaN I used there is if a pixel is horizontally, vertically, or diagonally touching a NaN, then it is flagged in the mask.
If you wanted only horizontal or vertical connections, then the convolution kernel would be [0 1 0;1 1 1;0 1 0] instead of all ones.
  5 Comments
John D'Errico
John D'Errico on 25 Jul 2024
Edited: John D'Errico on 25 Jul 2024
@Umar I don't see the actual data posted, so I won't try to do it with that data, but the problem is still pretty easy to solve in my eyes. I'll make something up. I suppose if I knew how to use the mapping toolbox, that would give me some data.
fun = @(x,y) x.^2 + + x.*y + 2*y.^2 + sin(x+y).*cos(x-2*y)*8;
H = fcontour(fun,[-10 10 -10 10]);
H.LevelList = 50;
[x,y] = meshgrid(linspace(-10,10,100));
A = fun(x,y);
A(A < 50) = NaN;
As such, I've created an irregular hole, filled with NaNs. Do you agree?
surf(x,y,A,LineStyle = 'none')
This should be qualitatively the same as what @Angus had.
Now, apply my scheme using conv2.
kernel = [0 1 0;1 1 1;0 1 0];
bordermask = xor(isnan(conv2(A,kernel,'same')),isnan(A));
H = fcontour(fun,[-10 10 -10 10]);
H.LevelList = 50;
H.LineColor = 'b';
hold on
plot(x(bordermask),y(bordermask),'.r')
As you can see, it created a 1 pixel border, falling entirely outside the region of NaNs. You can tell since I drew the contour on top, and as you should see, the blue contour falls entirely inside the red pixelated perimeter.
There are some points where that border is a little bumpy, but this is partly due to the coarseness of the mesh I used. As well, some of that is due to the definition of touching a NaN. But as you should understand, that is not my problem. The pixels that touch a NaN will not form a perfectly smooth set, and no matter how you generate them, they will have the same characteristics. Pixels are pixels. They will never be "smooth".
But I do want you to understand that NO loops were needed or used. Not much more than one line of code, and it will be blazingly efficient.
Umar
Umar on 26 Jul 2024
Edited: Umar on 26 Jul 2024
Thanks for your contribution. After reading your posted comments, I have to say that your code snippet does demonstrate the handling of NaNs in MATLAB contour plots. Your approach involving creating a hole filled with NaNs and then applying a scheme using conv2 to generate a border around the NaN region efficiently creates a 1-pixel border outside the NaN area and is evidenced by the contour plot. However, I do understand that the slight bumpiness in the border is attributed to mesh coarseness and the nature of NaN definitions, rather than yours implementation. Notably, the solution does not require loops and is computationally efficient, achieving the desired outcome effectively. Again, thanks for your help.

Sign in to comment.

Products


Release

R2024a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!