//-------------------------------------------------------------------------- // // File: nnpi.c // // Created: 15/11/2002 // // Author: Pavel Sakov // CSIRO Marine Research // // Purpose: Code for: // -- Natural Neighbours Point Interpolator // -- Natural Neighbours Point Hashing Interpolator // // Description: `nnpi' -- "Natural Neighbours Point // Interpolator" -- is a structure for conducting Natural // Neighbours interpolation on a given data on a // "point-to-point" basis. Because it involves weight // calculation for each next output point, it is not // particularly suitable for consequitive interpolations on // the same set of observation points -- use // `nnhpi' or `nnai' // in these cases. // // `nnhpi' is a structure for // conducting consequitive Natural Neighbours interpolations // on a given spatial data set in a random sequence of points // from a set of finite size, taking advantage of repeated // interpolations in the same point. It allows to modify Z // coordinate of data in between interpolations. // // // Revisions: 01/04/2003 PS: modified nnpi_triangle_process(): for // Sibson interpolation, if circle_build fails(), now a // local copy of a point is moved slightly rather than the // data point itself. The later approach have found leading // to inconsistencies of the new point position with the // earlier built triangulation. // //-------------------------------------------------------------------------- #include #include #include #include #include #include #include #include "nn.h" #include "delaunay.h" #include "nan.h" #include "hash.h" struct nnpi { delaunay* d; point * p; double wmin; // // work variables // int nvertices; int nallocated; int * vertices; // vertex indices double* weights; int n; // number of points processed }; int circle_build( circle* c, point* p0, point* p1, point* p2 ); int circle_contains( circle* c, point* p ); void delaunay_circles_find( delaunay* d, point* p, int* n, int** out ); int delaunay_xytoi( delaunay* d, point* p, int seed ); void nn_quit( const char* format, ... ); void nnpi_reset( nnpi* nn ); void nnpi_calculate_weights( nnpi* nn ); void nnpi_normalize_weights( nnpi* nn ); void nnpi_set_point( nnpi* nn, point* p ); int nnpi_get_nvertices( nnpi* nn ); int* nnpi_get_vertices( nnpi* nn ); double* nnpi_get_weights( nnpi* nn ); #define NSTART 10 #define NINC 10 #define EPS_SHIFT 1.0e-9 #define N_SEARCH_TURNON 20 #define BIGNUMBER 1.0e+100 #define min( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) ) #define max( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) ) // Creates Natural Neighbours point interpolator. // // @param d Delaunay triangulation // @return Natural Neighbours interpolation // nnpi* nnpi_create( delaunay* d ) { nnpi* nn = malloc( sizeof ( nnpi ) ); nn->d = d; nn->wmin = -DBL_MAX; nn->vertices = calloc( NSTART, sizeof ( int ) ); nn->weights = calloc( NSTART, sizeof ( double ) ); nn->nvertices = 0; nn->nallocated = NSTART; nn->p = NULL; nn->n = 0; return nn; } // Destroys Natural Neighbours point interpolator. // // @param nn Structure to be destroyed // void nnpi_destroy( nnpi* nn ) { free( nn->weights ); free( nn->vertices ); free( nn ); } void nnpi_reset( nnpi* nn ) { nn->nvertices = 0; nn->p = NULL; memset( nn->d->flags, 0, (size_t) ( nn->d->ntriangles ) * sizeof ( int ) ); } static void nnpi_add_weight( nnpi* nn, int vertex, double w ) { int i; // // find whether the vertex is already in the list // for ( i = 0; i < nn->nvertices; ++i ) if ( nn->vertices[i] == vertex ) break; if ( i == nn->nvertices ) // not in the list { // // get more memory if necessary // if ( nn->nvertices == nn->nallocated ) { nn->vertices = realloc( nn->vertices, (size_t) ( nn->nallocated + NINC ) * sizeof ( int ) ); nn->weights = realloc( nn->weights, (size_t) ( nn->nallocated + NINC ) * sizeof ( double ) ); nn->nallocated += NINC; } // // add the vertex to the list // nn->vertices[i] = vertex; nn->weights[i] = w; nn->nvertices++; } else // in the list { if ( nn_rule == SIBSON ) nn->weights[i] += w; else if ( w > nn->weights[i] ) nn->weights[i] = w; } } static double triangle_scale_get( delaunay* d, triangle* t ) { double x0 = d->points[t->vids[0]].x; double x1 = d->points[t->vids[1]].x; double x2 = d->points[t->vids[2]].x; double y0 = d->points[t->vids[0]].y; double y1 = d->points[t->vids[1]].y; double y2 = d->points[t->vids[2]].y; double xmin = min( min( x0, x1 ), x2 ); double xmax = max( max( x0, x1 ), x2 ); double ymin = min( min( y0, y1 ), y2 ); double ymax = max( max( y0, y1 ), y2 ); return xmax - xmin + ymax - ymin; } // This is a central procedure for the Natural Neighbours interpolation. It // uses the Watson's algorithm for the required areas calculation and implies // that the vertices of the delaunay triangulation are listed in uniform // (clockwise or counterclockwise) order. // static void nnpi_triangle_process( nnpi* nn, point* p, int i ) { delaunay* d = nn->d; triangle* t = &d->triangles[i]; circle * c = &d->circles[i]; circle cs[3]; int j; assert( circle_contains( c, p ) ); if ( nn_rule == SIBSON ) { point pp; pp.x = p->x; pp.y = p->y; // // Sibson interpolation by using Watson's algorithm // do { for ( j = 0; j < 3; ++j ) { int j1 = ( j + 1 ) % 3; int j2 = ( j + 2 ) % 3; int v1 = t->vids[j1]; int v2 = t->vids[j2]; if ( !circle_build( &cs[j], &d->points[v1], &d->points[v2], &pp ) ) { double scale = triangle_scale_get( d, t ); if ( d->points[v1].y == d->points[v2].y ) pp.y += EPS_SHIFT * scale; else pp.x += EPS_SHIFT * scale; break; } } } while ( j != 3 ); for ( j = 0; j < 3; ++j ) { int j1 = ( j + 1 ) % 3; int j2 = ( j + 2 ) % 3; double det = ( ( cs[j1].x - c->x ) * ( cs[j2].y - c->y ) - ( cs[j2].x - c->x ) * ( cs[j1].y - c->y ) ); nnpi_add_weight( nn, t->vids[j], det ); } } else if ( nn_rule == NON_SIBSONIAN ) { double d1 = c->r - hypot( p->x - c->x, p->y - c->y ); for ( i = 0; i < 3; ++i ) { int vid = t->vids[i]; point * pp = &d->points[vid]; double d2 = hypot( p->x - pp->x, p->y - pp->y ); if ( d2 == 0.0 ) nnpi_add_weight( nn, vid, BIGNUMBER ); else nnpi_add_weight( nn, vid, d1 / d2 ); } } else nn_quit( "unknown rule\n" ); } void nnpi_calculate_weights( nnpi* nn ) { point* p = nn->p; int n = nn->d->ntriangles; int i; if ( n > N_SEARCH_TURNON ) { int* tids; delaunay_circles_find( nn->d, p, &n, &tids ); for ( i = 0; i < n; ++i ) nnpi_triangle_process( nn, p, tids[i] ); } else for ( i = 0; i < n; ++i ) if ( circle_contains( &nn->d->circles[i], p ) ) nnpi_triangle_process( nn, p, i ); } void nnpi_normalize_weights( nnpi* nn ) { int n = nn->nvertices; double sum = 0.0; int i; for ( i = 0; i < n; ++i ) sum += nn->weights[i]; for ( i = 0; i < n; ++i ) nn->weights[i] /= sum; } // Finds Natural Neighbours-interpolated value for a point. // // @param nn NN interpolation // @param p Point to be interpolated (p->x, p->y -- input; p->z -- output) // void nnpi_interpolate_point( nnpi* nn, point* p ) { delaunay* d = nn->d; int i; nnpi_reset( nn ); nn->p = p; nnpi_calculate_weights( nn ); nnpi_normalize_weights( nn ); if ( nn_verbose ) { if ( nn_test_vertice == -1 ) { if ( nn->n == 0 ) fprintf( stderr, "weights:\n" ); fprintf( stderr, " %d: {", nn->n ); for ( i = 0; i < nn->nvertices; ++i ) { fprintf( stderr, "(%d,%.5g)", nn->vertices[i], nn->weights[i] ); if ( i < nn->nvertices - 1 ) fprintf( stderr, ", " ); } fprintf( stderr, "}\n" ); } else { double w = 0.0; if ( nn->n == 0 ) fprintf( stderr, "weights for vertex %d:\n", nn_test_vertice ); for ( i = 0; i < nn->nvertices; ++i ) { if ( nn->vertices[i] == nn_test_vertice ) { w = nn->weights[i]; break; } } fprintf( stderr, "%15.7g %15.7g %15.7g\n", p->x, p->y, w ); } } nn->n++; if ( nn->nvertices == 0 ) { p->z = NaN; return; } p->z = 0.0; for ( i = 0; i < nn->nvertices; ++i ) { double weight = nn->weights[i]; if ( weight < nn->wmin ) { p->z = NaN; return; } p->z += d->points[nn->vertices[i]].z * weight; } } // Performs Natural Neighbours interpolation for an array of points. // // @param nin Number of input points // @param pin Array of input points [pin] // @param wmin Minimal allowed weight // @param nout Number of output points // @param pout Array of output points [nout] // void nnpi_interpolate_points( int nin, point pin[], double wmin, int nout, point pout[] ) { delaunay* d = delaunay_build( nin, pin, 0, NULL, 0, NULL ); nnpi * nn = nnpi_create( d ); int seed = 0; int i; nn->wmin = wmin; if ( nn_verbose ) { fprintf( stderr, "xytoi:\n" ); for ( i = 0; i < nout; ++i ) { point* p = &pout[i]; fprintf( stderr, "(%.7g,%.7g) -> %d\n", p->x, p->y, delaunay_xytoi( d, p, seed ) ); } } for ( i = 0; i < nout; ++i ) nnpi_interpolate_point( nn, &pout[i] ); if ( nn_verbose ) { fprintf( stderr, "output:\n" ); for ( i = 0; i < nout; ++i ) { point* p = &pout[i]; fprintf( stderr, " %d:%15.7g %15.7g %15.7g\n", i, p->x, p->y, p->z ); } } nnpi_destroy( nn ); delaunay_destroy( d ); } // Sets minimal allowed weight for Natural Neighbours interpolation. // @param nn Natural Neighbours point interpolator // @param wmin Minimal allowed weight // void nnpi_setwmin( nnpi* nn, double wmin ) { nn->wmin = wmin; } // Sets point to interpolate in. // @param nn Natural Neighbours point interpolator // @param p Point to interpolate in // void nnpi_set_point( nnpi* nn, point* p ) { nn->p = p; } // Gets number of data points involved in current interpolation. // @return Number of data points involved in current interpolation // int nnpi_get_nvertices( nnpi* nn ) { return nn->nvertices; } // Gets indices of data points involved in current interpolation. // @return indices of data points involved in current interpolation // int* nnpi_get_vertices( nnpi* nn ) { return nn->vertices; } // Gets weights of data points involved in current interpolation. // @return weights of data points involved in current interpolation // double* nnpi_get_weights( nnpi* nn ) { return nn->weights; } // // nnhpi // struct nnhpi { nnpi * nnpi; hashtable* ht_data; hashtable* ht_weights; int n; // number of points processed }; typedef struct { int nvertices; int * vertices; // vertex indices [nvertices] double* weights; // vertex weights [nvertices] } nn_weights; // Creates Natural Neighbours hashing point interpolator. // // @param d Delaunay triangulation // @param size Hash table size (should be of order of number of output points) // @return Natural Neighbours interpolation // nnhpi* nnhpi_create( delaunay* d, int size ) { nnhpi* nn = malloc( sizeof ( nnhpi ) ); int i; nn->nnpi = nnpi_create( d ); nn->ht_data = ht_create_d2( d->npoints ); nn->ht_weights = ht_create_d2( size ); nn->n = 0; for ( i = 0; i < d->npoints; ++i ) ht_insert( nn->ht_data, &d->points[i], &d->points[i] ); return nn; } static void free_nn_weights( void* data ) { nn_weights* weights = (nn_weights *) data; free( weights->vertices ); free( weights->weights ); free( weights ); } // Destroys Natural Neighbours hashing point interpolation. // // @param nn Structure to be destroyed // void nnhpi_destroy( nnhpi* nn ) { ht_destroy( nn->ht_data ); ht_process( nn->ht_weights, free_nn_weights ); ht_destroy( nn->ht_weights ); nnpi_destroy( nn->nnpi ); } // Finds Natural Neighbours-interpolated value in a point. // // @param nnhp NN point hashing interpolator // @param p Point to be interpolated (p->x, p->y -- input; p->z -- output) // void nnhpi_interpolate( nnhpi* nnhp, point* p ) { nnpi * nnp = nnhp->nnpi; delaunay * d = nnp->d; hashtable * ht_weights = nnhp->ht_weights; nn_weights* weights; int i; if ( ht_find( ht_weights, p ) != NULL ) { weights = ht_find( ht_weights, p ); if ( nn_verbose ) fprintf( stderr, " \n" ); } else { nnpi_reset( nnp ); nnp->p = p; nnpi_calculate_weights( nnp ); nnpi_normalize_weights( nnp ); weights = malloc( sizeof ( nn_weights ) ); weights->vertices = malloc( sizeof ( int ) * (size_t) ( nnp->nvertices ) ); weights->weights = malloc( sizeof ( double ) * (size_t) ( nnp->nvertices ) ); weights->nvertices = nnp->nvertices; for ( i = 0; i < nnp->nvertices; ++i ) { weights->vertices[i] = nnp->vertices[i]; weights->weights[i] = nnp->weights[i]; } ht_insert( ht_weights, p, weights ); if ( nn_verbose ) { if ( nn_test_vertice == -1 ) { if ( nnp->n == 0 ) fprintf( stderr, "weights:\n" ); fprintf( stderr, " %d: {", nnp->n ); for ( i = 0; i < nnp->nvertices; ++i ) { fprintf( stderr, "(%d,%.5g)", nnp->vertices[i], nnp->weights[i] ); if ( i < nnp->nvertices - 1 ) fprintf( stderr, ", " ); } fprintf( stderr, "}\n" ); } else { double w = 0.0; if ( nnp->n == 0 ) fprintf( stderr, "weights for vertex %d:\n", nn_test_vertice ); for ( i = 0; i < nnp->nvertices; ++i ) { if ( nnp->vertices[i] == nn_test_vertice ) { w = nnp->weights[i]; break; } } fprintf( stderr, "%15.7g %15.7g %15.7g\n", p->x, p->y, w ); } } nnp->n++; } nnhp->n++; if ( weights->nvertices == 0 ) { p->z = NaN; return; } p->z = 0.0; for ( i = 0; i < weights->nvertices; ++i ) { if ( weights->weights[i] < nnp->wmin ) { p->z = NaN; return; } p->z += d->points[weights->vertices[i]].z * weights->weights[i]; } } // Modifies interpolated data. // Finds point* pd in the underlying Delaunay triangulation such that // pd->x = p->x and pd->y = p->y, and copies p->z to pd->z. Exits with error // if the point is not found. // // @param nnhp Natural Neighbours hashing point interpolator // @param p New data // void nnhpi_modify_data( nnhpi* nnhp, point* p ) { point* orig = ht_find( nnhp->ht_data, p ); assert( orig != NULL ); orig->z = p->z; } // Sets minimal allowed weight for Natural Neighbours interpolation. // @param nn Natural Neighbours point hashing interpolator // @param wmin Minimal allowed weight // void nnhpi_setwmin( nnhpi* nn, double wmin ) { nn->nnpi->wmin = wmin; } #if defined ( NNPHI_TEST ) #include #define NPOINTSIN 10000 #define NMIN 10 #define NX 101 #define NXMIN 1 #define SQ( x ) ( ( x ) * ( x ) ) static double franke( double x, double y ) { x *= 9.0; y *= 9.0; return 0.75 * exp( ( -SQ( x - 2.0 ) - SQ( y - 2.0 ) ) / 4.0 ) + 0.75 * exp( -SQ( x - 2.0 ) / 49.0 - ( y - 2.0 ) / 10.0 ) + 0.5 * exp( ( -SQ( x - 7.0 ) - SQ( y - 3.0 ) ) / 4.0 ) - 0.2 * exp( -SQ( x - 4.0 ) - SQ( y - 7.0 ) ); } static void usage() { printf( "Usage: nnhpi_test [-a] [-n ] [-v|-V]\n" ); printf( "Options:\n" ); printf( " -a -- use non-Sibsonian interpolation rule\n" ); printf( " -n :\n" ); printf( " -- number of input points (default = 10000)\n" ); printf( " -- number of output points per side (default = 64)\n" ); printf( " -v -- verbose\n" ); printf( " -V -- very verbose\n" ); exit( 0 ); } int main( int argc, char* argv[] ) { int nin = NPOINTSIN; int nx = NX; int nout = 0; point * pin = NULL; delaunay * d = NULL; point * pout = NULL; nnhpi * nn = NULL; int cpi = -1; // control point index struct timeval tv0, tv1; struct timezone tz; int i; i = 1; while ( i < argc ) { switch ( argv[i][1] ) { case 'a': i++; nn_rule = NON_SIBSONIAN; break; case 'n': i++; if ( i >= argc ) nn_quit( "no number of data points found after -n\n" ); nin = atoi( argv[i] ); i++; if ( i >= argc ) nn_quit( "no number of ouput points per side found after -i\n" ); nx = atoi( argv[i] ); i++; break; case 'v': i++; nn_verbose = 1; break; case 'V': i++; nn_verbose = 2; break; default: usage(); break; } } if ( nin < NMIN ) nin = NMIN; if ( nx < NXMIN ) nx = NXMIN; printf( "\nTest of Natural Neighbours hashing point interpolator:\n\n" ); printf( " %d data points\n", nin ); printf( " %d output points\n", nx * nx ); // // generate data // printf( " generating data:\n" ); fflush( stdout ); pin = malloc( nin * sizeof ( point ) ); for ( i = 0; i < nin; ++i ) { point* p = &pin[i]; p->x = (double) random() / RAND_MAX; p->y = (double) random() / RAND_MAX; p->z = franke( p->x, p->y ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } // // triangulate // printf( " triangulating:\n" ); fflush( stdout ); d = delaunay_build( nin, pin, 0, NULL, 0, NULL ); // // generate output points // points_generate2( -0.1, 1.1, -0.1, 1.1, nx, nx, &nout, &pout ); cpi = ( nx / 2 ) * ( nx + 1 ); gettimeofday( &tv0, &tz ); // // create interpolator // printf( " creating interpolator:\n" ); fflush( stdout ); nn = nnhpi_create( d, nout ); fflush( stdout ); gettimeofday( &tv1, &tz ); { long dt = 1000000 * ( tv1.tv_sec - tv0.tv_sec ) + tv1.tv_usec - tv0.tv_usec; printf( " interpolator creation time = %ld us (%.2f us / point)\n", dt, (double) dt / nout ); } // // interpolate // printf( " interpolating:\n" ); fflush( stdout ); gettimeofday( &tv1, &tz ); for ( i = 0; i < nout; ++i ) { point* p = &pout[i]; nnhpi_interpolate( nn, p ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } fflush( stdout ); gettimeofday( &tv0, &tz ); { long dt = 1000000.0 * ( tv0.tv_sec - tv1.tv_sec ) + tv0.tv_usec - tv1.tv_usec; printf( " interpolation time = %ld us (%.2f us / point)\n", dt, (double) dt / nout ); } if ( !nn_verbose ) printf( " control point: (%f, %f, %f) (expected z = %f)\n", pout[cpi].x, pout[cpi].y, pout[cpi].z, franke( pout[cpi].x, pout[cpi].y ) ); printf( " interpolating one more time:\n" ); fflush( stdout ); gettimeofday( &tv0, &tz ); for ( i = 0; i < nout; ++i ) { point* p = &pout[i]; nnhpi_interpolate( nn, p ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } fflush( stdout ); gettimeofday( &tv1, &tz ); { long dt = 1000000.0 * ( tv1.tv_sec - tv0.tv_sec ) + tv1.tv_usec - tv0.tv_usec; printf( " interpolation time = %ld us (%.2f us / point)\n", dt, (double) dt / nout ); } if ( !nn_verbose ) printf( " control point: (%f, %f, %f) (expected z = %f)\n", pout[cpi].x, pout[cpi].y, pout[cpi].z, franke( pout[cpi].x, pout[cpi].y ) ); printf( " entering new data:\n" ); fflush( stdout ); for ( i = 0; i < nin; ++i ) { point* p = &pin[i]; p->z = p->x * p->x - p->y * p->y; nnhpi_modify_data( nn, p ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } printf( " interpolating:\n" ); fflush( stdout ); gettimeofday( &tv1, &tz ); for ( i = 0; i < nout; ++i ) { point* p = &pout[i]; nnhpi_interpolate( nn, p ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } fflush( stdout ); gettimeofday( &tv0, &tz ); { long dt = 1000000.0 * ( tv0.tv_sec - tv1.tv_sec ) + tv0.tv_usec - tv1.tv_usec; printf( " interpolation time = %ld us (%.2f us / point)\n", dt, (double) dt / nout ); } if ( !nn_verbose ) printf( " control point: (%f, %f, %f) (expected z = %f)\n", pout[cpi].x, pout[cpi].y, pout[cpi].z, pout[cpi].x * pout[cpi].x - pout[cpi].y * pout[cpi].y ); printf( " restoring data:\n" ); fflush( stdout ); for ( i = 0; i < nin; ++i ) { point* p = &pin[i]; p->z = franke( p->x, p->y ); nnhpi_modify_data( nn, p ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } printf( " interpolating:\n" ); fflush( stdout ); gettimeofday( &tv0, &tz ); for ( i = 0; i < nout; ++i ) { point* p = &pout[i]; nnhpi_interpolate( nn, p ); if ( nn_verbose ) printf( " (%f, %f, %f)\n", p->x, p->y, p->z ); } fflush( stdout ); gettimeofday( &tv1, &tz ); { long dt = 1000000.0 * ( tv1.tv_sec - tv0.tv_sec ) + tv1.tv_usec - tv0.tv_usec; printf( " interpolation time = %ld us (%.2f us / point)\n", dt, (double) dt / nout ); } if ( !nn_verbose ) printf( " control point: (%f, %f, %f) (expected z = %f)\n", pout[cpi].x, pout[cpi].y, pout[cpi].z, franke( pout[cpi].x, pout[cpi].y ) ); printf( " hashtable stats:\n" ); fflush( stdout ); { hashtable* ht = nn->ht_data; printf( " input points: %d entries, %d table elements, %d filled elements\n", ht->n, ht->size, ht->nhash ); ht = nn->ht_weights; printf( " weights: %d entries, %d table elements, %d filled elements\n", ht->n, ht->size, ht->nhash ); } printf( "\n" ); nnhpi_destroy( nn ); free( pout ); delaunay_destroy( d ); free( pin ); return 0; } #endif