scem_main.c (5385B)
1 /* Copyright (C) 2025 |Méso|Star> (contact@meso-star.com) 2 * 3 * This program is free software: you can redismeteobute it and/or modify 4 * it under the terms of the GNU General Public License as published by 5 * the Free Software Foundation, either version 3 of the License, or 6 * (at your option) any later version. 7 * 8 * This program is dismeteobuted in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. */ 15 16 #define _XOPEN_SOURCE /* strptime support */ 17 #define _POSIX_C_SOURCE 200112L /* getopt support */ 18 19 #include "scem.h" 20 21 #include <rsys/rsys.h> 22 #include <rsys/cstr.h> 23 #include <rsys/mem_allocator.h> 24 25 #include <stdio.h> 26 #include <string.h> 27 #include <time.h> 28 #include <unistd.h> /* getopt */ 29 30 struct args { 31 struct scem_location pos; 32 struct tm time; /* In UTC+00:00 */ 33 enum scem_sun_algo algo; 34 int radian; /* Sun position in radians instead of degrees */ 35 int verbose; 36 int quit; 37 int no_dir_output; 38 int vec_output; 39 }; 40 static const struct args ARGS_DEFAULT = { 41 SCEM_LOCATION_NULL__, {0}, SCEM_SUN_MEEUS, 0, 0, 0, 0, 0 42 }; 43 44 /******************************************************************************* 45 * Helper functions 46 ******************************************************************************/ 47 static INLINE void 48 usage(FILE* stream) 49 { 50 fprintf(stream, 51 "usage: scem [-DhrVv] [-a algo] [-d utc_date] latitude longitude\n"); 52 } 53 54 static res_T 55 parse_algo(const char* str, enum scem_sun_algo* algo) 56 { 57 ASSERT(str && algo); 58 59 if(!strcmp(str, "meeus")) { 60 *algo = SCEM_SUN_MEEUS; 61 } else if(!strcmp(str, "psa")) { 62 *algo = SCEM_SUN_PSA; 63 } else { 64 return RES_BAD_ARG; 65 } 66 return RES_OK; 67 } 68 69 static res_T 70 parse_date(const char* str, struct tm* time) 71 { 72 char *p; 73 ASSERT(str && time); 74 75 p = strptime(str, "%Y-%m-%dT%H:%M:%S\n", time); 76 if(!p || *p != '\0') { /* strptime failed or trailing chars */ 77 return RES_BAD_ARG; 78 } else { 79 return RES_OK; 80 } 81 } 82 83 static res_T 84 parse_dbl 85 (const char* str, 86 const double min, 87 const double max, 88 double* out_dbl) /* In [min, max] */ 89 { 90 double dbl = 0; 91 res_T res = RES_OK; 92 ASSERT(str && out_dbl && min <= max); 93 94 if((res = cstr_to_double(str, &dbl)) != RES_OK) return res; 95 if(dbl < min || dbl > max) { 96 fprintf(stderr, "scem: value not in range: %g [%g %g]\n", dbl, min, max); 97 return RES_BAD_ARG; 98 } 99 100 *out_dbl = dbl; 101 return RES_OK; 102 } 103 104 static res_T 105 get_current_date(struct tm* date) 106 { 107 time_t epoch; 108 ASSERT(date); 109 110 epoch = time(NULL); 111 if(epoch == (time_t)-1) { 112 fprintf(stderr, "scem: error in querying local time\n"); 113 return RES_BAD_ARG; 114 } 115 116 *date = *gmtime(&epoch); 117 return RES_OK; 118 } 119 120 static res_T 121 args_init(struct args* args, int argc, char** argv) 122 { 123 int opt = 0; 124 res_T res = RES_OK; 125 126 *args = ARGS_DEFAULT; 127 128 if((res = get_current_date(&args->time)) != RES_OK) goto error; 129 130 /* Don't process the last 2 args as options, they are lat and long */ 131 while((opt = getopt(argc - 2, argv, "a:Dd:hVvr")) != -1) { 132 switch(opt) { 133 case 'a': res = parse_algo(optarg, &args->algo); break; 134 case 'D': args->no_dir_output = 1; break; 135 case 'd': res = parse_date(optarg, &args->time); break; 136 case 'h': usage(stdout); args->quit = 1; goto exit; 137 case 'V': args->vec_output = 1; break; 138 case 'v': args->verbose = 1; break; 139 case 'r': args->radian = 1; break; 140 default: res = RES_BAD_ARG; break; 141 } 142 if(res != RES_OK) { 143 if(optarg) { 144 fprintf(stderr, "scem: invalid option argument '%s' -- '%c'\n", 145 optarg, opt); 146 } 147 goto error; 148 } 149 } 150 151 /* Is location missing? */ 152 if(argc - optind < 2) { res = RES_BAD_ARG; goto error; } 153 154 res = parse_dbl(argv[optind+0], -90, 90, &args->pos.latitude); 155 if(res != RES_OK) goto error; 156 res = parse_dbl(argv[optind+1], -180, 180, &args->pos.longitude); 157 if(res != RES_OK) goto error; 158 exit: 159 return res; 160 error: 161 usage(stderr); 162 goto exit; 163 } 164 165 /******************************************************************************* 166 * The program 167 ******************************************************************************/ 168 int 169 main(int argc, char** argv) 170 { 171 struct args args = ARGS_DEFAULT; 172 struct scem_sun_pos pos = SCEM_SUN_POS_NULL; 173 double sun_dir[3]; 174 int err = 0; 175 res_T res = RES_OK; 176 177 if((res = args_init(&args, argc, argv)) != RES_OK) goto error; 178 if(args.quit) goto exit; 179 180 if(args.verbose) fprintf(stderr, "%s", asctime(&args.time)); 181 182 res = scem_sun_position_from_earth(&args.time, &args.pos, args.algo, &pos); 183 if(res != RES_OK) { 184 fprintf(stderr, "scem: error in computing the position of sun -- %s\n", 185 res_to_cstr(res)); 186 goto error; 187 } 188 189 if(!args.no_dir_output) { 190 if(args.radian) { 191 printf("%g %g\n", pos.elevation, pos.azimuth); 192 } else { 193 printf("%g %g\n", MRAD2DEG(pos.elevation), MRAD2DEG(pos.azimuth)); 194 } 195 } 196 if(args.vec_output) { 197 res = scem_sun_position_to_sun_vector(&pos, sun_dir); 198 if(res != RES_OK) goto error; 199 fprintf(stderr, "%g %g %g\n", SPLIT3(sun_dir)); 200 } 201 202 exit: 203 CHK(mem_allocated_size() == 0); 204 return err; 205 error: 206 err = 1; 207 goto exit; 208 }