Fix coding style
[survex.git] / src / model.cc
bloba40bde8230812f517650ab147f3d284685d9cff1
1 //
2 // model.cc
3 //
4 // Cave survey model.
5 //
6 // Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7 // Copyright (C) 2001-2003,2004,2005,2006,2010,2011,2012,2013,2014,2015,2016,2018 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 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
29 #include "model.h"
31 #include "img_hosted.h"
33 #include <cfloat>
34 #include <map>
36 using namespace std;
38 const static int img2aven_tab[] = {
39 #include "img2aven.h"
42 inline int
43 img2aven(int flags)
45 flags &= (sizeof(img2aven_tab) / sizeof(img2aven_tab[0]) - 1);
46 return img2aven_tab[flags];
49 int Model::Load(const wxString& file, const wxString& prefix)
51 // Load the processed survey data.
52 img* survey = img_read_stream_survey(wxFopen(file, wxT("rb")),
53 fclose,
54 file.c_str(),
55 prefix.utf8_str());
56 if (!survey) {
57 return img_error2msg(img_error());
60 m_IsExtendedElevation = survey->is_extended_elevation;
62 // Create a list of all the leg vertices, counting them and finding the
63 // extent of the survey at the same time.
65 m_NumFixedPts = 0;
66 m_NumExportedPts = 0;
67 m_NumEntrances = 0;
68 m_HasUndergroundLegs = false;
69 m_HasSplays = false;
70 m_HasDupes = false;
71 m_HasSurfaceLegs = false;
72 m_HasErrorInformation = false;
74 // FIXME: discard existing presentation? ask user about saving if we do!
76 // Delete any existing list entries.
77 m_Labels.clear();
79 double xmin = DBL_MAX;
80 double xmax = -DBL_MAX;
81 double ymin = DBL_MAX;
82 double ymax = -DBL_MAX;
83 double zmin = DBL_MAX;
84 double zmax = -DBL_MAX;
86 m_DepthMin = DBL_MAX;
87 double depthmax = -DBL_MAX;
89 m_DateMin = INT_MAX;
90 int datemax = -1;
91 complete_dateinfo = true;
93 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
94 traverses[f].clear();
96 tubes.clear();
98 // Ultimately we probably want different types (subclasses perhaps?) for
99 // underground and surface data, so we don't need to store LRUD for surface
100 // stuff.
101 traverse * current_traverse = NULL;
102 vector<XSect> * current_tube = NULL;
104 map<wxString, LabelInfo *> labelmap;
105 list<LabelInfo*>::const_iterator last_mapped_label = m_Labels.begin();
107 int result;
108 img_point prev_pt = {0,0,0};
109 bool current_polyline_is_surface = false;
110 int current_flags = 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 if (!current_polyline_is_surface && current_traverse) {
174 //FixLRUD(*current_traverse);
177 ++n_traverses[flags];
178 // Start new traverse (surface or underground).
179 if (is_surface) {
180 m_HasSurfaceLegs = true;
181 } else {
182 m_HasUndergroundLegs = true;
183 // The previous point was at a surface->ug transition.
184 if (current_polyline_is_surface) {
185 if (prev_pt.z < m_DepthMin) m_DepthMin = prev_pt.z;
186 if (prev_pt.z > depthmax) depthmax = prev_pt.z;
189 traverses[flags].push_back(traverse(survey->label));
190 current_traverse = &traverses[flags].back();
191 current_traverse->flags = survey->flags;
193 current_polyline_is_surface = is_surface;
194 current_flags = flags;
195 current_label = survey->label;
197 if (pending_move) {
198 // Update survey extents. We only need to do this if
199 // there's a pending move, since for a surface <->
200 // underground transition, we'll already have handled
201 // this point.
202 if (prev_pt.x < xmin) xmin = prev_pt.x;
203 if (prev_pt.x > xmax) xmax = prev_pt.x;
204 if (prev_pt.y < ymin) ymin = prev_pt.y;
205 if (prev_pt.y > ymax) ymax = prev_pt.y;
206 if (prev_pt.z < zmin) zmin = prev_pt.z;
207 if (prev_pt.z > zmax) zmax = prev_pt.z;
210 current_traverse->push_back(PointInfo(prev_pt));
213 current_traverse->push_back(PointInfo(pt, date));
215 prev_pt = pt;
216 pending_move = false;
217 break;
220 case img_LABEL: {
221 wxString s(survey->label, wxConvUTF8);
222 if (s.empty()) {
223 // If label isn't valid UTF-8 then this conversion will
224 // give an empty string. In this case, assume that the
225 // label is CP1252 (the Microsoft superset of ISO8859-1).
226 static wxCSConv ConvCP1252(wxFONTENCODING_CP1252);
227 s = wxString(survey->label, ConvCP1252);
228 if (s.empty()) {
229 // Or if that doesn't work (ConvCP1252 doesn't like
230 // strings with some bytes in) let's just go for
231 // ISO8859-1.
232 s = wxString(survey->label, wxConvISO8859_1);
235 int flags = img2aven(survey->flags);
236 LabelInfo* label = new LabelInfo(pt, s, flags);
237 if (label->IsEntrance()) {
238 m_NumEntrances++;
240 if (label->IsFixedPt()) {
241 m_NumFixedPts++;
243 if (label->IsExportedPt()) {
244 m_NumExportedPts++;
246 m_Labels.push_back(label);
247 break;
250 case img_XSECT: {
251 if (!current_tube) {
252 // Start new current_tube.
253 tubes.push_back(vector<XSect>());
254 current_tube = &tubes.back();
257 LabelInfo * lab;
258 wxString label(survey->label, wxConvUTF8);
259 map<wxString, LabelInfo *>::const_iterator p;
260 p = labelmap.find(label);
261 if (p != labelmap.end()) {
262 lab = p->second;
263 } else {
264 // Initialise labelmap lazily - we may have no
265 // cross-sections.
266 list<LabelInfo*>::const_iterator i;
267 if (labelmap.empty()) {
268 i = m_Labels.begin();
269 } else {
270 i = last_mapped_label;
271 ++i;
273 while (i != m_Labels.end() && (*i)->GetText() != label) {
274 labelmap[(*i)->GetText()] = *i;
275 ++i;
277 last_mapped_label = i;
278 if (i == m_Labels.end()) {
279 // Unattached cross-section - ignore for now.
280 printf("unattached cross-section\n");
281 if (current_tube->size() <= 1)
282 tubes.resize(tubes.size() - 1);
283 current_tube = NULL;
284 if (!m_Labels.empty())
285 --last_mapped_label;
286 break;
288 lab = *i;
289 labelmap[label] = lab;
292 int date = survey->days1;
293 if (date != -1) {
294 date += (survey->days2 - date) / 2;
295 if (date < m_DateMin) m_DateMin = date;
296 if (date > datemax) datemax = date;
299 current_tube->emplace_back(lab, date, survey->l, survey->r, survey->u, survey->d);
300 break;
303 case img_XSECT_END:
304 // Finish off current_tube.
305 // If there's only one cross-section in the tube, just
306 // discard it for now. FIXME: we should handle this
307 // when we come to skinning the tubes.
308 if (current_tube && current_tube->size() <= 1)
309 tubes.resize(tubes.size() - 1);
310 current_tube = NULL;
311 break;
313 case img_ERROR_INFO: {
314 if (survey->E == 0.0) {
315 // Currently cavern doesn't spot all articulating traverses
316 // so we assume that any traverse with no error isn't part
317 // of a loop. FIXME: fix cavern!
318 break;
320 m_HasErrorInformation = true;
321 for (size_t f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
322 list<traverse>::reverse_iterator t = traverses[f].rbegin();
323 size_t n = n_traverses[f];
324 n_traverses[f] = 0;
325 while (n) {
326 assert(t != traverses[f].rend());
327 t->n_legs = survey->n_legs;
328 t->length = survey->length;
329 t->E = survey->E;
330 t->H = survey->H;
331 t->V = survey->V;
332 --n;
333 ++t;
336 break;
339 case img_BAD: {
340 m_Labels.clear();
342 // FIXME: Do we need to reset all these? - Olly
343 m_NumFixedPts = 0;
344 m_NumExportedPts = 0;
345 m_NumEntrances = 0;
346 m_HasUndergroundLegs = false;
347 m_HasSplays = false;
348 m_HasSurfaceLegs = false;
350 img_close(survey);
352 return img_error2msg(img_error());
355 default:
356 break;
358 } while (result != img_STOP);
360 if (!current_polyline_is_surface && current_traverse) {
361 //FixLRUD(*current_traverse);
364 // Finish off current_tube.
365 // If there's only one cross-section in the tube, just
366 // discard it for now. FIXME: we should handle this
367 // when we come to skinning the tubes.
368 if (current_tube && current_tube->size() <= 1)
369 tubes.resize(tubes.size() - 1);
371 m_separator = survey->separator;
372 m_Title = wxString(survey->title, wxConvUTF8);
373 m_DateStamp_numeric = survey->datestamp_numeric;
374 if (survey->cs) {
375 m_cs_proj = wxString(survey->cs, wxConvUTF8);
376 } else {
377 m_cs_proj = wxString();
379 if (strcmp(survey->datestamp, "?") == 0) {
380 /* TRANSLATORS: used a processed survey with no processing date/time info */
381 m_DateStamp = wmsg(/*Date and time not available.*/108);
382 } else if (survey->datestamp[0] == '@') {
383 const struct tm * tm = localtime(&m_DateStamp_numeric);
384 char buf[256];
385 /* TRANSLATORS: This is the date format string used to timestamp .3d
386 * files internally. Probably best to keep it the same for all
387 * translations. */
388 strftime(buf, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm);
389 m_DateStamp = wxString(buf, wxConvUTF8);
391 if (m_DateStamp.empty()) {
392 m_DateStamp = wxString(survey->datestamp, wxConvUTF8);
394 img_close(survey);
396 // Check we've actually loaded some legs or stations!
397 if (!m_HasUndergroundLegs && !m_HasSurfaceLegs && m_Labels.empty()) {
398 return (/*No survey data in 3d file ā€œ%sā€*/202);
401 if (traverses[0].empty() &&
402 traverses[1].empty() &&
403 traverses[2].empty() &&
404 traverses[3].empty() &&
405 traverses[4].empty() &&
406 traverses[5].empty() &&
407 traverses[6].empty() &&
408 traverses[7].empty()) {
409 // No legs, so get survey extents from stations
410 list<LabelInfo*>::const_iterator i;
411 for (i = m_Labels.begin(); i != m_Labels.end(); ++i) {
412 if ((*i)->GetX() < xmin) xmin = (*i)->GetX();
413 if ((*i)->GetX() > xmax) xmax = (*i)->GetX();
414 if ((*i)->GetY() < ymin) ymin = (*i)->GetY();
415 if ((*i)->GetY() > ymax) ymax = (*i)->GetY();
416 if ((*i)->GetZ() < zmin) zmin = (*i)->GetZ();
417 if ((*i)->GetZ() > zmax) zmax = (*i)->GetZ();
421 m_Ext.assign(xmax - xmin, ymax - ymin, zmax - zmin);
423 if (datemax < m_DateMin) m_DateMin = datemax;
424 m_DateExt = datemax - m_DateMin;
426 // Centre the dataset around the origin.
427 CentreDataset(Vector3(xmin, ymin, zmin));
429 if (depthmax < m_DepthMin) {
430 m_DepthMin = 0;
431 m_DepthExt = 0;
432 } else {
433 m_DepthExt = depthmax - m_DepthMin;
434 m_DepthMin -= GetOffset().GetZ();
437 #if 0
438 printf("time to load = %.3f\n", (double)timer.Time());
439 #endif
441 return 0; // OK
444 void Model::CentreDataset(const Vector3& vmin)
446 // Centre the dataset around the origin.
448 m_Offset = vmin + (m_Ext * 0.5);
450 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
451 list<traverse>::iterator t = traverses[f].begin();
452 while (t != traverses[f].end()) {
453 assert(t->size() > 1);
454 vector<PointInfo>::iterator pos = t->begin();
455 while (pos != t->end()) {
456 Point & point = *pos++;
457 point -= m_Offset;
459 ++t;
463 list<LabelInfo*>::iterator lpos = m_Labels.begin();
464 while (lpos != m_Labels.end()) {
465 Point & point = **lpos++;
466 point -= m_Offset;
470 void
471 SurveyFilter::add(const wxString& name)
473 auto it = filters.lower_bound(name);
474 if (it != filters.end()) {
475 // It's invalid to add a survey which is already present.
476 assert(*it != name);
477 // Check if a survey prefixing name is visible.
478 if (name.StartsWith(*it) && name[it->size()] == separator) {
479 redundant_filters.insert(name);
480 return;
483 while (it != filters.begin()) {
484 --it;
485 const wxString& s = *it;
486 if (s.size() <= name.size()) break;
487 if (s.StartsWith(name) && s[name.size()] == separator) {
488 redundant_filters.insert(s);
489 it = filters.erase(it);
492 filters.insert(name);
495 void
496 SurveyFilter::remove(const wxString& name)
498 if (filters.erase(name) == 0) {
499 redundant_filters.erase(name);
500 return;
502 if (redundant_filters.empty()) {
503 return;
505 auto it = redundant_filters.upper_bound(name);
506 while (it != redundant_filters.begin()) {
507 --it;
508 // Check if a survey prefixed by name should be made visible.
509 const wxString& s = *it;
510 if (s.size() <= name.size()) {
511 break;
513 if (!(s.StartsWith(name) && s[name.size()] == separator))
514 break;
515 filters.insert(s);
516 it = redundant_filters.erase(it);
520 void
521 SurveyFilter::SetSeparator(wxChar separator_)
523 if (separator_ == separator) return;
525 separator = separator_;
527 if (filters.empty()) {
528 return;
531 // Move aside all the filters already set and re-add() them so they get
532 // split into redundant_filters appropriately.
533 std::set<wxString, std::greater<wxString>> old_filters;
534 std::set<wxString, std::greater<wxString>> old_redundant_filters;
535 swap(filters, old_filters);
536 swap(redundant_filters, old_redundant_filters);
537 for (auto& s : filters) {
538 add(s);
540 for (auto& s : redundant_filters) {
541 add(s);
545 bool
546 SurveyFilter::CheckVisible(const wxString& name) const
548 auto it = filters.lower_bound(name);
549 if (it == filters.end()) {
550 // There's no filter <= name so name is excluded.
551 return false;
553 if (*it == name) {
554 // Exact match.
555 return true;
557 // Check if a survey prefixing name is visible.
558 if (name.StartsWith(*it) && name[it->size()] == separator)
559 return true;
560 return false;