Eliminate EXPLICIT_FIXED_FLAG option
[survex.git] / src / model.cc
blob0a17f1c05d13689f48d6055f53af851a6680d54c
1 //
2 // model.cc
3 //
4 // Cave survey model.
5 //
6 // Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7 // Copyright (C) 2001-2024 Olly Betts
8 // Copyright (C) 2005 Martin Green
9 //
10 // This program is free software; you can redistribute it and/or modify
11 // it under the terms of the GNU General Public License as published by
12 // the Free Software Foundation; either version 2 of the License, or
13 // (at your option) any later version.
15 // This program is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
20 // You should have received a copy of the GNU General Public License
21 // along with this program; if not, write to the Free Software
22 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 #include <config.h>
27 #include "model.h"
29 #include "img_hosted.h"
30 #include "useful.h"
32 #include <cfloat>
33 #include <map>
35 using namespace std;
37 int Model::Load(const wxString& file, const wxString& prefix)
39 // Load the processed survey data.
40 img* survey = img_read_stream_survey(wxFopen(file, wxT("rb")),
41 fclose,
42 file.c_str(),
43 prefix.utf8_str());
44 if (!survey) {
45 return img_error2msg(img_error());
48 m_IsExtendedElevation = survey->is_extended_elevation;
50 // Create a list of all the leg vertices, counting them and finding the
51 // extent of the survey at the same time.
53 m_NumFixedPts = 0;
54 m_NumExportedPts = 0;
55 m_NumEntrances = 0;
56 m_HasUndergroundLegs = false;
57 m_HasSplays = false;
58 m_HasDupes = false;
59 m_HasSurfaceLegs = false;
60 m_HasErrorInformation = false;
62 // FIXME: discard existing presentation? ask user about saving if we do!
64 // Delete any existing list entries.
65 m_Labels.clear();
67 double xmin = DBL_MAX;
68 double xmax = -DBL_MAX;
69 double ymin = DBL_MAX;
70 double ymax = -DBL_MAX;
71 double zmin = DBL_MAX;
72 double zmax = -DBL_MAX;
74 m_DepthMin = DBL_MAX;
75 double depthmax = -DBL_MAX;
77 m_DateMin = INT_MAX;
78 int datemax = -1;
79 complete_dateinfo = true;
81 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
82 traverses[f].clear();
84 tubes.clear();
86 // Ultimately we probably want different types (subclasses perhaps?) for
87 // underground and surface data, so we don't need to store LRUD for surface
88 // stuff.
89 traverse * current_traverse = NULL;
90 vector<XSect> * current_tube = NULL;
92 map<wxString, LabelInfo *> labelmap;
93 list<LabelInfo*>::const_iterator last_mapped_label = m_Labels.begin();
95 int result;
96 img_point prev_pt = {0,0,0};
97 bool current_polyline_is_surface = false;
98 int current_flags = 0;
99 int current_style = 0;
100 string current_label;
101 bool pending_move = false;
102 // When legs within a traverse have different surface/splay/duplicate
103 // flags, we split it into contiguous traverses of each flag combination,
104 // but we need to track these so we can assign the error statistics to all
105 // of them. So we keep counts of how many of each combination we've
106 // generated for the current traverse.
107 size_t n_traverses[8];
108 memset(n_traverses, 0, sizeof(n_traverses));
109 do {
110 #if 0
111 if (++items % 200 == 0) {
112 long pos = ftell(survey->fh);
113 int progress = int((double(pos) / double(file_size)) * 100.0);
114 // SetProgress(progress);
116 #endif
118 img_point pt;
119 result = img_read_item(survey, &pt);
120 switch (result) {
121 case img_MOVE:
122 memset(n_traverses, 0, sizeof(n_traverses));
123 pending_move = true;
124 prev_pt = pt;
125 break;
127 case img_LINE: {
128 // Update survey extents.
129 if (pt.x < xmin) xmin = pt.x;
130 if (pt.x > xmax) xmax = pt.x;
131 if (pt.y < ymin) ymin = pt.y;
132 if (pt.y > ymax) ymax = pt.y;
133 if (pt.z < zmin) zmin = pt.z;
134 if (pt.z > zmax) zmax = pt.z;
136 int date = survey->days1;
137 if (date != -1) {
138 date += (survey->days2 - date) / 2;
139 if (date < m_DateMin) m_DateMin = date;
140 if (date > datemax) datemax = date;
141 } else {
142 complete_dateinfo = false;
145 int flags = survey->flags &
146 (img_FLAG_SURFACE|img_FLAG_SPLAY|img_FLAG_DUPLICATE);
147 bool is_surface = (flags & img_FLAG_SURFACE);
148 bool is_splay = (flags & img_FLAG_SPLAY);
149 bool is_dupe = (flags & img_FLAG_DUPLICATE);
151 if (!is_surface) {
152 if (pt.z < m_DepthMin) m_DepthMin = pt.z;
153 if (pt.z > depthmax) depthmax = pt.z;
155 if (is_splay)
156 m_HasSplays = true;
157 if (is_dupe)
158 m_HasDupes = true;
159 if (pending_move ||
160 current_flags != flags ||
161 current_label != survey->label ||
162 current_style != survey->style) {
163 if (!current_polyline_is_surface && current_traverse) {
164 //FixLRUD(*current_traverse);
167 ++n_traverses[flags];
168 // Start new traverse (surface or underground).
169 if (is_surface) {
170 m_HasSurfaceLegs = true;
171 } else {
172 m_HasUndergroundLegs = true;
173 // The previous point was at a surface->ug transition.
174 if (current_polyline_is_surface) {
175 if (prev_pt.z < m_DepthMin) m_DepthMin = prev_pt.z;
176 if (prev_pt.z > depthmax) depthmax = prev_pt.z;
179 traverses[flags].push_back(traverse(survey->label));
180 current_traverse = &traverses[flags].back();
181 current_traverse->flags = survey->flags;
182 current_traverse->style = survey->style;
184 current_polyline_is_surface = is_surface;
185 current_flags = flags;
186 current_label = survey->label;
187 current_style = survey->style;
189 if (pending_move) {
190 // Update survey extents. We only need to do this if
191 // there's a pending move, since for a surface <->
192 // underground transition, we'll already have handled
193 // this point.
194 if (prev_pt.x < xmin) xmin = prev_pt.x;
195 if (prev_pt.x > xmax) xmax = prev_pt.x;
196 if (prev_pt.y < ymin) ymin = prev_pt.y;
197 if (prev_pt.y > ymax) ymax = prev_pt.y;
198 if (prev_pt.z < zmin) zmin = prev_pt.z;
199 if (prev_pt.z > zmax) zmax = prev_pt.z;
202 current_traverse->push_back(PointInfo(prev_pt));
205 current_traverse->push_back(PointInfo(pt, date));
207 prev_pt = pt;
208 pending_move = false;
209 break;
212 case img_LABEL: {
213 wxString s(survey->label, wxConvUTF8);
214 if (s.empty()) {
215 // If label isn't valid UTF-8 then this conversion will
216 // give an empty string. In this case, assume that the
217 // label is CP1252 (the Microsoft superset of ISO8859-1).
218 static wxCSConv ConvCP1252(wxFONTENCODING_CP1252);
219 s = wxString(survey->label, ConvCP1252);
220 if (s.empty()) {
221 // Or if that doesn't work (ConvCP1252 doesn't like
222 // strings with some bytes in) let's just go for
223 // ISO8859-1.
224 s = wxString(survey->label, wxConvISO8859_1);
227 int flags = (survey->flags & LFLAG_IMG_MASK);
228 LabelInfo* label = new LabelInfo(pt, s, flags);
229 if (label->IsEntrance()) {
230 m_NumEntrances++;
232 if (label->IsFixedPt()) {
233 m_NumFixedPts++;
235 if (label->IsExportedPt()) {
236 m_NumExportedPts++;
238 m_Labels.push_back(label);
239 break;
242 case img_XSECT: {
243 if (!current_tube) {
244 // Start new current_tube.
245 tubes.push_back(vector<XSect>());
246 current_tube = &tubes.back();
249 LabelInfo * lab;
250 wxString label(survey->label, wxConvUTF8);
251 map<wxString, LabelInfo *>::const_iterator p;
252 p = labelmap.find(label);
253 if (p != labelmap.end()) {
254 lab = p->second;
255 } else {
256 // Initialise labelmap lazily - we may have no
257 // cross-sections.
258 list<LabelInfo*>::const_iterator i;
259 if (labelmap.empty()) {
260 i = m_Labels.begin();
261 } else {
262 i = last_mapped_label;
263 ++i;
265 while (i != m_Labels.end() && (*i)->GetText() != label) {
266 labelmap[(*i)->GetText()] = *i;
267 ++i;
269 last_mapped_label = i;
270 if (i == m_Labels.end()) {
271 // Unattached cross-section - ignore for now.
272 printf("unattached cross-section\n");
273 if (current_tube->size() <= 1)
274 tubes.resize(tubes.size() - 1);
275 current_tube = NULL;
276 if (!m_Labels.empty())
277 --last_mapped_label;
278 break;
280 lab = *i;
281 labelmap[label] = lab;
284 int date = survey->days1;
285 if (date != -1) {
286 date += (survey->days2 - date) / 2;
287 if (date < m_DateMin) m_DateMin = date;
288 if (date > datemax) datemax = date;
291 current_tube->emplace_back(lab, date, survey->l, survey->r, survey->u, survey->d);
292 break;
295 case img_XSECT_END:
296 // Finish off current_tube.
297 // If there's only one cross-section in the tube, just
298 // discard it for now. FIXME: we should handle this
299 // when we come to skinning the tubes.
300 if (current_tube && current_tube->size() <= 1)
301 tubes.resize(tubes.size() - 1);
302 current_tube = NULL;
303 break;
305 case img_ERROR_INFO: {
306 if (survey->E == 0.0) {
307 // Currently cavern doesn't spot all articulating traverses
308 // so we assume that any traverse with no error isn't part
309 // of a loop. FIXME: fix cavern!
310 break;
312 m_HasErrorInformation = true;
313 for (size_t f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
314 list<traverse>::reverse_iterator t = traverses[f].rbegin();
315 size_t n = n_traverses[f];
316 n_traverses[f] = 0;
317 while (n) {
318 assert(t != traverses[f].rend());
319 t->n_legs = survey->n_legs;
320 t->length = survey->length;
321 t->errors[traverse::ERROR_3D] = survey->E;
322 t->errors[traverse::ERROR_H] = survey->H;
323 t->errors[traverse::ERROR_V] = survey->V;
324 --n;
325 ++t;
328 break;
331 case img_BAD: {
332 m_Labels.clear();
334 // FIXME: Do we need to reset all these? - Olly
335 m_NumFixedPts = 0;
336 m_NumExportedPts = 0;
337 m_NumEntrances = 0;
338 m_HasUndergroundLegs = false;
339 m_HasSplays = false;
340 m_HasSurfaceLegs = false;
342 img_close(survey);
344 return img_error2msg(img_error());
347 default:
348 break;
350 } while (result != img_STOP);
352 if (!current_polyline_is_surface && current_traverse) {
353 //FixLRUD(*current_traverse);
356 // Finish off current_tube.
357 // If there's only one cross-section in the tube, just
358 // discard it for now. FIXME: we should handle this
359 // when we come to skinning the tubes.
360 if (current_tube && current_tube->size() <= 1)
361 tubes.resize(tubes.size() - 1);
363 m_separator = survey->separator;
364 m_Title = wxString(survey->title, wxConvUTF8);
365 m_DateStamp_numeric = survey->datestamp_numeric;
366 if (survey->cs) {
367 m_cs_proj = wxString(survey->cs, wxConvUTF8);
368 } else {
369 m_cs_proj = wxString();
371 if (strcmp(survey->datestamp, "?") == 0) {
372 /* TRANSLATORS: used a processed survey with no processing date/time info */
373 m_DateStamp = wmsg(/*Date and time not available.*/108);
374 } else if (survey->datestamp[0] == '@') {
375 const struct tm * tm = localtime(&m_DateStamp_numeric);
376 char buf[256];
377 /* TRANSLATORS: This is the date format string used to timestamp .3d
378 * files internally. Probably best to keep it the same for all
379 * translations. */
380 strftime(buf, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm);
381 m_DateStamp = wxString(buf, wxConvUTF8);
383 if (m_DateStamp.empty()) {
384 m_DateStamp = wxString(survey->datestamp, wxConvUTF8);
386 img_close(survey);
388 // Check we've actually loaded some legs or stations!
389 if (!m_HasUndergroundLegs && !m_HasSurfaceLegs && m_Labels.empty()) {
390 return (/*No survey data in 3d file “%s”*/202);
393 if (traverses[0].empty() &&
394 traverses[1].empty() &&
395 traverses[2].empty() &&
396 traverses[3].empty() &&
397 traverses[4].empty() &&
398 traverses[5].empty() &&
399 traverses[6].empty() &&
400 traverses[7].empty()) {
401 // No legs, so get survey extents from stations
402 list<LabelInfo*>::const_iterator i;
403 for (i = m_Labels.begin(); i != m_Labels.end(); ++i) {
404 if ((*i)->GetX() < xmin) xmin = (*i)->GetX();
405 if ((*i)->GetX() > xmax) xmax = (*i)->GetX();
406 if ((*i)->GetY() < ymin) ymin = (*i)->GetY();
407 if ((*i)->GetY() > ymax) ymax = (*i)->GetY();
408 if ((*i)->GetZ() < zmin) zmin = (*i)->GetZ();
409 if ((*i)->GetZ() > zmax) zmax = (*i)->GetZ();
413 m_Ext.assign(xmax - xmin, ymax - ymin, zmax - zmin);
415 if (datemax < m_DateMin) m_DateMin = datemax;
416 m_DateExt = datemax - m_DateMin;
418 // Centre the dataset around the origin.
419 CentreDataset(Vector3(xmin, ymin, zmin));
421 if (depthmax < m_DepthMin) {
422 m_DepthMin = 0;
423 m_DepthExt = 0;
424 } else {
425 m_DepthExt = depthmax - m_DepthMin;
426 m_DepthMin -= GetOffset().GetZ();
429 #if 0
430 printf("time to load = %.3f\n", (double)timer.Time());
431 #endif
433 return 0; // OK
436 void Model::CentreDataset(const Vector3& vmin)
438 // Centre the dataset around the origin.
440 m_Offset = vmin + (m_Ext * 0.5);
442 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
443 list<traverse>::iterator t = traverses[f].begin();
444 while (t != traverses[f].end()) {
445 assert(t->size() > 1);
446 vector<PointInfo>::iterator pos = t->begin();
447 while (pos != t->end()) {
448 Point & point = *pos++;
449 point -= m_Offset;
451 ++t;
455 list<LabelInfo*>::iterator lpos = m_Labels.begin();
456 while (lpos != m_Labels.end()) {
457 Point & point = **lpos++;
458 point -= m_Offset;
462 void
463 Model::do_prepare_tubes() const
465 // Fill in "right_bearing" for each cross-section.
466 for (auto&& tube : tubes) {
467 assert(tube.size() > 1);
468 Vector3 U[4];
469 XSect* prev_pt_v = NULL;
470 Vector3 last_right(1.0, 0.0, 0.0);
472 vector<XSect>::iterator i = tube.begin();
473 vector<XSect>::size_type segment = 0;
474 while (i != tube.end()) {
475 // get the coordinates of this vertex
476 XSect & pt_v = *i++;
478 bool cover_end = false;
480 Vector3 right, up;
482 const Vector3 up_v(0.0, 0.0, 1.0);
484 if (segment == 0) {
485 assert(i != tube.end());
486 // first segment
488 // get the coordinates of the next vertex
489 const XSect & next_pt_v = *i;
491 // calculate vector from this pt to the next one
492 Vector3 leg_v = next_pt_v - pt_v;
494 // obtain a vector in the LRUD plane
495 right = leg_v * up_v;
496 if (right.magnitude() == 0) {
497 right = last_right;
498 // Obtain a second vector in the LRUD plane,
499 // perpendicular to the first.
500 //up = right * leg_v;
501 up = up_v;
502 } else {
503 last_right = right;
504 up = up_v;
507 cover_end = true;
508 } else if (segment + 1 == tube.size()) {
509 // last segment
511 // Calculate vector from the previous pt to this one.
512 Vector3 leg_v = pt_v - *prev_pt_v;
514 // Obtain a horizontal vector in the LRUD plane.
515 right = leg_v * up_v;
516 if (right.magnitude() == 0) {
517 right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
518 // Obtain a second vector in the LRUD plane,
519 // perpendicular to the first.
520 //up = right * leg_v;
521 up = up_v;
522 } else {
523 last_right = right;
524 up = up_v;
527 cover_end = true;
528 } else {
529 assert(i != tube.end());
530 // Intermediate segment.
532 // Get the coordinates of the next vertex.
533 const XSect & next_pt_v = *i;
535 // Calculate vectors from this vertex to the
536 // next vertex, and from the previous vertex to
537 // this one.
538 Vector3 leg1_v = pt_v - *prev_pt_v;
539 Vector3 leg2_v = next_pt_v - pt_v;
541 // Obtain horizontal vectors perpendicular to
542 // both legs, then normalise and average to get
543 // a horizontal bisector.
544 Vector3 r1 = leg1_v * up_v;
545 Vector3 r2 = leg2_v * up_v;
546 r1.normalise();
547 r2.normalise();
548 right = r1 + r2;
549 if (right.magnitude() == 0) {
550 // This is the "mid-pitch" case...
551 right = last_right;
553 if (r1.magnitude() == 0) {
554 up = up_v;
556 // Rotate pitch section to minimise the
557 // "torsional stress" - FIXME: use
558 // triangles instead of rectangles?
559 int shift = 0;
560 double maxdotp = 0;
562 // Scale to unit vectors in the LRUD plane.
563 right.normalise();
564 up.normalise();
565 Vector3 vec = up - right;
566 for (int orient = 0; orient <= 3; ++orient) {
567 Vector3 tmp = U[orient] - prev_pt_v->GetPoint();
568 tmp.normalise();
569 double dotp = dot(vec, tmp);
570 if (dotp > maxdotp) {
571 maxdotp = dotp;
572 shift = orient;
575 if (shift) {
576 if (shift != 2) {
577 Vector3 temp(U[0]);
578 U[0] = U[shift];
579 U[shift] = U[2];
580 U[2] = U[shift ^ 2];
581 U[shift ^ 2] = temp;
582 } else {
583 swap(U[0], U[2]);
584 swap(U[1], U[3]);
587 #if 0
588 // Check that the above code actually permuted
589 // the vertices correctly.
590 shift = 0;
591 maxdotp = 0;
592 for (int j = 0; j <= 3; ++j) {
593 Vector3 tmp = U[j] - *prev_pt_v;
594 tmp.normalise();
595 double dotp = dot(vec, tmp);
596 if (dotp > maxdotp) {
597 maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
598 shift = j;
601 if (shift) {
602 printf("New shift = %d!\n", shift);
603 shift = 0;
604 maxdotp = 0;
605 for (int j = 0; j <= 3; ++j) {
606 Vector3 tmp = U[j] - *prev_pt_v;
607 tmp.normalise();
608 double dotp = dot(vec, tmp);
609 printf(" %d : %.8f\n", j, dotp);
612 #endif
613 } else {
614 up = up_v;
616 last_right = right;
619 // Scale to unit vectors in the LRUD plane.
620 right.normalise();
621 up.normalise();
623 double l = fabs(pt_v.GetL());
624 double r = fabs(pt_v.GetR());
625 double u = fabs(pt_v.GetU());
626 double d = fabs(pt_v.GetD());
628 // Produce coordinates of the corners of the LRUD "plane".
629 Vector3 v[4];
630 v[0] = pt_v.GetPoint() - right * l + up * u;
631 v[1] = pt_v.GetPoint() + right * r + up * u;
632 v[2] = pt_v.GetPoint() + right * r - up * d;
633 v[3] = pt_v.GetPoint() - right * l - up * d;
635 prev_pt_v = &pt_v;
636 U[0] = v[0];
637 U[1] = v[1];
638 U[2] = v[2];
639 U[3] = v[3];
641 // FIXME: Store rather than recomputing on each draw?
642 (void)cover_end;
644 pt_v.set_right_bearing(deg(atan2(right.GetX(), right.GetY())));
646 ++segment;
651 void
652 SurveyFilter::add(const wxString& name)
654 auto it = filters.lower_bound(name);
655 if (it != filters.end()) {
656 // It's invalid to add a survey which is already present.
657 assert(*it != name);
658 // Check if a survey prefixing name is visible.
659 if (name.StartsWith(*it) && name[it->size()] == separator) {
660 redundant_filters.insert(name);
661 return;
664 while (it != filters.begin()) {
665 --it;
666 const wxString& s = *it;
667 if (s.size() <= name.size()) break;
668 if (s.StartsWith(name) && s[name.size()] == separator) {
669 redundant_filters.insert(s);
670 it = filters.erase(it);
673 filters.insert(name);
676 void
677 SurveyFilter::remove(const wxString& name)
679 if (filters.erase(name) == 0) {
680 redundant_filters.erase(name);
681 return;
683 if (redundant_filters.empty()) {
684 return;
686 auto it = redundant_filters.upper_bound(name);
687 while (it != redundant_filters.begin()) {
688 --it;
689 // Check if a survey prefixed by name should be made visible.
690 const wxString& s = *it;
691 if (s.size() <= name.size()) {
692 break;
694 if (!(s.StartsWith(name) && s[name.size()] == separator))
695 break;
696 filters.insert(s);
697 it = redundant_filters.erase(it);
701 void
702 SurveyFilter::SetSeparator(wxChar separator_)
704 if (separator_ == separator) return;
706 separator = separator_;
708 if (filters.empty()) {
709 return;
712 // Move aside all the filters already set and re-add() them so they get
713 // split into redundant_filters appropriately.
714 std::set<wxString, std::greater<wxString>> old_filters;
715 std::set<wxString, std::greater<wxString>> old_redundant_filters;
716 swap(filters, old_filters);
717 swap(redundant_filters, old_redundant_filters);
718 for (auto& s : old_filters) {
719 add(s);
721 for (auto& s : old_redundant_filters) {
722 add(s);
726 bool
727 SurveyFilter::CheckVisible(const wxString& name) const
729 auto it = filters.lower_bound(name);
730 if (it == filters.end()) {
731 // There's no filter <= name so name is excluded.
732 return false;
734 if (*it == name) {
735 // Exact match.
736 return true;
738 // Check if a survey prefixing name is visible.
739 if (name.StartsWith(*it) && name[it->size()] == separator)
740 return true;
741 return false;