home *** CD-ROM | disk | FTP | other *** search
- /*--------------------------------------------------------------------
- * The GMT-system: @(#)fitcircle.c 2.13 3/13/95
- *
- * Copyright (c) 1991-1995 by P. Wessel and W. H. F. Smith
- * See README file for copying and redistribution conditions.
- *--------------------------------------------------------------------*/
- /*
- * fitcircle <lonlatfile> [-L1] [-L2] [-S]
- *
- * Read lon,lat pairs from stdin[file]. Find mean position and pole
- * of best-fit circle through these points. By default, fit great
- * circle. If -S, fit small circle. In this case, fit great circle
- * first, and then search for minimum small circle by bisection.
- *
- * Formally, we want to minimize some norm on the distance between
- * each point and the circle, measured perpendicular to the circle.
- * For both L1 and L2 norms this is a rather intractable problem.
- * (L2 is non-linear, and in L1 it is not clear how to proceed).
- * However, some approximations exist which work well and are simple
- * to compute. We create a list of x,y,z vectors on the unit sphere,
- * representing the original data. To find a great circle, do this:
- * For L1:
- * Find the Fisher mean of these data, call it mean position.
- * Find the (Fisher) mean of all cross-products between data and
- * the mean position; call this the pole to the great circle.
- * Note that the cross-products are proportional to the distance
- * between datum and mean; hence above average gives data far
- * from mean larger weight in determining pole. This is
- * analogous to fitting line in plane, where data far from
- * average abcissa have large leverage in determining slope.
- * For L2:
- * Create 3 x 3 matrix of sums of products of data vector elements.
- * Find eigenvectors and eigenvalues of this matrix.
- * Find mean as eigenvector corresponding to max eigenvalue.
- * Find pole as eigenvector corresponding to min eigenvalue.
- * Eigenvalue-eigenvector decomposition performed by Jacobi's iterative
- * method of successive Givens rotations. Trials suggest that
- * this converges extremely rapidly (3 sweeps, 9 rotations).
- *
- * To find a small circle, first find the great circle pole and the mean
- * position. Suppose the small circle pole to lie in the plane containing
- * the mean and great circle pole, and narrow down its location by bisection.
- *
- * Author: W. H. F. Smith
- * Date: 16 September 1991-1995.
- * Version: 2.0, for GMT release.
- *
- */
-
- #include "gmt.h"
-
- struct DATA {
- double x[3];
- } *data;
-
- main (argc,argv)
- int argc;
- char **argv;
- {
- int i, j, k, ix, iy, imin, imax, n_alloc, n_data, n, np, nrots, norm = -1;
- BOOLEAN error = FALSE, greenwich = FALSE, find_small_circle = FALSE;
-
- double lonsum, latsum, latlon[2];
- double meanv[3], cross[3], cross_sum[3], gcpole[3], scpole[3]; /* Extra vectors */
- double *a, *lambda, *v, *b, *z; /* Matrix stuff */
- double get_small_circle(), rad, *work;
-
- char buffer[512], format[80];
-
- FILE *fp = NULL;
-
- argc = gmt_begin (argc, argv);
-
- for (i = 1; i < argc; i++) {
- if (argv[i][0] == '-') {
- switch (argv[i][1]) {
-
-
- /* Common parameters */
-
- case 'H':
- case 'V':
- case ':':
- case '\0':
- error += get_common_args (argv[i], 0, 0, 0, 0);
- break;
-
- /* Supplemental parameters */
-
- case 'L':
- norm = 3;
- if (argv[i][2]) norm = atoi(&argv[i][2]);
- break;
- case 'S':
- find_small_circle = TRUE;
- break;
- default:
- error = TRUE;
- gmt_default_error (argv[i][1]);
- break;
- }
- }
- else {
- if ((fp = fopen(argv[i], "r")) == NULL) {
- fprintf (stderr, "fitcircle: Could not open file %s\n", argv[i]);
- error = TRUE;
- }
- }
- }
-
- if (argc == 1 || gmt_quick) {
- fprintf (stderr, "fitcircle %s - find best-fitting great circle to points on sphere\n\n", GMT_VERSION);
- fprintf(stderr,"usage: fitcircle [<input_file>] -L[<n>] [-H] [-S] [-V] [-:]\n\n");
-
- if (gmt_quick) exit (-1);
-
- fprintf(stderr,"\tReads from input_file or standard input\n");
- fprintf(stderr,"\t-L specify norm as -L1 or -L2; or use -L or -L3 to give both.\n");
- fprintf (stderr, "\n\tOPTIONS:\n");
- explain_option ('H');
- fprintf(stderr,"\t-S will attempt to fit a small circle rather than a great circle.\n");
- explain_option ('V');
- explain_option (':');
- exit(-1);
- }
-
- if (norm < 1 || norm > 3) {
- fprintf (stderr, "%s: GMT SYNTAX ERROR -L option: Choose between 1, 2, or 3\n", gmt_program);
- error++;
- }
- if (error) exit (-1);
-
- if (fp == NULL) fp = stdin;
- n_alloc = GMT_CHUNK;
- n_data = 0;
- lonsum = latsum = 0.0;
- sprintf (format, "%s\t%s\n\0", gmtdefs.d_format, gmtdefs.d_format);
-
- data = (struct DATA *) memory (CNULL, n_alloc, sizeof(struct DATA), "fitcircle");
-
- if (gmtdefs.io_header) for (i = 0; i < gmtdefs.n_header_recs; i++) fgets (buffer, 512, fp);
-
- ix = (gmtdefs.xy_toggle) ? 1 : 0; iy = 1 - ix; /* Set up which columns have x and y */
-
- while ( (fgets (buffer, 512, fp) ) != NULL) {
-
- if ((sscanf(buffer, "%lf %lf", &latlon[ix], &latlon[iy])) != 2) {
- fprintf(stderr,"fitcircle: Cannot read line %d\n", n_data+1);
- continue;
- }
- lonsum += latlon[0];
- latsum += latlon[1];
- geo_to_cart (&latlon[1], &latlon[0], data[n_data].x, TRUE);
- n_data++;
-
- if (n_data == n_alloc) {
- n_alloc += GMT_CHUNK;
- data = (struct DATA *) memory ((char *)data, n_alloc, sizeof(struct DATA), "fitcircle");
- }
- }
- if (fp != stdin) fclose(fp);
- data = (struct DATA *) memory ((char *)data, n_data, sizeof(struct DATA), "fitcircle");
- if (find_small_circle && (norm%2) ) work = (double *) memory(CNULL, n_data, sizeof(double), "fitcircle");
-
- lonsum /= n_data;
- latsum /= n_data;
- if (gmtdefs.verbose) {
- fprintf (stderr, "fitcircle: %d points read, Average Position (Flat Earth): ", n_data);
- fprintf (stderr, format, lonsum, latsum);
- }
- if (lonsum > 180.0) greenwich = TRUE;
-
- /* Get Fisher mean in any case, in order to set L2 mean correctly, if needed. */
-
-
- meanv[0] = meanv[1] = meanv[2] = 0.0;
-
- for (i = 0; i < n_data; i++) for (j = 0; j < 3; j++) meanv[j] += data[i].x[j];
-
- normalize3v(meanv);
-
- if (norm%2) {
- cart_to_geo (&latsum, &lonsum, meanv, TRUE);
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L1 Average Position (Fisher's Method): ");
- printf (format, lonsum, latsum);
-
- cross_sum[0] = cross_sum[1] = cross_sum[2] = 0.0;
- for (i = 0; i < n_data; i++) {
- cross3v(&data[i].x[0], meanv, cross);
- if (cross[2] < 0.0) {
- cross_sum[0] -= cross[0];
- cross_sum[1] -= cross[1];
- cross_sum[2] -= cross[2];
- }
- else {
- cross_sum[0] += cross[0];
- cross_sum[1] += cross[1];
- cross_sum[2] += cross[2];
- }
- }
- normalize3v(cross_sum);
- if (find_small_circle) for (i = 0; i < 3; i++) gcpole[i] = cross_sum[i];
-
- cart_to_geo (&latsum, &lonsum, cross_sum, TRUE);
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L1 N Hemisphere Great Circle Pole (Cross-Averaged): ");
- printf (format, lonsum, latsum);
- latsum = -latsum;
- lonsum = d_atan2(-cross_sum[1], -cross_sum[0]) * R2D;
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L1 S Hemisphere Great Circle Pole (Cross-Averaged): ");
- printf (format, lonsum, latsum);
- if (find_small_circle) {
- if (gmtdefs.verbose) fprintf(stderr,"Fitting small circle using L1 norm.\n");
- rad = get_small_circle(data, n_data, meanv, gcpole, scpole, 1, work);
- if (rad >= 0.0) {
- cart_to_geo (&latsum, &lonsum, scpole, TRUE);
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L1 Small Circle Pole: ");
- printf (format, lonsum, latsum);
- printf("Distance from Pole to L1 Small Circle (degrees): %.8lg\n", rad);
- }
- }
- }
-
- if (norm/2) {
-
- n = 3;
- np = n;
-
- a = (double *) memory (CNULL, np*np, sizeof(double), "fitcircle\n");
- lambda = (double *) memory (CNULL, np, sizeof(double), "fitcircle\n");
- b = (double *) memory (CNULL, np, sizeof(double), "fitcircle\n");
- z = (double *) memory (CNULL, np, sizeof(double), "fitcircle\n");
- v = (double *) memory (CNULL, np*np, sizeof(double), "fitcircle\n");
-
- for (i = 0; i < n_data; i++) for (j = 0; j < n; j++) for (k = 0; k < n; k++)
- a[j + k*np] += (data[i].x[j]*data[i].x[k]);
-
- if (jacobi(a, &n, &np, lambda, v, b, z, &nrots)) {
- fprintf(stderr,"fitcircle: Eigenvalue routine failed to converge in 50 sweeps.\n");
- fprintf(stderr,"fitcircle: The reported L2 positions might be garbage.\n");
- }
- if (gmtdefs.verbose) fprintf(stderr,"fitcircle: Eigenvalue routine converged in %d rotations.\n", nrots);
- imax = 0;
- imin = 2;
- if (d_acos (dot3v (v, meanv)) > M_PI_2)
- for (i = 0; i < 3; i++) meanv[i] = -v[imax*np+i];
- else
- for (i = 0; i < 3; i++) meanv[i] = v[imax*np+i];
- cart_to_geo (&latsum, &lonsum, meanv, TRUE);
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L2 Average Position (Eigenval Method): ");
- printf (format, lonsum, latsum);
-
- if (v[imin*np+2] < 0.0) /* Eigvec is in S Hemisphere */
- for (i = 0; i < 3; i++) gcpole[i] = -v[imin*np+i];
- else
- for (i = 0; i < 3; i++) gcpole[i] = v[imin*np+i];
-
- cart_to_geo (&latsum, &lonsum, gcpole, TRUE);
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L2 N Hemisphere Great Circle Pole (Eigenval Method): ");
- printf (format, lonsum, latsum);
- latsum = -latsum;
- lonsum = d_atan2(-gcpole[1], -gcpole[0]) * R2D;
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L2 S Hemisphere Great Circle Pole (Eigenval Method): ");
- printf (format, lonsum, latsum);
-
- free ( (char *)v);
- free ( (char *)z);
- free ( (char *)b);
- free ( (char *)lambda);
- free ( (char *)a);
- if (find_small_circle) {
- if (gmtdefs.verbose) fprintf(stderr,"fitcircle: Fitting small circle using L2 norm.\n");
- rad = get_small_circle(data, n_data, meanv, gcpole, scpole, 2, work);
- if (rad >= 0.0) {
- /* True when small circle fits better than great circle */
- cart_to_geo (&latsum, &lonsum, scpole, TRUE);
- if (greenwich && lonsum < 0.0) lonsum += 360.0;
- printf("L2 Small Circle Pole: ");
- printf (format, lonsum, latsum);
- printf("Distance from Pole to L2 Small Circle (degrees): %.8lg\n", rad);
- }
- }
- }
- if (find_small_circle && (norm%2) ) free ((char*)work);
- free ( (char *)data);
- gmt_end (argc, argv);
- }
-
- double get_small_circle (data, ndata, center, gcpole, scpole, norm, work)
- struct DATA *data;
- int ndata;
- double *center, *gcpole, *scpole, *work;
- int norm;
- {
-
- /* Find scpole, the pole to the best-fit small circle,
- by L(norm) iterative search along arc between
- center and +/- gcpole, the pole to the best fit
- great circle. */
-
- int i, j;
- double temppole[3], a[3], b[3], oldpole[3];
- double trypos, tryneg, afit, bfit, afactor, bfactor, fit, oldfit;
- double length_ab, length_aold, length_bold, circle_misfit(), circle_distance;
-
- /* First find out if solution is between center and gcpole,
- or center and -gcpole: */
-
- for (i = 0; i < 3; i++) temppole[i] = (center[i] + gcpole[i]);
- normalize3v(temppole);
- trypos = circle_misfit(data, ndata, temppole, norm, work, &circle_distance);
-
- for (i = 0; i < 3; i++) temppole[i] = (center[i] - gcpole[i]);
- normalize3v(temppole);
- tryneg = circle_misfit(data, ndata, temppole, norm, work, &circle_distance);
-
- if (tryneg < trypos) {
- for (i = 0; i < 3; i++) a[i] = center[i];
- for (i = 0; i < 3; i++) b[i] = -gcpole[i];
- }
- else {
- for (i = 0; i < 3; i++) a[i] = center[i];
- for (i = 0; i < 3; i++) b[i] = gcpole[i];
- }
-
- /* Now a is at center and b is at pole on correct side.
- Try to bracket a minimum. Move from b toward
- a in 1 degree steps: */
-
- afit = circle_misfit(data, ndata, a, norm, work, &circle_distance);
- bfit = circle_misfit(data, ndata, b, norm, work, &circle_distance);
- j = 1;
- do {
- afactor = sin(j * D2R);
- bfactor = cos(j * D2R);
- for (i = 0; i < 3; i++) temppole[i] = (afactor * a[i] + bfactor * b[i]);
- normalize3v(temppole);
- fit = circle_misfit(data, ndata, temppole, norm, work, &circle_distance);
- j++;
- } while (j < 90 && fit > bfit && fit > afit);
-
- if (j == 90) {
- /* Bad news. There isn't a better fitting pole anywhere. */
- fprintf(stderr,"fitcircle: Sorry. Cannot find small circle fitting better than great circle.\n");
- for (i = 0; i < 3; i++) scpole[i] = gcpole[i];
- return(-1.0);
- }
- /* Get here when temppole points to a minimum bracketed by a and b. */
-
- for (i = 0; i < 3; i++) oldpole[i] = temppole[i];
- oldfit = fit;
-
- /* Now, while not converged, take golden section of wider interval. */
- length_ab = d_acos (dot3v (a, b));
- length_aold = d_acos (dot3v (a, oldpole));
- length_bold = d_acos (dot3v (b, oldpole));
- do {
- if (length_aold > length_bold) {
- /* Section a_old */
- for (i = 0; i < 3; i++) temppole[i] = (0.38197*a[i] + 0.61803*oldpole[i]);
- normalize3v(temppole);
- fit = circle_misfit(data, ndata, temppole, norm, work, &circle_distance);
- if (fit < oldfit) {
- /* Improvement. b = oldpole, oldpole = temppole */
- for (i = 0; i < 3; i++) {
- b[i] = oldpole[i];
- oldpole[i] = temppole[i];
- }
- oldfit = fit;
- }
- else {
- /* Not improved. a = temppole */
- for (i = 0; i < 3; i++) a[i] = temppole[i];
- }
- }
- else {
- /* Section b_old */
- for (i = 0; i < 3; i++) temppole[i] = (0.38197*b[i] + 0.61803*oldpole[i]);
- normalize3v(temppole);
- fit = circle_misfit(data, ndata, temppole, norm, work, &circle_distance);
- if (fit < oldfit) {
- /* Improvement. a = oldpole, oldpole = temppole */
- for (i = 0; i < 3; i++) {
- a[i] = oldpole[i];
- oldpole[i] = temppole[i];
- }
- oldfit = fit;
- }
- else {
- /* Not improved. b = temppole */
- for (i = 0; i < 3; i++) b[i] = temppole[i];
- }
- }
- length_ab = d_acos (dot3v (a, b));
- length_aold = d_acos (dot3v (a, oldpole));
- length_bold = d_acos (dot3v (b, oldpole));
- } while (length_ab > 0.0001); /* 1 milliradian = 0.05 degree */
-
- for (i = 0; i < 3; i++) scpole[i] = oldpole[i];
- return(R2D * circle_distance);
- }
-
- double circle_misfit(data, ndata, pole, norm, work, circle_distance)
- struct DATA *data;
- int ndata;
- double *pole, *work, *circle_distance;
- int norm;
- {
- /* Find the L(norm) misfit between a small circle through
- center with pole pole. Return misfit in radians. */
-
- double distance, delta_distance, misfit = 0.0;
- int i;
-
- /* At first, I thought we could use the center to define
- circle_dist = distance between pole and center.
- Then sum over data {dist[i] - circle_dist}.
- But it turns out that if the data are tightly
- curved, so that they are on a small circle
- within a few degrees of the pole, then the
- center point is not on the small circle, and
- we cannot use it. So, we first have to fit
- the circle_dist correctly: */
-
- if (norm == 1) {
- for (i = 0; i < ndata; i++) work[i] = d_acos (dot3v (&data[i].x[0], pole));
- qsort((char *)work, ndata, sizeof(double), comp_double_asc);
- if (ndata%2)
- *circle_distance = work[ndata/2];
- else
- *circle_distance = 0.5 * (work[(ndata/2)-1] + work[ndata/2]);
- }
- else {
- *circle_distance = 0.0;
- for (i = 0; i < ndata; i++) *circle_distance += d_acos (dot3v (&data[i].x[0], pole));
- *circle_distance /= ndata;
- }
-
- /* Now do each data point: */
-
- for (i = 0; i < ndata; i++) {
- distance = d_acos (dot3v (&data[i].x[0], pole));
- delta_distance = fabs(*circle_distance - distance);
- misfit += ((norm == 1) ? delta_distance : delta_distance * delta_distance);
- }
- return (norm == 1) ? misfit : sqrt (misfit);
- }
-