Interpolation on the surface of a sphere

13 次查看(过去 30 天)
context:
I wrote a function to "uniformly" distribute points on the surface of a sphere/globe... I put a point at each pole, and then divided the rest of the east north space into latitude bands where the width/height of the latitude band is proportional to the number of points in it and the number of latitude bands and points in each latitude band is chosen to make make the east-west spacing roughly equal to the north-south spacing, and then I rotate each band independently to maximize the minimum longitude difference of any two points in consecutive latitude bands, and then I do a single spherical CVT (Centroidal Voronoi Tessellation) step to slightly improve the spacing of points. To do the CVT I use convhulln to get the delaunay tessellation of the surface of the sphere and compute the 1 Voronoi cell vertex per triangle, fractional area of each triangle that belongs to each of its nodes along with the centroid of the fractional areas, which I can add up (weighted by the fractional areas) to get the updated point positions. (the first CVT step improves the spacing then it decreases) anyway I'm getting a min/max point area ratio of about .91 which isn't a minimum energy/maxi-min design but it is quite close and it takes a second or less to generate. Using the point areas as weights in an integration also makes it a quite accurate at integrating a function over a sphere (getting between 3 and 5 sig figs of accuracy for O(1000) points, when the standard deviations equals the mean for the test function with an analytical solution).
That's context... put another way I DON'T have a rectangular grid of points on an east-west north-south map.
I would like to unwrap the function from the sphere onto an east north map, and I figured out the logic for a piece wise (triangle wise) constant unwrapping/flattening of sphere (which was the most complicated part of all of this, much more complicated than the point generation) and this meant that edges of the triangles would come out as curves on the map.
But really it would look a lot better (and be simpler) if I took an TP grid of lat long points on the map, translated them to xyz (on the surface of the sphere) points, identified the triangles on the sphere that contained those xyz points, did barycentric interpolation there, and then used those values with the "original" Tensor Product lat long coordinates to display the map.
The part I'm missing is a tsearchn equivalent for identifying which triangles on the sphere contain the xyz points; I am not a qhull expert, so what I would write would be no-where nearly as fast as the tsearchn function is. I was hoping that someone out there had already solved this.
Back when tsearchn was slow (took O(3) minutes for a few thousand points in O(6) dimensions), I wrote a replacement that took about a minute by using a space-filling curve as a short cut to restrict the number of triangles that I needed to check, and then using a simplex/neighbor based search, but shortly thereafter MATHWORKS released a update based on qhull that managed it in a few seconds, so like I said a qhull based (or similar) approach is going to be about 2 orders of magnitude faster than what I would write using my SFC+simplex approach.

回答(2 个)

