Add support for errors with a range of columns
[survex.git] / src / extend.c
blobe9911517f8652a9228056f8795a3f429c4804390
1 /* extend.c
2 * Produce an extended elevation
3 * Copyright (C) 1995-2002,2005,2010,2011,2013,2014,2016 Olly Betts
4 * Copyright (C) 2004,2005 John Pybus
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
25 #include <float.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
30 #include "cmdline.h"
31 #include "debug.h"
32 #include "filelist.h"
33 #include "filename.h"
34 #include "hash.h"
35 #include "img_hosted.h"
36 #include "message.h"
37 #include "useful.h"
39 /* To save memory we should probably use the prefix hash for the prefix on
40 * point labels (FIXME) */
42 typedef struct stn {
43 const char *label;
44 int flags;
45 const struct stn *next;
46 } stn;
48 typedef struct POINT {
49 img_point p;
50 double X;
51 const stn *stns;
52 unsigned int order;
53 char dir;
54 char fDone;
55 char fBroken;
56 struct POINT *next;
57 } point;
59 typedef struct LEG {
60 point *fr, *to;
61 const char *prefix;
62 char dir;
63 char fDone;
64 char broken;
65 int flags;
66 struct LEG *next;
67 } leg;
69 /* Values for leg.broken: */
70 #define BREAK_FR 0x01
71 #define BREAK_TO 0x02
73 /* Values for point.dir and leg.dir: */
74 #define ELEFT 0x01
75 #define ERIGHT 0x02
76 #define ESWAP 0x04
78 static point headpoint = {{0, 0, 0}, 0, NULL, 0, 0, 0, 0, NULL};
80 static leg headleg = {NULL, NULL, NULL, 0, 0, 0, 0, NULL};
82 static img *pimg_out;
84 static int show_breaks = 0;
86 static void do_stn(point *, double, const char *, int, int);
88 typedef struct pfx {
89 const char *label;
90 struct pfx *next;
91 } pfx;
93 static pfx **htab;
95 #define HTAB_SIZE 0x2000
97 static const char *
98 find_prefix(const char *prefix)
100 pfx *p;
101 int hash;
103 SVX_ASSERT(prefix);
105 hash = hash_string(prefix) & (HTAB_SIZE - 1);
106 for (p = htab[hash]; p; p = p->next) {
107 if (strcmp(prefix, p->label) == 0) return p->label;
110 p = osnew(pfx);
111 p->label = osstrdup(prefix);
112 p->next = htab[hash];
113 htab[hash] = p;
115 return p->label;
118 static point *
119 find_point(const img_point *pt)
121 point *p;
122 for (p = headpoint.next; p != NULL; p = p->next) {
123 if (pt->x == p->p.x && pt->y == p->p.y && pt->z == p->p.z) {
124 return p;
128 p = osmalloc(ossizeof(point));
129 p->p = *pt;
130 p->X = HUGE_VAL;
131 p->stns = NULL;
132 p->order = 0;
133 p->dir = 0;
134 p->fDone = 0;
135 p->fBroken = 0;
136 p->next = headpoint.next;
137 headpoint.next = p;
138 return p;
141 static void
142 add_leg(point *fr, point *to, const char *prefix, int flags)
144 leg *l;
145 fr->order++;
146 to->order++;
147 l = osmalloc(ossizeof(leg));
148 l->fr = fr;
149 l->to = to;
150 if (prefix)
151 l->prefix = find_prefix(prefix);
152 else
153 l->prefix = NULL;
154 l->next = headleg.next;
155 l->dir = 0;
156 l->fDone = 0;
157 l->broken = 0;
158 l->flags = flags;
159 headleg.next = l;
162 static void
163 add_label(point *p, const char *label, int flags)
165 stn *s = osnew(stn);
166 s->label = osstrdup(label);
167 s->flags = flags;
168 s->next = p->stns;
169 p->stns = s;
172 /* Read in config file */
175 /* lifted from img.c Should be put somewhere common? JPNP*/
176 static char *
177 getline_alloc(FILE *fh, size_t ilen)
179 int ch;
180 size_t i = 0;
181 size_t len = ilen;
182 char *buf = xosmalloc(len);
183 if (!buf) return NULL;
185 ch = GETC(fh);
186 while (ch != '\n' && ch != '\r' && ch != EOF) {
187 buf[i++] = ch;
188 if (i == len - 1) {
189 char *p;
190 len += len;
191 p = xosrealloc(buf, len);
192 if (!p) {
193 osfree(buf);
194 return NULL;
196 buf = p;
198 ch = GETC(fh);
200 if (ch == '\n' || ch == '\r') {
201 int otherone = ch ^ ('\n' ^ '\r');
202 ch = GETC(fh);
203 /* if it's not the other eol character, put it back */
204 if (ch != otherone) ungetc(ch, fh);
206 buf[i++] = '\0';
207 return buf;
210 static int lineno = 0;
211 static point *start = NULL;
213 static char*
214 delimword(char *ln, char** lr)
216 char *le;
218 while (*ln == ' ' || *ln == '\t' || *ln == '\n' || *ln == '\r')
219 ln++;
221 le = ln;
222 while (*le != ' ' && *le != '\t' && *le != '\n' && *le != '\r' && *le != ';' && *le != '\0')
223 le++;
225 if (*le == '\0' || *le == ';') {
226 *lr = le;
227 } else {
228 *lr = le + 1;
231 *le = '\0';
232 return ln;
235 static void
236 parseconfigline(const char *fnm, char *ln)
238 point *p;
239 const stn *s;
240 const stn *t;
241 leg *l;
242 char *lc = NULL;
244 ln = delimword(ln, &lc);
246 if (*ln == '\0') return;
248 if (strcmp(ln, "*start")==0) {
249 ln = delimword(lc, &lc);
250 if (*ln == 0)
251 /* TRANSLATORS: Here "station" is a survey station, not a train station. */
252 fatalerror_in_file(fnm, lineno, /*Expecting station name*/28);
253 for (p = headpoint.next; p != NULL; p = p->next) {
254 for (s = p->stns; s; s = s->next) {
255 if (strcmp(s->label, ln)==0) {
256 start = p;
257 /* TRANSLATORS: for extend: "extend" is starting to produce an extended elevation from station %s */
258 printf(msg(/*Starting from station %s*/512),ln);
259 putnl();
260 goto loopend;
264 /* TRANSLATORS: for extend: the user specified breaking a loop or
265 * changing extend direction at this station, but we didn’t find it in
266 * the 3d file */
267 warning_in_file(fnm, lineno, /*Failed to find station %s*/510, ln);
268 } else if (strcmp(ln, "*eleft")==0) {
269 char *ll = delimword(lc, &lc);
270 if (*ll == 0)
271 fatalerror_in_file(fnm, lineno, /*Expecting station name*/28);
272 ln = delimword(lc, &lc);
273 if (*ln == 0) {
274 /* One argument - look for station to switch at. */
275 for (p = headpoint.next; p != NULL; p = p->next) {
276 for (s = p->stns; s; s = s->next) {
277 if (strcmp(s->label, ll)==0) {
278 /* TRANSLATORS: for extend: */
279 printf(msg(/*Extending to the left from station %s*/513), ll);
280 putnl();
281 p->dir = ELEFT;
282 goto loopend;
286 warning_in_file(fnm, lineno, /*Failed to find station %s*/510, ll);
287 } else {
288 /* Two arguments - look for a specified leg. */
289 for (l = headleg.next; l; l=l->next) {
290 point * fr = l->fr;
291 point * to = l->to;
292 if (fr && to) {
293 for (s=fr->stns; s; s=s->next) {
294 int b = 0;
295 if (strcmp(s->label,ll)==0 || (strcmp(s->label, ln)==0 && (b = 1)) ) {
296 char * lr = (b ? ll : ln);
297 for (t=to->stns; t; t=t->next) {
298 if (strcmp(t->label,lr)==0) {
299 /* TRANSLATORS: for extend: */
300 printf(msg(/*Extending to the left from leg %s → %s*/515), s->label, t->label);
301 putnl();
302 l->dir = ELEFT;
303 goto loopend;
310 /* TRANSLATORS: for extend: the user specified breaking a loop or
311 * changing extend direction at this leg, but we didn’t find it in the
312 * 3d file */
313 warning_in_file(fnm, lineno, /*Failed to find leg %s → %s*/511, ll, ln);
315 } else if (strcmp(ln, "*eright")==0) {
316 char *ll = delimword(lc, &lc);
317 if (*ll == 0)
318 fatalerror_in_file(fnm, lineno, /*Expecting station name*/28);
319 ln = delimword(lc, &lc);
320 if (*ln == 0) {
321 /* One argument - look for station to switch at. */
322 for (p = headpoint.next; p != NULL; p = p->next) {
323 for (s = p->stns; s; s = s->next) {
324 if (strcmp(s->label, ll)==0) {
325 /* TRANSLATORS: for extend: */
326 printf(msg(/*Extending to the right from station %s*/514), ll);
327 putnl();
328 p->dir = ERIGHT;
329 goto loopend;
333 warning_in_file(fnm, lineno, /*Failed to find station %s*/510, ll);
334 } else {
335 /* Two arguments - look for a specified leg. */
336 for (l = headleg.next; l; l=l->next) {
337 point * fr = l->fr;
338 point * to = l->to;
339 if (fr && to) {
340 for (s=fr->stns; s; s=s->next) {
341 int b = 0;
342 if (strcmp(s->label,ll)==0 || (strcmp(s->label, ln)==0 && (b = 1)) ) {
343 char * lr = (b ? ll : ln);
344 for (t=to->stns; t; t=t->next) {
345 if (strcmp(t->label,lr)==0) {
346 /* TRANSLATORS: for extend: */
347 printf(msg(/*Extending to the right from leg %s → %s*/516), s->label, t->label);
348 putnl();
349 l->dir=ERIGHT;
350 goto loopend;
357 warning_in_file(fnm, lineno, /*Failed to find leg %s → %s*/511, ll, ln);
359 } else if (strcmp(ln, "*eswap")==0) {
360 char *ll = delimword(lc, &lc);
361 if (*ll == 0)
362 fatalerror_in_file(fnm, lineno, /*Expecting station name*/28);
363 ln = delimword(lc, &lc);
364 if (*ln == 0) {
365 /* One argument - look for station to switch at. */
366 for (p = headpoint.next; p != NULL; p = p->next) {
367 for (s = p->stns; s; s = s->next) {
368 if (strcmp(s->label, ll)==0) {
369 /* TRANSLATORS: for extend: */
370 printf(msg(/*Swapping extend direction from station %s*/519),ll);
371 putnl();
372 p->dir = ESWAP;
373 goto loopend;
377 warning_in_file(fnm, lineno, /*Failed to find station %s*/510, ll);
378 } else {
379 /* Two arguments - look for a specified leg. */
380 for (l = headleg.next; l; l=l->next) {
381 point * fr = l->fr;
382 point * to = l->to;
383 if (fr && to) {
384 for (s=fr->stns; s; s=s->next) {
385 int b = 0;
386 if (strcmp(s->label,ll)==0 || (strcmp(s->label, ln)==0 && (b = 1)) ) {
387 char * lr = (b ? ll : ln);
388 for (t=to->stns; t; t=t->next) {
389 if (strcmp(t->label,lr)==0) {
390 /* TRANSLATORS: for extend: */
391 printf(msg(/*Swapping extend direction from leg %s → %s*/520), s->label, t->label);
392 putnl();
393 l->dir = ESWAP;
394 goto loopend;
401 warning_in_file(fnm, lineno, /*Failed to find leg %s → %s*/511, ll, ln);
403 } else if (strcmp(ln, "*break")==0) {
404 char *ll = delimword(lc, &lc);
405 if (*ll == 0)
406 fatalerror_in_file(fnm, lineno, /*Expecting station name*/28);
407 ln = delimword(lc, &lc);
408 if (*ln == 0) {
409 /* One argument - look for specified station to break at. */
410 for (p = headpoint.next; p != NULL; p = p->next) {
411 for (s = p->stns; s; s = s->next) {
412 if (strcmp(s->label, ll)==0) {
413 /* TRANSLATORS: for extend: */
414 printf(msg(/*Breaking survey loop at station %s*/517), ll);
415 putnl();
416 p->fBroken = 1;
417 goto loopend;
421 warning_in_file(fnm, lineno, /*Failed to find station %s*/510, ll);
422 } else {
423 /* Two arguments - look for specified leg and disconnect it at the
424 * first station. */
425 for (l = headleg.next; l; l=l->next) {
426 point * fr = l->fr;
427 point * to = l->to;
428 if (fr && to) {
429 for (s=fr->stns; s; s=s->next) {
430 int b = 0;
431 if (strcmp(s->label,ll)==0 || (strcmp(s->label, ln)==0 && (b = 1)) ) {
432 char * lr = (b ? ll : ln);
433 for (t=to->stns; t; t=t->next) {
434 if (strcmp(t->label,lr)==0) {
435 /* TRANSLATORS: for extend: */
436 printf(msg(/*Breaking survey loop at leg %s → %s*/518), s->label, t->label);
437 putnl();
438 l->broken = (b ? BREAK_TO : BREAK_FR);
439 goto loopend;
446 warning_in_file(fnm, lineno, /*Failed to find leg %s → %s*/511, ll, ln);
448 } else {
449 fatalerror_in_file(fnm, lineno, /*Unknown command “%s”*/12, ln);
451 loopend:
452 ln = delimword(lc, &lc);
453 if (*ln != 0) {
454 fatalerror_in_file(fnm, lineno, /*End of line not blank*/15);
455 /* FIXME: give ln as context? */
459 static const struct option long_opts[] = {
460 /* const char *name; int has_arg (0 no_argument, 1 required_*, 2 optional_*); int *flag; int val; */
461 {"survey", required_argument, 0, 's'},
462 {"specfile", required_argument, 0, 'p'},
463 {"show-breaks", no_argument, 0, 'b' },
464 {"help", no_argument, 0, HLP_HELP},
465 {"version", no_argument, 0, HLP_VERSION},
466 {0, 0, 0, 0}
469 #define short_opts "s:p:b"
471 static struct help_msg help[] = {
472 /* <-- */
473 {HLP_ENCODELONG(0), /*only load the sub-survey with this prefix*/199, 0},
474 /* TRANSLATORS: --help output for extend --specfile option */
475 {HLP_ENCODELONG(1), /*.espec file to control extending*/90, 0},
476 /* TRANSLATORS: --help output for extend --show-breaks option */
477 {HLP_ENCODELONG(2), /*show breaks with surface survey legs in output*/91, 0},
478 {0, 0, 0}
481 static point *
482 pick_start_stn(void)
484 point * best = NULL;
485 double zMax = -DBL_MAX;
486 point *p;
488 /* Start at the highest entrance with some legs attached. */
489 for (p = headpoint.next; p != NULL; p = p->next) {
490 if (p->order > 0 && p->p.z > zMax) {
491 const stn *s;
492 for (s = p->stns; s; s = s->next) {
493 if (s->flags & img_SFLAG_ENTRANCE) {
494 zMax = p->p.z;
495 return p;
500 if (best) return best;
502 /* If no entrances with legs, start at the highest 1-node. */
503 for (p = headpoint.next; p != NULL; p = p->next) {
504 if (p->order == 1 && p->p.z > zMax) {
505 best = p;
506 zMax = p->p.z;
509 if (best) return best;
511 /* of course we may have no 1-nodes... */
512 for (p = headpoint.next; p != NULL; p = p->next) {
513 if (p->order != 0 && p->p.z > zMax) {
514 best = p;
515 zMax = p->p.z;
518 if (best) return best;
520 /* There are no legs - just pick the highest station... */
521 for (p = headpoint.next; p != NULL; p = p->next) {
522 if (p->p.z > zMax) {
523 best = p;
524 zMax = p->p.z;
527 return best;
531 main(int argc, char **argv)
533 const char *fnm_in, *fnm_out;
534 char *desc;
535 img_point pt;
536 int result;
537 point *fr = NULL, *to;
538 const char *survey = NULL;
539 const char *specfile = NULL;
540 img *pimg;
541 int have_xsect = 0;
543 msg_init(argv);
545 /* TRANSLATORS: Part of extend --help */
546 cmdline_set_syntax_message(/*INPUT_3D_FILE [OUTPUT_3D_FILE]*/267, 0, NULL);
547 cmdline_init(argc, argv, short_opts, long_opts, NULL, help, 1, 2);
548 while (1) {
549 int opt = cmdline_getopt();
550 if (opt == EOF) break;
551 switch (opt) {
552 case 'b':
553 show_breaks = 1;
554 break;
555 case 's':
556 survey = optarg;
557 break;
558 case 'p':
559 specfile = optarg;
560 break;
563 fnm_in = argv[optind++];
564 if (argv[optind]) {
565 fnm_out = argv[optind];
566 } else {
567 char * base_in = base_from_fnm(fnm_in);
568 char * base_out = osmalloc(strlen(base_in) + 8);
569 strcpy(base_out, base_in);
570 strcat(base_out, "_extend");
571 fnm_out = add_ext(base_out, EXT_SVX_3D);
572 osfree(base_in);
573 osfree(base_out);
576 /* try to open image file, and check it has correct header */
577 pimg = img_open_survey(fnm_in, survey);
578 if (pimg == NULL) fatalerror(img_error2msg(img_error()), fnm_in);
580 putnl();
581 puts(msg(/*Reading in data - please wait…*/105));
583 htab = osmalloc(ossizeof(pfx*) * HTAB_SIZE);
585 int i;
586 for (i = 0; i < HTAB_SIZE; ++i) htab[i] = NULL;
589 do {
590 result = img_read_item(pimg, &pt);
591 switch (result) {
592 case img_MOVE:
593 fr = find_point(&pt);
594 break;
595 case img_LINE:
596 if (!fr) {
597 result = img_BAD;
598 break;
600 to = find_point(&pt);
601 if (!(pimg->flags & (img_FLAG_SURFACE|img_FLAG_SPLAY)))
602 add_leg(fr, to, pimg->label, pimg->flags);
603 fr = to;
604 break;
605 case img_LABEL:
606 to = find_point(&pt);
607 add_label(to, pimg->label, pimg->flags);
608 break;
609 case img_BAD:
610 (void)img_close(pimg);
611 fatalerror(img_error2msg(img_error()), fnm_in);
612 break;
613 case img_XSECT:
614 have_xsect = 1;
615 break;
617 } while (result != img_STOP);
619 desc = osstrdup(pimg->title);
621 if (specfile) {
622 FILE *fs = NULL;
623 char *fnm_used;
624 /* TRANSLATORS: for extend: */
625 printf(msg(/*Applying specfile: “%s”*/521), specfile);
626 putnl();
627 fs = fopenWithPthAndExt("", specfile, NULL, "r", &fnm_used);
628 if (fs == NULL) fatalerror(/*Couldn’t open file “%s”*/24, specfile);
629 while (!feof(fs)) {
630 char *lbuf = getline_alloc(fs, 32);
631 lineno++;
632 if (!lbuf)
633 fatalerror_in_file(fnm_used, lineno, /*Error reading file*/18);
634 parseconfigline(fnm_used, lbuf);
635 osfree(lbuf);
637 osfree(fnm_used);
640 if (start == NULL) {
641 /* *start wasn't specified in specfile. */
642 start = pick_start_stn();
643 if (!start) fatalerror(/*No survey data*/43);
646 /* TRANSLATORS: for extend:
647 * Used to tell the user that a file is being written - %s is the filename
649 printf(msg(/*Writing %s…*/522), fnm_out);
650 putnl();
651 pimg_out = img_open_write(fnm_out, desc, img_FFLAG_EXTENDED);
653 /* Only does single connected component currently. */
654 do_stn(start, 0.0, NULL, ERIGHT, 0);
656 if (have_xsect) {
657 img_rewind(pimg);
658 /* Read ahead on pimg before writing pimg_out so we find out if an
659 * img_XSECT_END comes next. */
660 char * label = NULL;
661 int flags = 0;
662 do {
663 result = img_read_item(pimg, &pt);
664 if (result == img_XSECT || result == img_XSECT_END) {
665 if (label) {
666 if (result == img_XSECT_END)
667 flags |= img_XFLAG_END;
668 img_write_item(pimg_out, img_XSECT, flags, label, 0, 0, 0);
669 osfree(label);
670 label = NULL;
673 if (result == img_XSECT) {
674 label = osstrdup(pimg->label);
675 flags = pimg->flags;
676 pimg_out->l = pimg->l;
677 pimg_out->r = pimg->r;
678 pimg_out->u = pimg->u;
679 pimg_out->d = pimg->d;
681 } while (result != img_STOP);
684 (void)img_close(pimg);
686 if (!img_close(pimg_out)) {
687 (void)remove(fnm_out);
688 fatalerror(img_error2msg(img_error()), fnm_out);
691 return EXIT_SUCCESS;
694 static int adjust_direction(int dir, int by) {
695 if (by == ESWAP)
696 return dir ^ (ELEFT|ERIGHT);
697 if (by)
698 return by;
699 return dir;
702 static void
703 do_stn(point *p, double X, const char *prefix, int dir, int labOnly)
705 leg *l, *lp;
706 double dX;
707 const stn *s;
708 int odir = dir;
709 int try_all;
711 for (s = p->stns; s; s = s->next) {
712 img_write_item(pimg_out, img_LABEL, s->flags, s->label, X, 0, p->p.z);
714 if (show_breaks && p->X != HUGE_VAL && p->X != X) {
715 /* Draw "surface" leg between broken stations. */
716 img_write_item(pimg_out, img_MOVE, 0, NULL, p->X, 0, p->p.z);
717 img_write_item(pimg_out, img_LINE, img_FLAG_SURFACE, NULL, X, 0, p->p.z);
719 p->X = X;
720 if (labOnly || p->fBroken) {
721 return;
724 /* It's better to follow legs along a survey, so make two passes and only
725 * follow legs in the same survey for the first pass.
727 for (try_all = 0; try_all != 2; ++try_all) {
728 lp = &headleg;
729 for (l = lp->next; l; lp = l, l = lp->next) {
730 dir = odir;
731 if (l->fDone) {
732 /* this case happens iff a recursive call causes the next leg to be
733 * removed, leaving our next pointing to a leg which has been dealt
734 * with... */
735 continue;
737 if (!try_all && l->prefix != prefix) {
738 continue;
740 if (l->to == p) {
741 if (l->broken & BREAK_TO) continue;
742 lp->next = l->next;
743 /* adjust direction of extension if necessary */
744 dir = adjust_direction(dir, l->to->dir);
745 dir = adjust_direction(dir, l->dir);
747 dX = hypot(l->fr->p.x - l->to->p.x, l->fr->p.y - l->to->p.y);
748 if (dir == ELEFT) dX = -dX;
749 img_write_item(pimg_out, img_MOVE, 0, NULL, X + dX, 0, l->fr->p.z);
750 img_write_item(pimg_out, img_LINE, l->flags, l->prefix,
751 X, 0, l->to->p.z);
752 l->fDone = 1;
753 do_stn(l->fr, X + dX, l->prefix, dir, (l->broken & BREAK_FR));
754 l = lp;
755 } else if (l->fr == p) {
756 if (l->broken & BREAK_FR) continue;
757 lp->next = l->next;
758 /* adjust direction of extension if necessary */
759 dir = adjust_direction(dir, l->fr->dir);
760 dir = adjust_direction(dir, l->dir);
762 dX = hypot(l->fr->p.x - l->to->p.x, l->fr->p.y - l->to->p.y);
763 if (dir == ELEFT) dX = -dX;
764 img_write_item(pimg_out, img_MOVE, 0, NULL, X, 0, l->fr->p.z);
765 img_write_item(pimg_out, img_LINE, l->flags, l->prefix,
766 X + dX, 0, l->to->p.z);
767 l->fDone = 1;
768 do_stn(l->to, X + dX, l->prefix, dir, (l->broken & BREAK_TO));
769 l = lp;