Enable more show and hide actions in survey tree
[survex.git] / src / model.cc
blobb4ea2a8cf591ab811cfd20d7d4c0c9a52b0fa494
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 const static int img2aven_tab[] = {
38 #include "img2aven.h"
41 inline int
42 img2aven(int flags)
44 flags &= (sizeof(img2aven_tab) / sizeof(img2aven_tab[0]) - 1);
45 return img2aven_tab[flags];
48 int Model::Load(const wxString& file, const wxString& prefix)
50 // Load the processed survey data.
51 img* survey = img_read_stream_survey(wxFopen(file, wxT("rb")),
52 fclose,
53 file.c_str(),
54 prefix.utf8_str());
55 if (!survey) {
56 return img_error2msg(img_error());
59 m_IsExtendedElevation = survey->is_extended_elevation;
61 // Create a list of all the leg vertices, counting them and finding the
62 // extent of the survey at the same time.
64 m_NumFixedPts = 0;
65 m_NumExportedPts = 0;
66 m_NumEntrances = 0;
67 m_HasUndergroundLegs = false;
68 m_HasSplays = false;
69 m_HasDupes = false;
70 m_HasSurfaceLegs = false;
71 m_HasErrorInformation = false;
73 // FIXME: discard existing presentation? ask user about saving if we do!
75 // Delete any existing list entries.
76 m_Labels.clear();
78 double xmin = DBL_MAX;
79 double xmax = -DBL_MAX;
80 double ymin = DBL_MAX;
81 double ymax = -DBL_MAX;
82 double zmin = DBL_MAX;
83 double zmax = -DBL_MAX;
85 m_DepthMin = DBL_MAX;
86 double depthmax = -DBL_MAX;
88 m_DateMin = INT_MAX;
89 int datemax = -1;
90 complete_dateinfo = true;
92 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
93 traverses[f].clear();
95 tubes.clear();
97 // Ultimately we probably want different types (subclasses perhaps?) for
98 // underground and surface data, so we don't need to store LRUD for surface
99 // stuff.
100 traverse * current_traverse = NULL;
101 vector<XSect> * current_tube = NULL;
103 map<wxString, LabelInfo *> labelmap;
104 list<LabelInfo*>::const_iterator last_mapped_label = m_Labels.begin();
106 int result;
107 img_point prev_pt = {0,0,0};
108 bool current_polyline_is_surface = false;
109 int current_flags = 0;
110 int current_style = 0;
111 string current_label;
112 bool pending_move = false;
113 // When legs within a traverse have different surface/splay/duplicate
114 // flags, we split it into contiguous traverses of each flag combination,
115 // but we need to track these so we can assign the error statistics to all
116 // of them. So we keep counts of how many of each combination we've
117 // generated for the current traverse.
118 size_t n_traverses[8];
119 memset(n_traverses, 0, sizeof(n_traverses));
120 do {
121 #if 0
122 if (++items % 200 == 0) {
123 long pos = ftell(survey->fh);
124 int progress = int((double(pos) / double(file_size)) * 100.0);
125 // SetProgress(progress);
127 #endif
129 img_point pt;
130 result = img_read_item(survey, &pt);
131 switch (result) {
132 case img_MOVE:
133 memset(n_traverses, 0, sizeof(n_traverses));
134 pending_move = true;
135 prev_pt = pt;
136 break;
138 case img_LINE: {
139 // Update survey extents.
140 if (pt.x < xmin) xmin = pt.x;
141 if (pt.x > xmax) xmax = pt.x;
142 if (pt.y < ymin) ymin = pt.y;
143 if (pt.y > ymax) ymax = pt.y;
144 if (pt.z < zmin) zmin = pt.z;
145 if (pt.z > zmax) zmax = pt.z;
147 int date = survey->days1;
148 if (date != -1) {
149 date += (survey->days2 - date) / 2;
150 if (date < m_DateMin) m_DateMin = date;
151 if (date > datemax) datemax = date;
152 } else {
153 complete_dateinfo = false;
156 int flags = survey->flags &
157 (img_FLAG_SURFACE|img_FLAG_SPLAY|img_FLAG_DUPLICATE);
158 bool is_surface = (flags & img_FLAG_SURFACE);
159 bool is_splay = (flags & img_FLAG_SPLAY);
160 bool is_dupe = (flags & img_FLAG_DUPLICATE);
162 if (!is_surface) {
163 if (pt.z < m_DepthMin) m_DepthMin = pt.z;
164 if (pt.z > depthmax) depthmax = pt.z;
166 if (is_splay)
167 m_HasSplays = true;
168 if (is_dupe)
169 m_HasDupes = true;
170 if (pending_move ||
171 current_flags != flags ||
172 current_label != survey->label ||
173 current_style != survey->style) {
174 if (!current_polyline_is_surface && current_traverse) {
175 //FixLRUD(*current_traverse);
178 ++n_traverses[flags];
179 // Start new traverse (surface or underground).
180 if (is_surface) {
181 m_HasSurfaceLegs = true;
182 } else {
183 m_HasUndergroundLegs = true;
184 // The previous point was at a surface->ug transition.
185 if (current_polyline_is_surface) {
186 if (prev_pt.z < m_DepthMin) m_DepthMin = prev_pt.z;
187 if (prev_pt.z > depthmax) depthmax = prev_pt.z;
190 traverses[flags].push_back(traverse(survey->label));
191 current_traverse = &traverses[flags].back();
192 current_traverse->flags = survey->flags;
193 current_traverse->style = survey->style;
195 current_polyline_is_surface = is_surface;
196 current_flags = flags;
197 current_label = survey->label;
198 current_style = survey->style;
200 if (pending_move) {
201 // Update survey extents. We only need to do this if
202 // there's a pending move, since for a surface <->
203 // underground transition, we'll already have handled
204 // this point.
205 if (prev_pt.x < xmin) xmin = prev_pt.x;
206 if (prev_pt.x > xmax) xmax = prev_pt.x;
207 if (prev_pt.y < ymin) ymin = prev_pt.y;
208 if (prev_pt.y > ymax) ymax = prev_pt.y;
209 if (prev_pt.z < zmin) zmin = prev_pt.z;
210 if (prev_pt.z > zmax) zmax = prev_pt.z;
213 current_traverse->push_back(PointInfo(prev_pt));
216 current_traverse->push_back(PointInfo(pt, date));
218 prev_pt = pt;
219 pending_move = false;
220 break;
223 case img_LABEL: {
224 wxString s(survey->label, wxConvUTF8);
225 if (s.empty()) {
226 // If label isn't valid UTF-8 then this conversion will
227 // give an empty string. In this case, assume that the
228 // label is CP1252 (the Microsoft superset of ISO8859-1).
229 static wxCSConv ConvCP1252(wxFONTENCODING_CP1252);
230 s = wxString(survey->label, ConvCP1252);
231 if (s.empty()) {
232 // Or if that doesn't work (ConvCP1252 doesn't like
233 // strings with some bytes in) let's just go for
234 // ISO8859-1.
235 s = wxString(survey->label, wxConvISO8859_1);
238 int flags = img2aven(survey->flags);
239 LabelInfo* label = new LabelInfo(pt, s, flags);
240 if (label->IsEntrance()) {
241 m_NumEntrances++;
243 if (label->IsFixedPt()) {
244 m_NumFixedPts++;
246 if (label->IsExportedPt()) {
247 m_NumExportedPts++;
249 m_Labels.push_back(label);
250 break;
253 case img_XSECT: {
254 if (!current_tube) {
255 // Start new current_tube.
256 tubes.push_back(vector<XSect>());
257 current_tube = &tubes.back();
260 LabelInfo * lab;
261 wxString label(survey->label, wxConvUTF8);
262 map<wxString, LabelInfo *>::const_iterator p;
263 p = labelmap.find(label);
264 if (p != labelmap.end()) {
265 lab = p->second;
266 } else {
267 // Initialise labelmap lazily - we may have no
268 // cross-sections.
269 list<LabelInfo*>::const_iterator i;
270 if (labelmap.empty()) {
271 i = m_Labels.begin();
272 } else {
273 i = last_mapped_label;
274 ++i;
276 while (i != m_Labels.end() && (*i)->GetText() != label) {
277 labelmap[(*i)->GetText()] = *i;
278 ++i;
280 last_mapped_label = i;
281 if (i == m_Labels.end()) {
282 // Unattached cross-section - ignore for now.
283 printf("unattached cross-section\n");
284 if (current_tube->size() <= 1)
285 tubes.resize(tubes.size() - 1);
286 current_tube = NULL;
287 if (!m_Labels.empty())
288 --last_mapped_label;
289 break;
291 lab = *i;
292 labelmap[label] = lab;
295 int date = survey->days1;
296 if (date != -1) {
297 date += (survey->days2 - date) / 2;
298 if (date < m_DateMin) m_DateMin = date;
299 if (date > datemax) datemax = date;
302 current_tube->emplace_back(lab, date, survey->l, survey->r, survey->u, survey->d);
303 break;
306 case img_XSECT_END:
307 // Finish off current_tube.
308 // If there's only one cross-section in the tube, just
309 // discard it for now. FIXME: we should handle this
310 // when we come to skinning the tubes.
311 if (current_tube && current_tube->size() <= 1)
312 tubes.resize(tubes.size() - 1);
313 current_tube = NULL;
314 break;
316 case img_ERROR_INFO: {
317 if (survey->E == 0.0) {
318 // Currently cavern doesn't spot all articulating traverses
319 // so we assume that any traverse with no error isn't part
320 // of a loop. FIXME: fix cavern!
321 break;
323 m_HasErrorInformation = true;
324 for (size_t f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
325 list<traverse>::reverse_iterator t = traverses[f].rbegin();
326 size_t n = n_traverses[f];
327 n_traverses[f] = 0;
328 while (n) {
329 assert(t != traverses[f].rend());
330 t->n_legs = survey->n_legs;
331 t->length = survey->length;
332 t->errors[traverse::ERROR_3D] = survey->E;
333 t->errors[traverse::ERROR_H] = survey->H;
334 t->errors[traverse::ERROR_V] = survey->V;
335 --n;
336 ++t;
339 break;
342 case img_BAD: {
343 m_Labels.clear();
345 // FIXME: Do we need to reset all these? - Olly
346 m_NumFixedPts = 0;
347 m_NumExportedPts = 0;
348 m_NumEntrances = 0;
349 m_HasUndergroundLegs = false;
350 m_HasSplays = false;
351 m_HasSurfaceLegs = false;
353 img_close(survey);
355 return img_error2msg(img_error());
358 default:
359 break;
361 } while (result != img_STOP);
363 if (!current_polyline_is_surface && current_traverse) {
364 //FixLRUD(*current_traverse);
367 // Finish off current_tube.
368 // If there's only one cross-section in the tube, just
369 // discard it for now. FIXME: we should handle this
370 // when we come to skinning the tubes.
371 if (current_tube && current_tube->size() <= 1)
372 tubes.resize(tubes.size() - 1);
374 m_separator = survey->separator;
375 m_Title = wxString(survey->title, wxConvUTF8);
376 m_DateStamp_numeric = survey->datestamp_numeric;
377 if (survey->cs) {
378 m_cs_proj = wxString(survey->cs, wxConvUTF8);
379 } else {
380 m_cs_proj = wxString();
382 if (strcmp(survey->datestamp, "?") == 0) {
383 /* TRANSLATORS: used a processed survey with no processing date/time info */
384 m_DateStamp = wmsg(/*Date and time not available.*/108);
385 } else if (survey->datestamp[0] == '@') {
386 const struct tm * tm = localtime(&m_DateStamp_numeric);
387 char buf[256];
388 /* TRANSLATORS: This is the date format string used to timestamp .3d
389 * files internally. Probably best to keep it the same for all
390 * translations. */
391 strftime(buf, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm);
392 m_DateStamp = wxString(buf, wxConvUTF8);
394 if (m_DateStamp.empty()) {
395 m_DateStamp = wxString(survey->datestamp, wxConvUTF8);
397 img_close(survey);
399 // Check we've actually loaded some legs or stations!
400 if (!m_HasUndergroundLegs && !m_HasSurfaceLegs && m_Labels.empty()) {
401 return (/*No survey data in 3d file “%s”*/202);
404 if (traverses[0].empty() &&
405 traverses[1].empty() &&
406 traverses[2].empty() &&
407 traverses[3].empty() &&
408 traverses[4].empty() &&
409 traverses[5].empty() &&
410 traverses[6].empty() &&
411 traverses[7].empty()) {
412 // No legs, so get survey extents from stations
413 list<LabelInfo*>::const_iterator i;
414 for (i = m_Labels.begin(); i != m_Labels.end(); ++i) {
415 if ((*i)->GetX() < xmin) xmin = (*i)->GetX();
416 if ((*i)->GetX() > xmax) xmax = (*i)->GetX();
417 if ((*i)->GetY() < ymin) ymin = (*i)->GetY();
418 if ((*i)->GetY() > ymax) ymax = (*i)->GetY();
419 if ((*i)->GetZ() < zmin) zmin = (*i)->GetZ();
420 if ((*i)->GetZ() > zmax) zmax = (*i)->GetZ();
424 m_Ext.assign(xmax - xmin, ymax - ymin, zmax - zmin);
426 if (datemax < m_DateMin) m_DateMin = datemax;
427 m_DateExt = datemax - m_DateMin;
429 // Centre the dataset around the origin.
430 CentreDataset(Vector3(xmin, ymin, zmin));
432 if (depthmax < m_DepthMin) {
433 m_DepthMin = 0;
434 m_DepthExt = 0;
435 } else {
436 m_DepthExt = depthmax - m_DepthMin;
437 m_DepthMin -= GetOffset().GetZ();
440 #if 0
441 printf("time to load = %.3f\n", (double)timer.Time());
442 #endif
444 return 0; // OK
447 void Model::CentreDataset(const Vector3& vmin)
449 // Centre the dataset around the origin.
451 m_Offset = vmin + (m_Ext * 0.5);
453 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
454 list<traverse>::iterator t = traverses[f].begin();
455 while (t != traverses[f].end()) {
456 assert(t->size() > 1);
457 vector<PointInfo>::iterator pos = t->begin();
458 while (pos != t->end()) {
459 Point & point = *pos++;
460 point -= m_Offset;
462 ++t;
466 list<LabelInfo*>::iterator lpos = m_Labels.begin();
467 while (lpos != m_Labels.end()) {
468 Point & point = **lpos++;
469 point -= m_Offset;
473 void
474 Model::do_prepare_tubes() const
476 // Fill in "right_bearing" for each cross-section.
477 for (auto&& tube : tubes) {
478 assert(tube.size() > 1);
479 Vector3 U[4];
480 XSect* prev_pt_v = NULL;
481 Vector3 last_right(1.0, 0.0, 0.0);
483 vector<XSect>::iterator i = tube.begin();
484 vector<XSect>::size_type segment = 0;
485 while (i != tube.end()) {
486 // get the coordinates of this vertex
487 XSect & pt_v = *i++;
489 bool cover_end = false;
491 Vector3 right, up;
493 const Vector3 up_v(0.0, 0.0, 1.0);
495 if (segment == 0) {
496 assert(i != tube.end());
497 // first segment
499 // get the coordinates of the next vertex
500 const XSect & next_pt_v = *i;
502 // calculate vector from this pt to the next one
503 Vector3 leg_v = next_pt_v - pt_v;
505 // obtain a vector in the LRUD plane
506 right = leg_v * up_v;
507 if (right.magnitude() == 0) {
508 right = last_right;
509 // Obtain a second vector in the LRUD plane,
510 // perpendicular to the first.
511 //up = right * leg_v;
512 up = up_v;
513 } else {
514 last_right = right;
515 up = up_v;
518 cover_end = true;
519 } else if (segment + 1 == tube.size()) {
520 // last segment
522 // Calculate vector from the previous pt to this one.
523 Vector3 leg_v = pt_v - *prev_pt_v;
525 // Obtain a horizontal vector in the LRUD plane.
526 right = leg_v * up_v;
527 if (right.magnitude() == 0) {
528 right = Vector3(last_right.GetX(), last_right.GetY(), 0.0);
529 // Obtain a second vector in the LRUD plane,
530 // perpendicular to the first.
531 //up = right * leg_v;
532 up = up_v;
533 } else {
534 last_right = right;
535 up = up_v;
538 cover_end = true;
539 } else {
540 assert(i != tube.end());
541 // Intermediate segment.
543 // Get the coordinates of the next vertex.
544 const XSect & next_pt_v = *i;
546 // Calculate vectors from this vertex to the
547 // next vertex, and from the previous vertex to
548 // this one.
549 Vector3 leg1_v = pt_v - *prev_pt_v;
550 Vector3 leg2_v = next_pt_v - pt_v;
552 // Obtain horizontal vectors perpendicular to
553 // both legs, then normalise and average to get
554 // a horizontal bisector.
555 Vector3 r1 = leg1_v * up_v;
556 Vector3 r2 = leg2_v * up_v;
557 r1.normalise();
558 r2.normalise();
559 right = r1 + r2;
560 if (right.magnitude() == 0) {
561 // This is the "mid-pitch" case...
562 right = last_right;
564 if (r1.magnitude() == 0) {
565 up = up_v;
567 // Rotate pitch section to minimise the
568 // "torsional stress" - FIXME: use
569 // triangles instead of rectangles?
570 int shift = 0;
571 double maxdotp = 0;
573 // Scale to unit vectors in the LRUD plane.
574 right.normalise();
575 up.normalise();
576 Vector3 vec = up - right;
577 for (int orient = 0; orient <= 3; ++orient) {
578 Vector3 tmp = U[orient] - prev_pt_v->GetPoint();
579 tmp.normalise();
580 double dotp = dot(vec, tmp);
581 if (dotp > maxdotp) {
582 maxdotp = dotp;
583 shift = orient;
586 if (shift) {
587 if (shift != 2) {
588 Vector3 temp(U[0]);
589 U[0] = U[shift];
590 U[shift] = U[2];
591 U[2] = U[shift ^ 2];
592 U[shift ^ 2] = temp;
593 } else {
594 swap(U[0], U[2]);
595 swap(U[1], U[3]);
598 #if 0
599 // Check that the above code actually permuted
600 // the vertices correctly.
601 shift = 0;
602 maxdotp = 0;
603 for (int j = 0; j <= 3; ++j) {
604 Vector3 tmp = U[j] - *prev_pt_v;
605 tmp.normalise();
606 double dotp = dot(vec, tmp);
607 if (dotp > maxdotp) {
608 maxdotp = dotp + 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
609 shift = j;
612 if (shift) {
613 printf("New shift = %d!\n", shift);
614 shift = 0;
615 maxdotp = 0;
616 for (int j = 0; j <= 3; ++j) {
617 Vector3 tmp = U[j] - *prev_pt_v;
618 tmp.normalise();
619 double dotp = dot(vec, tmp);
620 printf(" %d : %.8f\n", j, dotp);
623 #endif
624 } else {
625 up = up_v;
627 last_right = right;
630 // Scale to unit vectors in the LRUD plane.
631 right.normalise();
632 up.normalise();
634 double l = fabs(pt_v.GetL());
635 double r = fabs(pt_v.GetR());
636 double u = fabs(pt_v.GetU());
637 double d = fabs(pt_v.GetD());
639 // Produce coordinates of the corners of the LRUD "plane".
640 Vector3 v[4];
641 v[0] = pt_v.GetPoint() - right * l + up * u;
642 v[1] = pt_v.GetPoint() + right * r + up * u;
643 v[2] = pt_v.GetPoint() + right * r - up * d;
644 v[3] = pt_v.GetPoint() - right * l - up * d;
646 prev_pt_v = &pt_v;
647 U[0] = v[0];
648 U[1] = v[1];
649 U[2] = v[2];
650 U[3] = v[3];
652 // FIXME: Store rather than recomputing on each draw?
653 (void)cover_end;
655 pt_v.set_right_bearing(deg(atan2(right.GetX(), right.GetY())));
657 ++segment;
662 void
663 SurveyFilter::add(const wxString& name)
665 auto it = filters.lower_bound(name);
666 if (it != filters.end()) {
667 // name is already present in the filter.
668 if (*it == name) {
669 return;
671 // Check if a survey prefixing name is visible.
672 if (name.StartsWith(*it) && name[it->size()] == separator) {
673 redundant_filters.insert(name);
674 return;
677 while (it != filters.begin()) {
678 --it;
679 const wxString& s = *it;
680 if (s.size() <= name.size()) break;
681 if (s.StartsWith(name) && s[name.size()] == separator) {
682 redundant_filters.insert(s);
683 it = filters.erase(it);
686 filters.insert(name);
689 void
690 SurveyFilter::remove(const wxString& name)
692 if (filters.erase(name) == 0) {
693 redundant_filters.erase(name);
694 return;
696 if (redundant_filters.empty()) {
697 return;
699 auto it = redundant_filters.upper_bound(name);
700 while (it != redundant_filters.begin()) {
701 --it;
702 // Check if a survey prefixed by name should be made visible.
703 const wxString& s = *it;
704 if (s.size() <= name.size()) {
705 break;
707 if (!(s.StartsWith(name) && s[name.size()] == separator))
708 break;
709 filters.insert(s);
710 it = redundant_filters.erase(it);
714 void
715 SurveyFilter::SetSeparator(wxChar separator_)
717 if (separator_ == separator) return;
719 separator = separator_;
721 if (filters.empty()) {
722 return;
725 // Move aside all the filters already set and re-add() them so they get
726 // split into redundant_filters appropriately.
727 std::set<wxString, std::greater<wxString>> old_filters;
728 std::set<wxString, std::greater<wxString>> old_redundant_filters;
729 swap(filters, old_filters);
730 swap(redundant_filters, old_redundant_filters);
731 for (auto& s : old_filters) {
732 add(s);
734 for (auto& s : old_redundant_filters) {
735 add(s);
739 bool
740 SurveyFilter::CheckVisible(const wxString& name) const
742 auto it = filters.lower_bound(name);
743 if (it == filters.end()) {
744 // There's no filter <= name so name is excluded.
745 return false;
747 if (*it == name) {
748 // Exact match.
749 return true;
751 // Check if a survey prefixing name is visible.
752 if (name.StartsWith(*it) && name[it->size()] == separator)
753 return true;
754 return false;