John D'Errico
John D'Errico 2013-12-24
It depends on your goals in the end. It sounds like you are trying to do a numerical integration over a sphere from one comment, and this is just a means to an end.
For example, I have written tools that can work with a simplicial complex in any number of dimensions. One of the pieces can generate a triangulated tiling of a 3-sphere.
>> sc = tilesphere(1,30,12)
sc =
domain: [292x3 double]
tessellation: [580x3 double]
range: [292x0 double]
>> plotsc(sc)
A nice feature of these tools is they are also capable of a gaussian integration of some general function over that domain, where you can specify the order of the gaussian rule. This would easily yield far more than a few significant digits of accuracy. I've also written adaptive integration rules like quad, but in several dimensions, but I've never really been happy with them. (One day I'll finish them.)

Keith Dalbey
Keith Dalbey 2013-12-26
编辑:Keith Dalbey 2013-12-26
I have 2 goals with the sample design.
1) to efficiently integrate a function over the surface of the earth... I can't say what the real function is but something comparable is temperature that is the output of a simulator. So the output is most definitely not a polynomial but it is likely to be smooth over short distances. From your plot you seem to be using a tensor product grid that got wrapped to the sphere (this is what is currently being done for the project and they came to me as a UQ guy to see if it could be done better/with fewer simulation evaluations), that approach basically only benefits from 2/pi of the points because of down weighting (there are far more points at the poles than are needed).
2) to display this function both on the sphere (easy, since MATLAB does this) and 2 to display a MAP, displaying on a map is easy if you use a tensor product grid of points, but difficult if your points are uniformly distributed on a sphere.
I figure it might help to be able the generate the sample designs I'm working with so
Here's code for the spherical cvt step
if true
% code
function [x,y,z,longlat,ptArea,Kt]=cvtSphere2(x,y,z,Niter)
Niter=Niter+1;
Npts=numel(x);
i4=[1:3 1]';
j3=[2 3 1]';
i3=[3 1 2]';
ptArea=zeros(Npts,1);
for iter=1:Niter
Kt=convhulln([x y z])';
NK=size(Kt,2);
%calculate the 3 edge lengths (in x y and z coordinates) for each
%triangle
dx=diff(x(Kt(i4,:)));
dy=diff(y(Kt(i4,:)));
dz=diff(z(Kt(i4,:)));
%square of the adjacent edge of the fractional triangle
dadj2=0.25*(dx.^2+dy.^2+dz.^2);
%calculate the voronoi vertices
xv=dz(1,:).*dy(3,:)-dy(1,:).*dz(3,:);
yv=dx(1,:).*dz(3,:)-dz(1,:).*dx(3,:);
zv=dy(1,:).*dx(3,:)-dx(1,:).*dy(3,:);
normconst=-(xv.^2+yv.^2+zv.^2).^-0.5;
xv=xv.*normconst;
yv=yv.*normconst;
zv=zv.*normconst;
%square of the hypotenuse of the fractional triangle
dh2=(x(Kt)-repmat(xv,3,1)).^2+(y(Kt)-repmat(yv,3,1)).^2+(z(Kt)-repmat(zv,3,1)).^2;
%the opposite and adjacent edges of the fractional triangle are orthogonal
Areaa=0.5*sqrt((dh2-dadj2).*dadj2);
Areab=0.5*sqrt((dh2-dadj2(i3,:)).*dadj2(i3,:));
Area=Areaa+Areab; %add the areas of the 2 fractional triangles (a & b)
%that share a node and the voronioi vertex and you get the portion of
%the whole triangle's area that belongs to the node
%need the mid points of the adjacent edges of the whole triangle to
%determine the centroids of the fractional triangles
xm=0.5*(x(Kt)+x(Kt(j3,:)));
ym=0.5*(y(Kt)+y(Kt(j3,:)));
zm=0.5*(z(Kt)+z(Kt(j3,:)));
%skip intermediate calculation of the centroids of fractional
%triangles' a & b and go directly to the centroids of portion of the
%whole triangle that belongs to its nearest nodes
xc=((x(Kt)+repmat(xv,3,1)).*Area+xm.*Areaa+xm(i3,:).*Areab)./(3*Area);
yc=((y(Kt)+repmat(yv,3,1)).*Area+ym.*Areaa+ym(i3,:).*Areab)./(3*Area);
zc=((z(Kt)+repmat(zv,3,1)).*Area+zm.*Areaa+zm(i3,:).*Areab)./(3*Area);
minsumAi=inf;
maxsumAi=0;
if(iter<Niter)
for ipt=1:Npts
Ai=(Kt==ipt).*Area;
sumAi=sum(Ai(:));
maxsumAi=max(maxsumAi,sumAi);
minsumAi=min(minsumAi,sumAi);
x(ipt)=sum(sum(xc.*Ai))/sumAi;
y(ipt)=sum(sum(yc.*Ai))/sumAi;
z(ipt)=sum(sum(zc.*Ai))/sumAi;
%shift it back to the surface of the sphere
invr=(x(ipt)^2+y(ipt)^2+z(ipt)^2).^-0.5;
x(ipt)=x(ipt)*invr;
y(ipt)=y(ipt)*invr;
z(ipt)=z(ipt)*invr;
end
else
%the last time it just calculates the area of the points for
%weighting
for ipt=1:Npts
Ai=(Kt==ipt).*Area;
sumAi=sum(Ai(:));
maxsumAi=max(maxsumAi,sumAi);
minsumAi=min(minsumAi,sumAi);
ptArea(ipt)=sumAi;
end
end
%fprintf('iter=%d minsumAi/maxsumAi=%g\n',iter-1,minsumAi/maxsumAi)
end
longlat(:,2)=atan2d(z,(x.^2+y.^2).^0.5);
longlat(:,1)=atan2d(y,x);
end
end
and here's code for the uniform distribution of points on the sphere (which calls the CVT code for a single step, sorry this one's not commented better but the variable names are fairly descriptive), 1098 points do pretty good for integrating and displaying maps
if true
% code
function [x,y,z,longlat,ptArea,K]=equalAreaPointsOnSphere4(N)
longlat=nan;
thetapole=asin(2/N-1);
lat_to_split=2*-thetapole;
long_to_split=2*pi;
map_area_to_split=lat_to_split*long_to_split;
sphere_area_to_split=4*pi*(1-2/N);
Npseudo=(N-2)*map_area_to_split/sphere_area_to_split;
N_lat_bands=round(sqrt(Npseudo/map_area_to_split)*lat_to_split);
lat_band_ends=((0:N_lat_bands)/N_lat_bands-0.5)*lat_to_split/pi*180;
Nom_points_per_lat_band=(diff(sind(lat_band_ends))/(2*sin(abs(thetapole))))*(N-2);
N_points_per_lat_band=floor(Nom_points_per_lat_band);
N_points_to_alloc=(N-2)-sum(N_points_per_lat_band);
residual_points=Nom_points_per_lat_band-N_points_per_lat_band;
[~,ilat_band_to_increase]=sort(residual_points,'descend');
ilat_band_to_increase=ilat_band_to_increase(1:N_points_to_alloc);
N_points_per_lat_band(ilat_band_to_increase)=...
N_points_per_lat_band(ilat_band_to_increase)+1;
band_area=(2*sin(abs(thetapole)))*(N_points_per_lat_band/(N-2));
for ilat_band=1:N_lat_bands-1
lat_band_ends(ilat_band+1)=asind(band_area(ilat_band)+sind(lat_band_ends(ilat_band)));
end
diff_lat_band_ends=diff(lat_band_ends);
lat_band_centers=0.5*(lat_band_ends(1:end-1)+lat_band_ends(2:end));
lat_bands=repmat(struct('lat',[],'long',[]),N_lat_bands,1);
ilat_band=1;
lat_bands(ilat_band).lat=lat_band_centers(ilat_band);
lat_bands(ilat_band).long=(0:N_points_per_lat_band(ilat_band)-1)/N_points_per_lat_band(ilat_band)*360;
longlat=[lat_bands(ilat_band).long(:) repmat(lat_band_centers(ilat_band),N_points_per_lat_band(ilat_band),1)];
for ilat_band=2:N_lat_bands
N_points_this_lat_band=N_points_per_lat_band(ilat_band);
prev_long=lat_bands(ilat_band-1).long;
base_long=(0:N_points_this_lat_band-1)/N_points_this_lat_band*360+lat_bands(ilat_band-1).long(1);
N_cand_offset=33; %can replace this with any odd integer
offset_candidates=(360/max(N_points_per_lat_band(ilat_band-(0:1))))*(((1:N_cand_offset)-0.5)/N_cand_offset);
max_min_distance=0;
for ioffset=1:N_cand_offset
cand_long=sort(mod(base_long+offset_candidates(ioffset),360));
min_dist=inf;
for ilong=1:N_points_this_lat_band
ilongprev=find(prev_long>=cand_long(ilong),1);
if(isempty(ilongprev))
min_dist=min(min_dist,min(prev_long(1)+360-cand_long(ilong),cand_long(ilong)-prev_long(end)));
elseif(ilongprev==1)
min_dist=min(min_dist,min(prev_long(1)-cand_long(ilong),cand_long(ilong)+360-prev_long(end)));
else
min_dist=min(min_dist,min(prev_long(ilongprev)-cand_long(ilong),cand_long(ilong)-prev_long(ilongprev-1)));
end
end
if(min_dist>max_min_distance)
max_min_distance=min_dist;
lat_bands(ilat_band).long=cand_long;
end
end
longlat=[longlat; lat_bands(ilat_band).long(:) ...
repmat(lat_band_centers(ilat_band),....
N_points_per_lat_band(ilat_band),1)];
end
longlat=sortrows([longlat; 0 90; 0 -90],[2 1]);
z=sind(longlat(:,2));
r=cosd(longlat(:,2));
x=r.*cosd(longlat(:,1));
y=r.*sind(longlat(:,1));
[x,y,z,longlat,ptArea,K]=cvtSphere2(x,y,z,1);
K=K';
end
end
I've written a test function that I call planarCross which I use in a lot of different contexts, it's a great stress test for probability of failure/reliability algorithms,
if true
% code
function [y,varargout]=planarCross(x)
%[y,d1y,d2y]=planarCross(x)
%y=prod(0.5*(1+cos((2*pi)*x)),2).^(1/Ndim); %human readible form of eqn
%d1y is gradient of y (a size(x) array)
%d2y is main effects 2nd ders of y (diagonal of Hessian, a size(x) array)
%planarCross uses lazy computing (only computes the requested outputs, a
%minimum of 1 output is (y), no more than 3 outputs (y,d1y,&d2y)
%are allowed)
[Nx,Ndim]=size(x);
if(nargout==1) %if only want y
y=exp(mean(log(0.5*(1+cos((2*pi)*x))),2)); %should have less round
%off error in high dimensions when implemented this way
elseif(nargout==2) %if only want y and d1y (gradient)
x2p=2*pi*x;
u=0.5*(1+cos(x2p));
d1u=-pi*sin(x2p); %derivative of ui with respect to xi
y=exp(mean(log(u),2));
d1y=zeros(Nx,Ndim);
for idim=1:Ndim
d1y(:,idim)=(1/Ndim)*y./u(:,idim).*d1u(:,idim);
end
varargout{1}=d1y;
elseif(nargout==3) %want y, d1y, and d2y (main effects second ders)
x2p=2*pi*x;
cx2p=cos(x2p);
u=0.5*(1+cx2p);
d1u=-pi*sin(x2p); %derivative of ui with respect to xi
d2u=(-2*pi^2)*cx2p; %2nd derivative of ui with respect to xi
clear x2p cx2p;
y=exp(mean(log(u),2));
d1y=zeros(Nx,Ndim);
d2y=zeros(Nx,Ndim);
onetonx=(1:Nx)';
for idim=1:Ndim
d1y_dui=(1/Ndim)*y./u(:,idim); %1st derivative of y W.R.T. ui
d1y(:,idim)=d1y_dui.*d1u(:,idim); %nan at x=0.5
d2y(:,idim)=((1-Ndim)/Ndim)*d1y(:,idim)./u(:,idim).*d1u(:,idim)+...
d1y_dui.*d2u(:,idim); %will return nan at 0.5 but should be
%zero if Ndim=2 or -inf otherwise
j=sort((x(:,idim)==0.5).*onetonx,'descend');
if(j(1)>0)
%at least one occurence of x(:,idim)=0.5
if(j(Nx)==0)
%not all x(:,idim)
j=j(1:find(~j,1)-1);
end
if(Ndim==2)
d2y(j,idim)=0; %second derivative is continuous at
%x=0.5 in this case even though the first derivative is
%not
else
d2y(j,idim)=-inf; %1st der passes through +/- infinity
%(like -tan(pi/2) does) so second der is -inf
end
end
end
varargout={d1y, d2y};
else
error('does not support %d outputs');
end
end
end
for the spherical integration and mapping case I'm using it like this
if true
% code
theta=params.randRot;
XYZtempRot=[x y z]*[cosd(theta(1)) 0 sind(theta(1)); 0 1 0; -sind(theta(1)) 0 cosd(theta(1))]*[1 0 0; 0 cosd(theta(2)) sind(theta(2)); 0 -sind(theta(2)) cosd(theta(2))];
lattemp=atan2d(XYZtempRot(:,3),(XYZtempRot(:,1).^2+XYZtempRot(:,2).^2).^0.5);
longtemp=atan2d(XYZtempRot(:,2),XYZtempRot(:,1));
outPC=planarCross([longtemp*params.PCwraps lattemp]/180);
end
params.PCwraps varies but 3.5 is a good number for the 1098 points I quoted above, params.PCwraps=8.5 is a good test for sample designs with about 1851 points
The wrapping is to make the problem tougher as is the random rotation (otherwise there is rotational symmetry about the polar axis)

类别

Help CenterFile Exchange 中查找有关 Spatial Search 的更多信息

产品

Community Treasure Hunt

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

Start Hunting!

Translated by