Location of highest density on a sphere - algorithm
I have a lot of points on the surface of the sphere.
How can I calculate the area/spot of the sphere that has the largest point density?
I need this to be done very fast. If this was a square for example I guess I could create a grid and then let the points vote which part of the grid is the best.
I have tried with transforming the points to spherical coordinates and then do a grid, both this did not work well since points around north pole are close on the sphere but distant after the transform.
Thanks
There is in fact no real reason to partition the sphere into a regular non-overlapping mesh, try this:
partition your sphere into semi-overlapping circles
see here for generating uniformly distributed points (your circle centers)
Dispersing n points uniformly on a sphere
you can identify the points in each circle very fast by a simple dot product..it really doesn't matter if some points are double counted, the circle with the most points still represents the highest density
mathematica implementation
this takes 12 seconds to analyze 5000 points. (and took about 10 minutes to write )
testcircles = { RandomReal[ {0, 1}, {3}] // Normalize};
Do[While[ (test = RandomReal[ {-1, 1}, {3}] // Normalize ;
Select[testcircles , #.test > .9 & , 1] ) == {} ];
AppendTo[testcircles, test];, {2000}];
vmax = testcircles[[First#
Ordering[-Table[
Count[ (testcircles[[i]].#) & /# points , x_ /; x > .98 ] ,
{i, Length[testcircles]}], 1]]];
To add some other, alternative schemes to the mix: it's possible to define a number of (almost) regular grids on sphere-like geometries by refining an inscribed polyhedron.
The first option is called an icosahedral grid, which is a triangulation of the spherical surface. By joining the centres of the triangles about each vertex, you can also create a dual hexagonal grid based on the underlying triangulation:
Another option, if you dislike triangles (and/or hexagons) is the cubed-sphere grid, formed by subdividing the faces of an inscribed cube and projecting the result onto the spherical surface:
In either case, the important point is that the resulting grids are almost regular -- so to evaluate the region of highest density on the sphere you can simply perform a histogram-style analysis, counting the number of samples per grid cell.
As a number of commenters have pointed out, to account for the slight irregularity in the grid it's possible to normalise the histogram counts by dividing through by the area of each grid cell. The resulting density is then given as a "per unit area" measure. To calculate the area of each grid cell there are two options: (i) you could calculate the "flat" area of each cell, by assuming that the edges are straight lines -- such an approximation is probably pretty good when the grid is sufficiently dense, or (ii) you can calculate the "true" surface areas by evaluating the necessary surface integrals.
If you are interested in performing the requisite "point-in-cell" queries efficiently, one approach is to construct the grid as a quadtree -- starting with a coarse inscribed polyhedron and refining it's faces into a tree of sub-faces. To locate the enclosing cell you can simply traverse the tree from the root, which is typically an O(log(n)) operation.
You can get some additional information regarding these grid types here.
Treating points on a sphere as 3D points might not be so bad.
Try either:
Select k, do approximate k-NN search in 3D for each point in the data or selected point of interest, then weight the result by their distance to the query point. Complexity may vary for different approximate k-NN algorithms.
Build a space-partitioning data structure like k-d Tree, then do approximate (or exact) range counting query with a ball range centered at each point in the data or selected point of interest. Complexity is O(log(n) + epsilon^(-3)) or O(epsilon^(-3)*log(n)) for each approximate range query with state of the art algorithms, where epsilon is the range error threshold w.r.t. the size of the querying ball. For exact range query, the complexity is O(n^(2/3)) for each query.
Partition the sphere into equal-area regions (bounded by parallels and meridians) as described in my answer there and count the points in each region.
The aspect ratio of the regions will not be uniform (the equatorial regions will be more "squarish" when N~M, while the polar regions will be more elongated).
This is not a problem because the diameters of the regions go to 0 as N and M increase.
The computational simplicity of this method trumps the better uniformity of domains in the other excellent answers which contain beautiful pictures.
One simple modification would be to add two "polar cap" regions to the N*M regions described in the linked answer to improve the numeric stability (when the point is very close to a pole, its longitude is not well defined). This way the aspect ratio of the regions is bounded.
You can use the Peters projection, which preserves the areas.
This will allow you to efficiently count the points in a grid, but also in a sliding window (box Parzen window) by using the integral image trick.
If I understand correctly, you are trying to find the densepoint on sphere.
if points are denser at some point
Consider Cartesian coordinates and find the mean X,Y,Z of points
Find closest point to mean X,Y,Z that is on sphere (you may consider using spherical coordinates, just extend the radius to original radius).
Constraints
If distance between mean X,Y,Z and the center is less than r/2, then this algorithm may not work as desired.
I am not master of mathematics but may be it can solve by analytical way as:
1.Short the coordinate
2.R=(Σ(n=0. n=max)(Σ(m=0. M=n)(1/A^diff_in_consecative))*angle)/Σangle
A=may any constant
This is really just an inverse of this answer of mine
just invert the equations of equidistant sphere surface vertexes to surface cell index. Don't even try to visualize the cell different then circle or you go mad. But if someone actually do it then please post the result here (and let me now)
Now just create 2D cell map and do the density computation in O(N) (like histograms are done) similar to what Darren Engwirda propose in his answer
This is how the code looks like in C++
//---------------------------------------------------------------------------
const int na=16; // sphere slices
int nb[na]; // cells per slice
const int na2=na<<1;
int map[na][na2]; // surface cells
const double da=M_PI/double(na-1); // latitude angle step
double db[na]; // longitude angle step per slice
// sherical -> orthonormal
void abr2xyz(double &x,double &y,double &z,double a,double b,double R)
{
double r;
r=R*cos(a);
z=R*sin(a);
y=r*sin(b);
x=r*cos(b);
}
// sherical -> surface cell
void ab2ij(int &i,int &j,double a,double b)
{
i=double(((a+(0.5*M_PI))/da)+0.5);
if (i>=na) i=na-1;
if (i< 0) i=0;
j=double(( b /db[i])+0.5);
while (j< 0) j+=nb[i];
while (j>=nb[i]) j-=nb[i];
}
// sherical <- surface cell
void ij2ab(double &a,double &b,int i,int j)
{
if (i>=na) i=na-1;
if (i< 0) i=0;
a=-(0.5*M_PI)+(double(i)*da);
b= double(j)*db[i];
}
// init variables and clear map
void ij_init()
{
int i,j;
double a;
for (a=-0.5*M_PI,i=0;i<na;i++,a+=da)
{
nb[i]=ceil(2.0*M_PI*cos(a)/da); // compute actual circle cell count
if (nb[i]<=0) nb[i]=1;
db[i]=2.0*M_PI/double(nb[i]); // longitude angle step
if ((i==0)||(i==na-1)) { nb[i]=1; db[i]=1.0; }
for (j=0;j<na2;j++) map[i][j]=0; // clear cell map
}
}
//---------------------------------------------------------------------------
// this just draws circle from point x0,y0,z0 with normal nx,ny,nz and radius r
// need some vector stuff of mine so i did not copy the body here (it is not important)
void glCircle3D(double x0,double y0,double z0,double nx,double ny,double nz,double r,bool _fill);
//---------------------------------------------------------------------------
void analyse()
{
// n is number of points and r is just visual radius of sphere for rendering
int i,j,ii,jj,n=1000;
double x,y,z,a,b,c,cm=1.0/10.0,r=1.0;
// init
ij_init(); // init variables and map[][]
RandSeed=10; // just to have the same random points generated every frame (do not need to store them)
// generate draw and process some random surface points
for (i=0;i<n;i++)
{
a=M_PI*(Random()-0.5);
b=M_PI* Random()*2.0 ;
ab2ij(ii,jj,a,b); // cell corrds
abr2xyz(x,y,z,a,b,r); // 3D orthonormal coords
map[ii][jj]++; // update cell density
// this just draw the point (x,y,z) as line in OpenGL so you can ignore this
double w=1.1; // w-1.0 is rendered line size factor
glBegin(GL_LINES);
glColor3f(1.0,1.0,1.0); glVertex3d(x,y,z);
glColor3f(0.0,0.0,0.0); glVertex3d(w*x,w*y,w*z);
glEnd();
}
// draw cell grid (color is function of density)
for (i=0;i<na;i++)
for (j=0;j<nb[i];j++)
{
ij2ab(a,b,i,j); abr2xyz(x,y,z,a,b,r);
c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2); glCircle3D(x,y,z,x,y,z,0.45*da,0); // outline
glColor3f(0.1,0.1,c ); glCircle3D(x,y,z,x,y,z,0.45*da,1); // filled by bluish color the more dense the cell the more bright it is
}
}
//---------------------------------------------------------------------------
The result looks like this:
so now just see what is in the map[][] array you can find the global/local min/max of density or whatever you need... Just do not forget that the size is map[na][nb[i]] where i is the first index in array. The grid size is controlled by na constant and cm is just density to color scale ...
[edit1] got the Quad grid which is far more accurate representation of used mapping
this is with na=16 the worst rounding errors are on poles. If you want to be precise then you can weight density by cell surface size. For all non pole cells it is simple quad. For poles its triangle fan (regular polygon)
This is the grid draw code:
// draw cell quad grid (color is function of density)
int i,j,ii,jj;
double x,y,z,a,b,c,cm=1.0/10.0,mm=0.49,r=1.0;
double dx=mm*da,dy;
for (i=1;i<na-1;i++) // ignore poles
for (j=0;j<nb[i];j++)
{
dy=mm*db[i];
ij2ab(a,b,i,j);
c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2);
glBegin(GL_LINE_LOOP);
abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b-dy,r); glVertex3d(x,y,z);
glEnd();
glColor3f(0.1,0.1,c );
glBegin(GL_QUADS);
abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b+dy,r); glVertex3d(x,y,z);
abr2xyz(x,y,z,a+dx,b-dy,r); glVertex3d(x,y,z);
glEnd();
}
i=0; j=0; ii=i+1; dy=mm*db[ii];
ij2ab(a,b,i,j); c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2);
glBegin(GL_LINE_LOOP);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z); }
glEnd();
glColor3f(0.1,0.1,c );
glBegin(GL_TRIANGLE_FAN); abr2xyz(x,y,z,a ,b ,r); glVertex3d(x,y,z);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b-dy,r); glVertex3d(x,y,z); }
glEnd();
i=na-1; j=0; ii=i-1; dy=mm*db[ii];
ij2ab(a,b,i,j); c=map[i][j]; c=0.1+(c*cm); if (c>1.0) c=1.0;
glColor3f(0.2,0.2,0.2);
glBegin(GL_LINE_LOOP);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z); }
glEnd();
glColor3f(0.1,0.1,c );
glBegin(GL_TRIANGLE_FAN); abr2xyz(x,y,z,a ,b ,r); glVertex3d(x,y,z);
for (j=0;j<nb[ii];j++) { ij2ab(a,b,ii,j); abr2xyz(x,y,z,a-dx,b+dy,r); glVertex3d(x,y,z); }
glEnd();
the mm is the grid cell size mm=0.5 is full cell size , less creates a space between cells
If you want a radial region of the greatest density, this is the robust disk covering problem with k = 1 and dist(a, b) = great circle distance (a, b) (see https://en.wikipedia.org/wiki/Great-circle_distance)
https://www4.comp.polyu.edu.hk/~csbxiao/paper/2003%20and%20before/PDCS2003.pdf
Consider using a geographic method to solve this. GIS tools, geography data types in SQL, etc. all handle curvature of a spheroid. You might have to find a coordinate system that uses a pure sphere instead of an earthlike spheroid if you are not actually modelling something on Earth.
For speed, if you have large numbers of points and want the densest location of them, a raster heatmap type solution might work well. You could create low resolution rasters, then zoom to areas of high density and create higher resolution only cells that you care about.
Related
Getting the centers of all triangles in a bound isometric grid
I have a geometry problem: Consider the following isometric grid where the center is (0, 0) and a side of a triangle are of length one unit: How can I get the coordinate of all the individual triangles, including the ones that are not complete? I tried simply dividing the width and height by different factors, but I cannot seem to find a formula that always encompasses partial triangles.
One way to do it: The coordinates of the center of a triangle is simply the average of it's three vertices. Since the grid is vertically aligned, you can find the missing vertices of partial triangles in the vertical directions (up and down) by simply adding/subtracting s the length of a side of the triangles to the Y-axis. Because these are equilateral triangles, you can find the missing vertices of the partial triangles with 2 visible vertices in the horizontal directions (left and right) by adding/subtracting (√3/2)s to the X-axis of one of the existing vertices and setting the Y-axis value to the average of the Y values of the two existing vertices. You can find the missing vertices of partial triangles that have only one visible vertex in the horizontal directions by getting them from thier adjacent horizontal triangles with two visible vertices. Partial corner triangles (only one vertex) can be extended as follows: first get the vertically-missing vertex by applying rule (2) above. Second, now that you know two vertices, you can get the horizontally-missing vertex by applying rule (3) above. Note that this is not the only way to do it, no even necessarily the easiest way.
First you need to chose topology of your grid. I see it like this: Yellow indexes are intersection points on grid and Aqua indexes are individual triangles of the grid. So define basis vectors u,v,w describing 3 possible directions of the grid and then just transform grid point position yellow (i,j) into (x,y). That can be used to form triangles using aqua triangle index parity (even/odd) of each index (4 possible cases) to compute its 3 yellow point indexes ... Here small C++/OpenGL 2D example rendering 2n+1 x 2n+1 triangles grid: //--------------------------------------------------------------------------- void get_hexpoint(double *p,int i,int j) // i,j yellow { static const double deg=M_PI/180.0; // deg -> rad static const double u[2]={+cos(30.0*deg),-sin(30.0*deg)}; static const double v[2]={+cos(30.0*deg),+sin(30.0*deg)}; static const double w[2]={ 0.0 , +1.0 }; double ii=i,jj=j; for (i=0;i<2;i++) p[i]=0.0; for (i=0;i<2;i++) p[i]+=(ii*u[i])+(jj*w[i]); } //--------------------------------------------------------------------------- void get_hextriangle(double *p0,double *p1,double *p2,int i,int j) // i,j aqua { if (int(j&1)==0) { j=(j>>1)+(i>>1); if (int(i&1)==0) { get_hexpoint(p0,i,j); get_hexpoint(p1,i+1,j); get_hexpoint(p2,i+1,j+1); } else{ get_hexpoint(p0,i,j); get_hexpoint(p1,i,j+1); get_hexpoint(p2,i+1,j+1); } } else{ j=(j>>1)+(i>>1); if (int(i&1)==0) { get_hexpoint(p0,i,j); get_hexpoint(p1,i+1,j+1); get_hexpoint(p2,i,j+1); } else{ j++; get_hexpoint(p0,i,j); get_hexpoint(p1,i+1,j); get_hexpoint(p2,i+1,j+1); } } } //--------------------------------------------------------------------------- void TMain::draw() { scr.cls(); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glScalef(0.2,0.2,1.0); int i,j,n=10; double p0[2],p1[2],p2[2]; glLineWidth(3); for (i=-n;i<=+n;i++) for (j=-n;j<=+n;j++) { get_hextriangle(p0,p1,p2,i,j); glColor3f(0.2,0.2,0.2); glBegin(GL_TRIANGLES); glVertex2dv(p0); glVertex2dv(p1); glVertex2dv(p2); glEnd(); glColor3f(1.0,1.0,1.0); glBegin(GL_LINE_LOOP); glVertex2dv(p0); glVertex2dv(p1); glVertex2dv(p2); glEnd(); } glLineWidth(1); scr.exe(); scr.rfs(); } //--------------------------------------------------------------------------- And here the output: Note that the 4 cases branches might be converted to brunch-less code by changing the logic a bit moving the if statements to point index deltas or by using LUTs... but I am too lazy to do that.
Point in Polygon 3d (same plane) algorithm
I have a point and a polygon in the same plane in 3d space and now I want to check whether or not the point is in the polygon or not. Is there an easy way to change the algorithm from this thread Point in Polygon Algorithm to work for 3d space? Or are there other algorithms that can solve this problem easily? If there are not, would the following idea work: Check if the plane is the XZ-plane or the YZ-plane, if yes, ignore the other axis (i.e. for the XZ-plane ignore the y values) and use the pip algorithm from the before mentioned thread. And if no, just ignore the z values of the point and the polygon and use the pip algorithm.
there are 2 "basic" ways of testing planar concave polygon: convert to set of convex ones and test direction of cross product between point and all faces the conversion to convex polygon is not as easy but its doable either by triangulation or clipping ear or what ever method... After that just check the cross products... so if your convex polygon has vertexes p0,p1,p2,...,p(n-1) and testing point p then d0 = cross( p-p0 , p0-p(n-1) ); for (i=1;i<n;i++) { di = cross( p-p(i), p(i)-p(i-1) ); if ( dot ( d0 , di ) <=0.0 ) return false; } return true; so just check all the polygons and return OR of the subresults use hit test You cast ray from your point in any direction parallel to your plane and count the number of hits you ray has done with the edges of polygon. If its even point is outside if its odd point is inside. The link in your question uses this algo. However in 3D you need to change the direction so it still is inside plane... for example by using single edge of polygon dir=p1-p0 as your direction. You also have to code some rules for cases when your ray hits Vertex directly so its counted just once instead of multiple times. Also the hit must be computed in 3D so you need axis/line intersection. It can be found here: Cone to box collision just look for line closest(axis a0,line l1) function. It returns line that is the closest connection between line and axis. Then just simply check if the two points are the same or not (length of the line is zero). Now to simplify this you might port your 3D data into 2D That can get rid of some accuracy problems related to rounding to the plane... You are doing this by just ignoring one coordinate. That is simple but it might bring some rounding problems also the result has different shape (scaled differently in each axis) so the metrics is not the same anymore which might bring other problems latter if this is used for other purposes or any kind of thresholding is used. There is a better method. We need any 2 basis vectors u,v that are perpendicular to each and are inside your plane and one point o inside the plane. That is easy just: o = p0; // any point from the polygon u = p1-p0; // any edge of polygon u /= |u|; // normalize v = p2-p1; // any other edge of polygon v /= |v|; // normalize for (;fabs(dot(u,v)>0.75;) // if too parallel { v=(p(1+rand(n-1))-p0); // chose random "edge" v /= |v|; // normalize } v=cross(u,v); // make u,v perpendicular v=cross(v,u); // and inside the plane v /= |v|; // normalize just in case because of rounding the size might not be unit anymore Now to convert point p(x,y,z) from 3D to 2D (x,y) just do: x = dot(p-o,u); y = dot(p-o,v); to convert back to 3D: p = o + x*u + y*v; With this way of conversion the metrics is the same so the length of polygon edges and size of polygon will not change ...
Dodecahedron (or any platonic solids) uniform rotations, so that the vertices do not overlap any of the previous rotations
How to rotate an object, so that its vertices never overlap with any of the other rotations? With a predefined number of rotations. Idea: It can be achieved with relaxation. (Idea comes from Greg Turk's paper: Generating Textures on Arbitrary Surfaces Using Reaction-Diffusion) Steps: Generate x dodecahedrons or any object symmetric to its centre point. These objects should be identical in position, orientation and size. (so we can create an easy relation between vertices => ones that overlap at the beginning are related) Create a function that calculates the distance between the related points. Maximize the average distance between related points. (every point has x-1 related points) Problems: This is not a simple relaxation problem with points. Here, due to the dodecahedron constraint, I cannot just translate around. Rotation matrixes/quaternions are needed.
Possible solution with Brute force Summary:Rotate randomly until desired average distance is achieved. Explanation:Every dodecahedron is rotated until it's vertices do not overlap any vertices of the other dodecahedrons. Then average distance is calculated and checked against the best (minimal) so far. Save all vertex positions, and the rotation quaternion, that will turn the base dodecahedron into the rotated one. float minThreshold <- user defined int iterationThreshold <- user defined float minAveDistance = Infinity; while (minAveDistance > minThreshold || maxIteration > iterationThreshold) { Foreach (dodecahedron) { // except the first one, that can stay as is // rotate randomly until there are no overlapping positions with the other dodecahedrons while (checkOverlappingWithOtherDodecahedrons(dodecahedron)) { rotateRandomly(dodecahedron); } } float aveDistance = CalculateAverageDistanceBetweenAllPointsOfDodecahedrons(); if (aveDistance < minAveDistance) { minAveDistance = aveDistance; SaveAllPositions(); // e.g.: to a file SaveAllRotationQuaternionsFromStartOrientation(); // e.g.: to a file } }
Finding the area of a 2-D data set
I have a .txt file with about 100,000 points in the 2-D plane. When I plot the points, there is a clearly defined 2-D region (think of a 2-D disc that has been morphed a bit). What is the easiest way to compute the area of this region? Any way of doing easily in Matlab? I made a polygonal approximation by finding a bunch (like 40) points on the boundary of the region and computing the area of the polygonal region in Matlab, but I was wondering if there is another, less tedious method than finding 40 points on the boundary.
Consider this example: %# random points x = randn(300,1); y = randn(300,1); %# convex hull dt = DelaunayTri(x,y); k = convexHull(dt); %# area of convex hull ar = polyarea(dt.X(k,1),dt.X(k,2)) %# plot plot(dt.X(:,1), dt.X(:,2), '.'), hold on fill(dt.X(k,1),dt.X(k,2), 'r', 'facealpha', 0.2); hold off title( sprintf('area = %g',ar) ) There is a short screencast By Doug Hull which solves this exact problem. EDIT: I am posting a second answer inspired by the solution proposed by #Jean-FrançoisCorbett. First I create random data, and using the interactive brush tool, I remove some points to make it look like the desired "kidney" shape... To have a baseline to compare against, we can manually trace the enclosing region using the IMFREEHAND function (I'm doing this using my laptop's touchpad, so not the most accurate drawing!). Then we find the area of this polygon using POLYAREA. Just like my previous answer, I compute the convex hull as well: Now, and based on a previous SO question I had answered (2D histogram), the idea is to lay a grid over the data. The choice of the grid resolution is very important, mine was numBins = [20 30]; for the data used. Next we count the number of squares containing enough points (I used at least 1 point as threshold, but you could try a higher value). Finally we multiply this count by the area of one grid square to obtain the approximated total area. %### DATA ### %# some random data X = randn(100000,1)*1; Y = randn(100000,1)*2; %# HACK: remove some point to make data look like a kidney idx = (X<-1 & -4<Y & Y<4 ); X(idx) = []; Y(idx) = []; %# or use the brush tool %#brush on %### imfreehand ### figure line('XData',X, 'YData',Y, 'LineStyle','none', ... 'Color','b', 'Marker','.', 'MarkerSize',1); daspect([1 1 1]) hROI = imfreehand('Closed',true); pos = getPosition(hROI); %# pos = wait(hROI); delete(hROI) %# total area ar1 = polyarea(pos(:,1), pos(:,2)); %# plot hold on, plot(pos(:,1), pos(:,2), 'Color','m', 'LineWidth',2) title('Freehand') %### 2D histogram ### %# center of bins numBins = [20 30]; xbins = linspace(min(X), max(X), numBins(1)); ybins = linspace(min(Y), max(Y), numBins(2)); %# map X/Y values to bin-indices Xi = round( interp1(xbins, 1:numBins(1), X, 'linear', 'extrap') ); Yi = round( interp1(ybins, 1:numBins(2), Y, 'linear', 'extrap') ); %# limit indices to the range [1,numBins] Xi = max( min(Xi,numBins(1)), 1); Yi = max( min(Yi,numBins(2)), 1); %# count number of elements in each bin H = accumarray([Yi(:), Xi(:)], 1, [numBins(2) numBins(1)]); %# total area THRESH = 0; sqNum = sum(H(:)>THRESH); sqArea = (xbins(2)-xbins(1)) * (ybins(2)-ybins(1)); ar2 = sqNum*sqArea; %# plot 2D histogram/thresholded_histogram figure, imagesc(xbins, ybins, H) axis on, axis image, colormap hot; colorbar; %#caxis([0 500]) title( sprintf('2D Histogram, bins=[%d %d]',numBins) ) figure, imagesc(xbins, ybins, H>THRESH) axis on, axis image, colormap gray title( sprintf('H > %d',THRESH) ) %### convex hull ### dt = DelaunayTri(X,Y); k = convexHull(dt); %# total area ar3 = polyarea(dt.X(k,1), dt.X(k,2)); %# plot figure, plot(X, Y, 'b.', 'MarkerSize',1), daspect([1 1 1]) hold on, fill(dt.X(k,1),dt.X(k,2), 'r', 'facealpha',0.2); hold off title('Convex Hull') %### plot ### figure, hold on %# plot histogram imagesc(xbins, ybins, H>=1) axis on, axis image, colormap gray %# plot grid lines xoff = diff(xbins(1:2))/2; yoff = diff(ybins(1:2))/2; xv1 = repmat(xbins+xoff,[2 1]); xv1(end+1,:) = NaN; yv1 = repmat([ybins(1)-yoff;ybins(end)+yoff;NaN],[1 size(xv1,2)]); yv2 = repmat(ybins+yoff,[2 1]); yv2(end+1,:) = NaN; xv2 = repmat([xbins(1)-xoff;xbins(end)+xoff;NaN],[1 size(yv2,2)]); xgrid = [xv1(:);NaN;xv2(:)]; ygrid = [yv1(:);NaN;yv2(:)]; line(xgrid, ygrid, 'Color',[0.8 0.8 0.8], 'HandleVisibility','off') %# plot points h(1) = line('XData',X, 'YData',Y, 'LineStyle','none', ... 'Color','b', 'Marker','.', 'MarkerSize',1); %# plot convex hull h(2) = patch('XData',dt.X(k,1), 'YData',dt.X(k,2), ... 'LineWidth',2, 'LineStyle','-', ... 'EdgeColor','r', 'FaceColor','r', 'FaceAlpha',0.5); %# plot freehand polygon h(3) = plot(pos(:,1), pos(:,2), 'g-', 'LineWidth',2); %# compare results title(sprintf('area_{freehand} = %g, area_{grid} = %g, area_{convex} = %g', ... ar1,ar2,ar3)) legend(h, {'Points' 'Convex Jull','FreeHand'}) hold off Here is the final result of all three methods overlayed, with the area approximations displayed:
My answer is the simplest and perhaps the least elegant and precise. But first, a comment on previous answers: Since your shape is usually kidney-shaped (not convex), calculating the area of its convex hull won't do, and an alternative is to determine its concave hull (see e.g. http://www.concavehull.com/home.php?main_menu=1) and calculate the area of that. But determining a concave hull is far more difficult than a convex hull. Plus, straggler points will cause trouble in both he convex and concave hull. Delaunay triangulation followed by pruning, as suggested in #Ed Staub's answer, may a bit be more straightforward. My own suggestion is this: How precise does your surface area calculation have to be? My guess is, not very. With either concave hull or pruned Delaunay triangulation, you'll have to make an arbitrary choice anyway as to where the "boundary" of your shape is (the edge isn't knife-sharp, and I see there are some straggler points sprinkled around it). Therefore a simpler algorithm may be just as good for your application. Divide your image in an orthogonal grid. Loop through all grid "pixels" or squares; if a given square contains at least one point (or perhaps two points?), mark the square as full, else empty. Finally, add the area of all full squares. Bingo. The only parameter is the resolution length (size of the squares). Its value should be set to something similar to the pruning length in the case of Delaunay triangulation, i.e. "points within my shape are closer to each other than this length, and points further apart than this length should be ignored". Perhaps an additional parameter is the number of points threshold for a square to be considered full. Maybe 2 would be good to ignore straggler points, but that may define the main shape a bit too tightly for your taste... Try both 1 and 2, and perhaps take an average of both. Or, use 1 and prune away the squares that have no neighbours (game-of-life-style). Simlarly, empty squares whose 8 neighbours are full should be considered full, to avoid holes in the middle of the shape. There is no end to how much this algorithm can be refined, but due to the arbitrariness intrinsic to the problem definition in your particular application, any refinement is probably the algorithm equivalent of "polishing a turd".
I know next to nothing, so don't put much stock in this... consider doing a Delaunay triangulation. Then remove any hull (outer) edges longer than some maximum. Repeat until nothing to remove. Fill the remaining triangles. This will orphan some outlier points.
I suggest using a space-filling-curve, for example a z-curve or better a moore curve. A sfc fills the full space and is good to index each points. For example for all f(x)=y you can sort the points of the curve in ascendending order and from that result you take as many points until you get a full roundtrip. These points you can then use to compute the area. Because you have many points maybe you want to use less points and use a cluster which make the result less accurate.
I think you can get the border points using convex hull algorithm with restriction to the edge length (you should sort points by vertical axis). Thus it will follow nonconvexity of your region. I propose length round 0.02. In any case you can experiment a bit with different lengths drawing the result and examining it visually.
Fast Collision Detection for Circle Insertion into 2D Plane
I know there are lots of posts about collision detection generally for sprites moving about a 2D plane, but my question is slightly different. I'm inserting circles into a 2D plane. The circles have variable radii. I'm trying to optimize my method of finding a random position within the plane where I can insert a new circle without it colliding with any other circles already on the plane. Right now I'm using a very "un-optimized" approach that simply generates a random point within the plane and then checks it against all the other circles on the plane. Are there ways to optimize this? For this particular app, the bounds of the plane can only hold 20-25 circles at a time and typically there are between 5-10 present. As you would expect, when the number of circles approaches the max that can fit, you have to test many points before finding one that works. It gets very slow. Note: safeDistance is the radius of the circle I want to add to the plane. Here's the code: - (CGPoint)getSafePosition:(float)safeDistance { // Point must be far enough from edges // Point must be far enough from other sprites CGPoint thePoint; BOOL pointIsSafe = NO; int sd = ceil(safeDistance); while(!pointIsSafe) { self.pointsTested++; // DEBUG // generate a random point inside the plane boundaries to test thePoint = CGPointMake((arc4random() % ((int)self.manager.gameView.frame.size.width - sd*2)) + sd, (arc4random() % ((int)self.manager.gameView.frame.size.height - sd*2)) + sd); if(self.manager.gameView.sprites.count > 0) { for(BasicSprite *theSprite in self.manager.gameView.sprites) { // get distance between test point and the sprite position float distance = [BasicSprite distanceBetweenPoints:thePoint b:theSprite.position]; // check if distance is less than the sum of the min safe distances of the two entities if(distance < (safeDistance + [theSprite minSafeDistance])) { // point not safe pointIsSafe = NO; break; } // if we get here, the point did not collide with the last tested point pointIsSafe = YES; } } else { pointIsSafe = YES; } } return thePoint; }
Subdivide your window into w by h blocks. You'll be maintaining a w by h array, dist. dist[x][y] contains the size of the largest circle that can be centred at (x, y). (You can use pixels as blocks, although we'll be updating the entire array with each circle placed, so you may want to choose larger blocks for improved speed, at the cost of slightly reduced packing densities.) Initialisation Initially, set all dist[x][y] to min(x, y, w - x, h - y). This encodes the limits given by the bounding box that is the window. Update procedure Every time you add a circle to the window, say one positioned at (a, b) with radius r, you need to update all elements of dist. The update required for each position (x, y) is: dist[x][y] = min(dist[x][y], sqrt((x - a)^2 + (y - b)^2) - r); (Obviously, ^2 here means squaring, not XOR.) Basically, we are saying: "Set dist[x][y] to the minimum distance to the circle just placed, unless the situation is already worse than that." dist values for points inside the circle just placed will be negative, but that doesn't matter. Finding the next location Then, when you want to insert the next circle of radius q, just scan through dist looking for a location with dist value >= q. (If you want to randomly choose such a location, find the complete list of valid locations and then randomly choose one.)
Honestly, with only 20-25 circles, you're not going to get much of a speed boost by using a fancier algorithm or data structure (e.g. a quadtree or a kd-tree). Everything is fast for small n. Are you absolutely sure this is the bottleneck in your application? Have you profiled? If yes, then the way you're going to speed this up is through microoptimization, not through advanced algorithms. Are you making lots of iterations through the while loop because most of the plane is unsafe?
You could split your plane in lots of little rectangles (slightly quadtree-related) and save which rectangles are hit by at least one of the circles. When you look for a insertion-point, you'll just have to look for some "empty" ones (which doesn't need any random jumps and is possible in constant time). The number and constellation of rectangles can be computed by the radius.
Just an outline, since this solution is fairly involved. If you want to guarantee you always find a place to put a circle if it's possible, you can do the following. Consider each existing circle C. We will try to find a location where we can place the new circle so that it is touching C. For each circle D (other than C) that is sufficiently close to C, there will be a range of angles where placing a new circle at one of those angles around C will make it intersect with D. Some geometry will give you that range. Similarly, for each of the four boundaries that are close enough to C, there will be a range of angles where placing a new circle at one of those angles will make it intersect with the boundary. If all these intervals cover all 360 degrees around C, then you cannot place a circle adjacent to C, and you will have to try the next circle, until there are no more candidates for C. If you find a place to put the new circle, you can move it some random distance away from C so that all your new circles do not have to be adjacent to an existing circle if that is not necessary.