The Tiny HTM Library
|
00001 00007 #include <errno.h> 00008 #include <stdarg.h> 00009 #include <stdio.h> 00010 #include <stdlib.h> 00011 #include <string.h> 00012 #include <getopt.h> 00013 00014 #include "tinyhtm/geometry.h" 00015 #include "tinyhtm/tree.h" 00016 00017 #ifdef __cplusplus 00018 extern "C" { 00019 #endif 00020 00023 /* Should output be in JSON format or in IPAC SVC format? */ 00024 static int json = 0; 00025 /* Should the count be estimated, or determined exactly? */ 00026 static int estimate = 0; 00027 00028 /* Performs string escaping for the JSON/IPAC SVC formats. */ 00029 static const char * esc(const char *s) { 00030 static char buf[8192]; 00031 char *e = buf; 00032 if (s == NULL) { 00033 return "null"; 00034 } else { 00035 *e++ = '"'; 00036 for (; *s != '\0' && e < buf + (sizeof(buf) - 2); ++s) { 00037 switch (*s) { 00038 case '"': e[0] = '\\'; e[1] = '"'; e += 2; break; 00039 case '\\': e[0] = '\\'; e[1] = '\\'; e += 2; break; 00040 case '\b': e[0] = '\\'; e[1] = 'b'; e += 2; break; 00041 case '\f': e[0] = '\\'; e[1] = 'f'; e += 2; break; 00042 case '\n': e[0] = '\\'; e[1] = 'n'; e += 2; break; 00043 case '\r': e[0] = '\\'; e[1] = 'r'; e += 2; break; 00044 case '\t': e[0] = '\\'; e[1] = 't'; e += 2; break; 00045 default: 00046 if (*s > 0x1f && *s < 0x7f) { 00047 *e++ = *s; 00048 } 00049 break; 00050 } 00051 } 00052 if (*s != '\0' && e == buf + (sizeof(buf) - 2)) { 00053 strcpy(buf + (sizeof(buf) - 6), " ...\""); 00054 } else { 00055 e[0] = '"'; 00056 e[1] = '\0'; 00057 } 00058 } 00059 return buf; 00060 } 00061 00062 00063 static void err(const char *fmt, ...) 00064 { 00065 char buf[4095]; 00066 int len; 00067 00068 va_list ap; 00069 va_start(ap, fmt); 00070 len = vsnprintf(buf, sizeof(buf), fmt, ap); 00071 va_end(ap); 00072 if (len >= (int) sizeof(buf)) { 00073 strcpy(buf + (sizeof(buf) - 5), " ..."); 00074 } 00075 if (json) { 00076 printf("{\"stat\":\"ERROR\", \"msg\":%s}\n", esc(buf)); 00077 } else { 00078 printf("[struct stat=\"ERROR\", msg=%s]\n", esc(buf)); 00079 } 00080 fflush(stdout); 00081 exit(EXIT_FAILURE); 00082 } 00083 00084 00085 static double get_double(const char *s) 00086 { 00087 char *endptr; 00088 double d = strtod(s, &endptr); 00089 if (errno != 0 || endptr == s) { 00090 err("failed to convert argument `%s' to a double", s); 00091 } 00092 return d; 00093 } 00094 00095 00096 static void print_count(int64_t count) 00097 { 00098 if (json) { 00099 printf("{\"stat\":\"OK\", \"count\":%lld}\n", (long long) count); 00100 } else { 00101 printf("[struct stat=\"OK\", count=\"%lld\"]\n", (long long) count); 00102 } 00103 } 00104 00105 static void print_range(const struct htm_range *range) 00106 { 00107 if (json) { 00108 printf("{\"stat\":\"OK\", \"min\":%lld, \"max\":%lld}\n", 00109 (long long) range->min, (long long) range->max); 00110 } else { 00111 printf("[struct stat=\"OK\", min=\"%lld\", max=\"%lld\"]\n", 00112 (long long) range->min, (long long) range->max); 00113 } 00114 } 00115 00116 static void circle_count(const char * const treefile, 00117 const char * const datafile, 00118 char **argv) 00119 { 00120 struct htm_tree tree; 00121 struct htm_sc sc; 00122 struct htm_v3 cen; 00123 double r; 00124 enum htm_errcode ec; 00125 00126 ec = htm_sc_init(&sc, get_double(argv[0]), get_double(argv[1])); 00127 if (ec != HTM_OK) { 00128 err("Invalid circle center coordinates: %s", htm_errmsg(ec)); 00129 } 00130 ec = htm_sc_tov3(&cen, &sc); 00131 if (ec != HTM_OK) { 00132 err("Failed to convert spherical coordinates to a unit vector: %s", 00133 htm_errmsg(ec)); 00134 } 00135 r = get_double(argv[2]); 00136 ec = htm_tree_init(&tree, treefile, datafile); 00137 if (ec != HTM_OK) { 00138 err("Failed to load tree and/or data file: %s", htm_errmsg(ec)); 00139 } 00140 if (estimate != 0) { 00141 struct htm_range range = htm_tree_s2circle_range(&tree, &cen, r, &ec); 00142 htm_tree_destroy(&tree); 00143 if (ec != HTM_OK) { 00144 err("Failed to estimate points in circle: %s", htm_errmsg(ec)); 00145 } 00146 print_range(&range); 00147 } else { 00148 int64_t count = htm_tree_s2circle_count(&tree, &cen, r, &ec); 00149 htm_tree_destroy(&tree); 00150 if (ec != HTM_OK) { 00151 err("Failed to count points in circle: %s", htm_errmsg(ec)); 00152 } 00153 print_count(count); 00154 } 00155 } 00156 00157 00158 static void ellipse_count(const char * const treefile, 00159 const char * const datafile, 00160 char **argv) 00161 { 00162 struct htm_tree tree; 00163 struct htm_s2ellipse ellipse; 00164 struct htm_sc sc; 00165 struct htm_v3 cen; 00166 double a, b, angle; 00167 enum htm_errcode ec; 00168 00169 ec = htm_sc_init(&sc, get_double(argv[0]), get_double(argv[1])); 00170 if (ec != HTM_OK) { 00171 err("Invalid ellipse center coordinates: %s", htm_errmsg(ec)); 00172 } 00173 ec = htm_sc_tov3(&cen, &sc); 00174 if (ec != HTM_OK) { 00175 err("Failed to convert spherical coordinates to a unit vector: %s", 00176 htm_errmsg(ec)); 00177 } 00178 a = get_double(argv[2]); 00179 b = get_double(argv[3]); 00180 angle = get_double(argv[4]); 00181 ec = htm_s2ellipse_init2(&ellipse, &cen, a, b, angle); 00182 if (ec != HTM_OK) { 00183 err("Invalid ellipse parameters: %s", htm_errmsg(ec)); 00184 } 00185 ec = htm_tree_init(&tree, treefile, datafile); 00186 if (ec != HTM_OK) { 00187 err("Failed to load tree and/or data file: %s", htm_errmsg(ec)); 00188 } 00189 if (estimate != 0) { 00190 struct htm_range range = htm_tree_s2ellipse_range(&tree, &ellipse, &ec); 00191 htm_tree_destroy(&tree); 00192 if (ec != HTM_OK) { 00193 err("Failed to estimate points in ellipse: %s", htm_errmsg(ec)); 00194 } 00195 print_range(&range); 00196 } else { 00197 int64_t count = htm_tree_s2ellipse_count(&tree, &ellipse, &ec); 00198 htm_tree_destroy(&tree); 00199 if (ec != HTM_OK) { 00200 err("Failed to count points in ellipse: %s", htm_errmsg(ec)); 00201 } 00202 print_count(count); 00203 } 00204 } 00205 00206 00207 static void hull_count(const char * const treefile, 00208 const char * const datafile, 00209 const int argc, 00210 char **argv) 00211 { 00212 struct htm_tree tree; 00213 struct htm_s2cpoly *poly; 00214 struct htm_sc sc; 00215 struct htm_v3 *verts; 00216 int i; 00217 enum htm_errcode ec = HTM_OK; 00218 00219 verts = (struct htm_v3 *) malloc(sizeof(struct htm_v3) * (size_t) argc/2); 00220 if (verts == NULL) { 00221 err(htm_errmsg(HTM_ENOMEM)); 00222 } 00223 for (i = 0; i < argc/2; ++i) { 00224 ec = htm_sc_init(&sc, get_double(argv[2*i]), get_double(argv[2*i + 1])); 00225 if (ec != HTM_OK) { 00226 free(verts); 00227 err("Invalid vertex coordinates: %s", htm_errmsg(ec)); 00228 } 00229 ec = htm_sc_tov3(&verts[i], &sc); 00230 if (ec != HTM_OK) { 00231 free(verts); 00232 err("Failed to convert spherical coordinates to a unit vector: %s", 00233 htm_errmsg(ec)); 00234 } 00235 } 00236 poly = htm_s2cpoly_hull(verts, (size_t) i, &ec); 00237 if (poly == NULL || ec != HTM_OK) { 00238 free(verts); 00239 err("Failed to compute convex hull: %s", htm_errmsg(ec)); 00240 } 00241 free(verts); 00242 ec = htm_tree_init(&tree, treefile, datafile); 00243 if (ec != HTM_OK) { 00244 free(poly); 00245 err("Failed to load tree and/or data file: %s", htm_errmsg(ec)); 00246 } 00247 if (estimate != 0) { 00248 struct htm_range range = htm_tree_s2cpoly_range(&tree, poly, &ec); 00249 htm_tree_destroy(&tree); 00250 free(poly); 00251 if (ec != HTM_OK) { 00252 err("Failed to estimate points in hull: %s", htm_errmsg(ec)); 00253 } 00254 print_range(&range); 00255 } else { 00256 int64_t count = htm_tree_s2cpoly_count(&tree, poly, &ec); 00257 htm_tree_destroy(&tree); 00258 free(poly); 00259 if (ec != HTM_OK) { 00260 err("Failed to count points in hull: %s", htm_errmsg(ec)); 00261 } 00262 print_count(count); 00263 } 00264 } 00265 00266 00267 static void test_tree(const char * const treefile, 00268 const char * const datafile, 00269 char **argv) 00270 { 00271 struct htm_tree tree; 00272 double r; 00273 unsigned long long i; 00274 enum htm_errcode ec; 00275 00276 r = get_double(argv[0]); 00277 ec = htm_tree_init(&tree, treefile, datafile); 00278 if (ec != HTM_OK) { 00279 err("Failed to load tree and/or data file: %s", htm_errmsg(ec)); 00280 } 00281 for (i = 0; i < tree.count; ++i) { 00282 int64_t c = htm_tree_s2circle_count(&tree, &tree.entries[i].v, r, &ec); 00283 if (c != 1) { 00284 err("Circle of radius %g around %llu:(%.18g, %.18g, %.18g) " 00285 "contains %lld points (expecting 1).", r, i, 00286 tree.entries[i].v.x, tree.entries[i].v.y, tree.entries[i].v.z, 00287 (long long) c); 00288 } 00289 } 00290 htm_tree_destroy(&tree); 00291 } 00292 00293 00294 static void usage(const char *prog) 00295 { 00296 printf("%1$s [options] <file> circle <ra> <dec> <radius>\n" 00297 "\n" 00298 "%1$s [options] <file> ellipse <ra> <dec> <axis1> <axis2> <angle>\n" 00299 "\n" 00300 "%1$s [options] <file> hull <ra_1> <dec_1> <ra_2> <dec_2> ...<ra_n> <dec_n>\n" 00301 "\n" 00302 "%1$s [options] <file> test <radius>\n" 00303 "\n" 00304 " This utility counts the number of points from <file> that\n" 00305 "lie in the given spherical region. Note one particularly important\n" 00306 "option: --tree (-t). This option specifies a tree index file that\n" 00307 "can be used to greatly speed up point counting. Three different\n" 00308 "types of spherical region are supported:\n" 00309 "\n" 00310 " Circle\n" 00311 " A circle is specified by the spherical coordinates of its\n" 00312 " center and its radius. All parameters are assumed to be in\n" 00313 " degrees.\n" 00314 "\n" 00315 " Ellipse\n" 00316 " An ellipse is specified by the spherical coordinates of its\n" 00317 " center, two axis angles, and a position angle giving the\n" 00318 " the orientation (east of north) of the first axis. All\n" 00319 " parameters are assumed to be in degrees.\n" 00320 "\n" 00321 " Convex Hull\n" 00322 " A convex hull is given by a hemispherical set of at least\n" 00323 " 3 points. Points are specified by their spherical coordinates;\n" 00324 " coordinate values are assumed to be in degrees.\n" 00325 "\n" 00326 " Finally, the utility supports a tree file integrity test.\n" 00327 "This mode constructs circles of the given radius around every\n" 00328 "point in <file>, and checks that the point count for that circle\n" 00329 "is exactly one. Note that this is useful only when points are\n" 00330 "unique and one knows their minimum separation.\n" 00331 "\n" 00332 "== Options ====\n" 00333 "\n" 00334 "--help | -h : Prints usage information.\n" 00335 "--estimate | -e : Estimate count instead of determining\n" 00336 " the exact value. Has no effect unless\n" 00337 " used in conjunction with --tree.\n" 00338 "--json | -j : Print results in JSON format.\n" 00339 "--tree | -t <tree_file> : File name of tree index over\n" 00340 " the input points.\n", 00341 prog); 00342 } 00343 00344 00345 int main(int argc, char **argv) 00346 { 00347 char *treefile = NULL; 00348 char *datafile = NULL; 00349 00350 opterr = 0; 00351 while (1) { 00352 static struct option long_options[] = { 00353 { "help", no_argument, 0, 'h' }, 00354 { "json", no_argument, 0, 'j' }, 00355 { "estimate", no_argument, 0, 'e' }, 00356 { "tree", required_argument, 0, 't' }, 00357 { 0, 0, 0, 0 } 00358 }; 00359 int option_index = 0; 00360 int c = getopt_long(argc, argv, "+ehjt:", long_options, &option_index); 00361 if (c == -1) { 00362 break; /* no more options */ 00363 } 00364 switch (c) { 00365 case 'e': 00366 estimate = 1; 00367 break; 00368 case 'h': 00369 usage(argv[0]); 00370 return EXIT_SUCCESS; 00371 case 'j': 00372 json = 1; 00373 break; 00374 case 't': 00375 treefile = optarg; 00376 break; 00377 case '?': 00378 if (optopt == 't') { 00379 err("Option --tree/-t requires an argument. " 00380 "Pass --help for usage instructions"); 00381 } else { 00382 err("Unknown option. Pass --help for usage instructions"); 00383 } 00384 default: 00385 abort(); 00386 } 00387 } 00388 if (argc - optind < 2) { 00389 err("Missing arguments. Pass --help for usage instructions."); 00390 } 00391 datafile = argv[optind]; 00392 if (strcmp(argv[optind + 1], "circle") == 0) { 00393 optind += 2; 00394 if (argc - optind != 3) { 00395 err("Missing arguments. Pass --help for usage instructions."); 00396 } 00397 circle_count(treefile, datafile, argv + optind); 00398 } else if (strcmp(argv[optind + 1], "ellipse") == 0) { 00399 optind += 2; 00400 if (argc - optind != 5) { 00401 err("Missing arguments. Pass --help for usage instructions."); 00402 } 00403 ellipse_count(treefile, datafile, argv + optind); 00404 } else if (strcmp(argv[optind + 1], "hull") == 0) { 00405 optind += 2; 00406 if (argc - optind < 6 || (argc - optind) % 2 != 0) { 00407 err("Missing arguments. Pass --help for usage instructions."); 00408 } 00409 hull_count(treefile, datafile, argc - optind, argv + optind); 00410 } else if (strcmp(argv[optind + 1], "test") == 0) { 00411 optind += 2; 00412 if (argc - optind != 1) { 00413 err("Missing arguments. Pass --help for usage instructions."); 00414 } 00415 if (treefile == NULL) { 00416 err("Please specify a tree file with --tree (-t)."); 00417 } 00418 test_tree(treefile, datafile, argv + optind); 00419 if (json) { 00420 printf("{\"stat\":\"OK\"}\n"); 00421 } else { 00422 printf("[struct stat=\"OK\"]\n"); 00423 } 00424 fflush(stdout); 00425 return EXIT_SUCCESS; 00426 } else { 00427 err("Invalid region type '%s' - expecting 'circle', " 00428 "'ellipse', or 'hull'", argv[optind + 1]); 00429 } 00430 fflush(stdout); 00431 return EXIT_SUCCESS; 00432 } 00433 00436 #ifdef __cplusplus 00437 } 00438 #endif