Move Model class to its own file
[survex.git] / src / model.cc
blob5b39d66c7ed6312c15970b0e161d3504d68f2f42
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 bool pending_move = false;
112 // When legs within a traverse have different surface/splay/duplicate
113 // flags, we split it into contiguous traverses of each flag combination,
114 // but we need to track these so we can assign the error statistics to all
115 // of them. So we keep counts of how many of each combination we've
116 // generated for the current traverse.
117 size_t n_traverses[8];
118 memset(n_traverses, 0, sizeof(n_traverses));
119 do {
120 #if 0
121 if (++items % 200 == 0) {
122 long pos = ftell(survey->fh);
123 int progress = int((double(pos) / double(file_size)) * 100.0);
124 // SetProgress(progress);
126 #endif
128 img_point pt;
129 result = img_read_item(survey, &pt);
130 switch (result) {
131 case img_MOVE:
132 memset(n_traverses, 0, sizeof(n_traverses));
133 pending_move = true;
134 prev_pt = pt;
135 break;
137 case img_LINE: {
138 // Update survey extents.
139 if (pt.x < xmin) xmin = pt.x;
140 if (pt.x > xmax) xmax = pt.x;
141 if (pt.y < ymin) ymin = pt.y;
142 if (pt.y > ymax) ymax = pt.y;
143 if (pt.z < zmin) zmin = pt.z;
144 if (pt.z > zmax) zmax = pt.z;
146 int date = survey->days1;
147 if (date != -1) {
148 date += (survey->days2 - date) / 2;
149 if (date < m_DateMin) m_DateMin = date;
150 if (date > datemax) datemax = date;
151 } else {
152 complete_dateinfo = false;
155 int flags = survey->flags &
156 (img_FLAG_SURFACE|img_FLAG_SPLAY|img_FLAG_DUPLICATE);
157 bool is_surface = (flags & img_FLAG_SURFACE);
158 bool is_splay = (flags & img_FLAG_SPLAY);
159 bool is_dupe = (flags & img_FLAG_DUPLICATE);
161 if (!is_surface) {
162 if (pt.z < m_DepthMin) m_DepthMin = pt.z;
163 if (pt.z > depthmax) depthmax = pt.z;
165 if (is_splay)
166 m_HasSplays = true;
167 if (is_dupe)
168 m_HasDupes = true;
169 if (pending_move ||
170 current_flags != flags) {
171 if (!current_polyline_is_surface && current_traverse) {
172 //FixLRUD(*current_traverse);
175 ++n_traverses[flags];
176 // Start new traverse (surface or underground).
177 if (is_surface) {
178 m_HasSurfaceLegs = true;
179 } else {
180 m_HasUndergroundLegs = true;
181 // The previous point was at a surface->ug transition.
182 if (current_polyline_is_surface) {
183 if (prev_pt.z < m_DepthMin) m_DepthMin = prev_pt.z;
184 if (prev_pt.z > depthmax) depthmax = prev_pt.z;
187 traverses[flags].push_back(traverse());
188 current_traverse = &traverses[flags].back();
189 current_traverse->flags = survey->flags;
191 current_polyline_is_surface = is_surface;
192 current_flags = flags;
194 if (pending_move) {
195 // Update survey extents. We only need to do this if
196 // there's a pending move, since for a surface <->
197 // underground transition, we'll already have handled
198 // this point.
199 if (prev_pt.x < xmin) xmin = prev_pt.x;
200 if (prev_pt.x > xmax) xmax = prev_pt.x;
201 if (prev_pt.y < ymin) ymin = prev_pt.y;
202 if (prev_pt.y > ymax) ymax = prev_pt.y;
203 if (prev_pt.z < zmin) zmin = prev_pt.z;
204 if (prev_pt.z > zmax) zmax = prev_pt.z;
207 current_traverse->push_back(PointInfo(prev_pt));
210 current_traverse->push_back(PointInfo(pt, date));
212 prev_pt = pt;
213 pending_move = false;
214 break;
217 case img_LABEL: {
218 wxString s(survey->label, wxConvUTF8);
219 if (s.empty()) {
220 // If label isn't valid UTF-8 then this conversion will
221 // give an empty string. In this case, assume that the
222 // label is CP1252 (the Microsoft superset of ISO8859-1).
223 static wxCSConv ConvCP1252(wxFONTENCODING_CP1252);
224 s = wxString(survey->label, ConvCP1252);
225 if (s.empty()) {
226 // Or if that doesn't work (ConvCP1252 doesn't like
227 // strings with some bytes in) let's just go for
228 // ISO8859-1.
229 s = wxString(survey->label, wxConvISO8859_1);
232 int flags = img2aven(survey->flags);
233 LabelInfo* label = new LabelInfo(pt, s, flags);
234 if (label->IsEntrance()) {
235 m_NumEntrances++;
237 if (label->IsFixedPt()) {
238 m_NumFixedPts++;
240 if (label->IsExportedPt()) {
241 m_NumExportedPts++;
243 m_Labels.push_back(label);
244 break;
247 case img_XSECT: {
248 if (!current_tube) {
249 // Start new current_tube.
250 tubes.push_back(vector<XSect>());
251 current_tube = &tubes.back();
254 LabelInfo * lab;
255 wxString label(survey->label, wxConvUTF8);
256 map<wxString, LabelInfo *>::const_iterator p;
257 p = labelmap.find(label);
258 if (p != labelmap.end()) {
259 lab = p->second;
260 } else {
261 // Initialise labelmap lazily - we may have no
262 // cross-sections.
263 list<LabelInfo*>::const_iterator i;
264 if (labelmap.empty()) {
265 i = m_Labels.begin();
266 } else {
267 i = last_mapped_label;
268 ++i;
270 while (i != m_Labels.end() && (*i)->GetText() != label) {
271 labelmap[(*i)->GetText()] = *i;
272 ++i;
274 last_mapped_label = i;
275 if (i == m_Labels.end()) {
276 // Unattached cross-section - ignore for now.
277 printf("unattached cross-section\n");
278 if (current_tube->size() <= 1)
279 tubes.resize(tubes.size() - 1);
280 current_tube = NULL;
281 if (!m_Labels.empty())
282 --last_mapped_label;
283 break;
285 lab = *i;
286 labelmap[label] = lab;
289 int date = survey->days1;
290 if (date != -1) {
291 date += (survey->days2 - date) / 2;
292 if (date < m_DateMin) m_DateMin = date;
293 if (date > datemax) datemax = date;
296 current_tube->push_back(XSect(*lab, date, survey->l, survey->r, survey->u, survey->d));
297 break;
300 case img_XSECT_END:
301 // Finish off current_tube.
302 // If there's only one cross-section in the tube, just
303 // discard it for now. FIXME: we should handle this
304 // when we come to skinning the tubes.
305 if (current_tube && current_tube->size() <= 1)
306 tubes.resize(tubes.size() - 1);
307 current_tube = NULL;
308 break;
310 case img_ERROR_INFO: {
311 if (survey->E == 0.0) {
312 // Currently cavern doesn't spot all articulating traverses
313 // so we assume that any traverse with no error isn't part
314 // of a loop. FIXME: fix cavern!
315 break;
317 m_HasErrorInformation = true;
318 for (size_t f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
319 list<traverse>::reverse_iterator t = traverses[f].rbegin();
320 size_t n = n_traverses[f];
321 n_traverses[f] = 0;
322 while (n) {
323 assert(t != traverses[f].rend());
324 t->n_legs = survey->n_legs;
325 t->length = survey->length;
326 t->E = survey->E;
327 t->H = survey->H;
328 t->V = survey->V;
329 --n;
330 ++t;
333 break;
336 case img_BAD: {
337 m_Labels.clear();
339 // FIXME: Do we need to reset all these? - Olly
340 m_NumFixedPts = 0;
341 m_NumExportedPts = 0;
342 m_NumEntrances = 0;
343 m_HasUndergroundLegs = false;
344 m_HasSplays = false;
345 m_HasSurfaceLegs = false;
347 img_close(survey);
349 return img_error2msg(img_error());
352 default:
353 break;
355 } while (result != img_STOP);
357 if (!current_polyline_is_surface && current_traverse) {
358 //FixLRUD(*current_traverse);
361 // Finish off current_tube.
362 // If there's only one cross-section in the tube, just
363 // discard it for now. FIXME: we should handle this
364 // when we come to skinning the tubes.
365 if (current_tube && current_tube->size() <= 1)
366 tubes.resize(tubes.size() - 1);
368 m_separator = survey->separator;
369 m_Title = wxString(survey->title, wxConvUTF8);
370 m_DateStamp_numeric = survey->datestamp_numeric;
371 if (survey->cs) {
372 m_cs_proj = wxString(survey->cs, wxConvUTF8);
373 } else {
374 m_cs_proj = wxString();
376 if (strcmp(survey->datestamp, "?") == 0) {
377 /* TRANSLATORS: used a processed survey with no processing date/time info */
378 m_DateStamp = wmsg(/*Date and time not available.*/108);
379 } else if (survey->datestamp[0] == '@') {
380 const struct tm * tm = localtime(&m_DateStamp_numeric);
381 char buf[256];
382 /* TRANSLATORS: This is the date format string used to timestamp .3d
383 * files internally. Probably best to keep it the same for all
384 * translations. */
385 strftime(buf, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm);
386 m_DateStamp = wxString(buf, wxConvUTF8);
388 if (m_DateStamp.empty()) {
389 m_DateStamp = wxString(survey->datestamp, wxConvUTF8);
391 img_close(survey);
393 // Check we've actually loaded some legs or stations!
394 if (!m_HasUndergroundLegs && !m_HasSurfaceLegs && m_Labels.empty()) {
395 return (/*No survey data in 3d file ā€œ%sā€*/202);
398 if (traverses[0].empty() &&
399 traverses[1].empty() &&
400 traverses[2].empty() &&
401 traverses[3].empty() &&
402 traverses[4].empty() &&
403 traverses[5].empty() &&
404 traverses[6].empty() &&
405 traverses[7].empty()) {
406 // No legs, so get survey extents from stations
407 list<LabelInfo*>::const_iterator i;
408 for (i = m_Labels.begin(); i != m_Labels.end(); ++i) {
409 if ((*i)->GetX() < xmin) xmin = (*i)->GetX();
410 if ((*i)->GetX() > xmax) xmax = (*i)->GetX();
411 if ((*i)->GetY() < ymin) ymin = (*i)->GetY();
412 if ((*i)->GetY() > ymax) ymax = (*i)->GetY();
413 if ((*i)->GetZ() < zmin) zmin = (*i)->GetZ();
414 if ((*i)->GetZ() > zmax) zmax = (*i)->GetZ();
418 m_Ext.assign(xmax - xmin, ymax - ymin, zmax - zmin);
420 if (datemax < m_DateMin) m_DateMin = datemax;
421 m_DateExt = datemax - m_DateMin;
423 // Centre the dataset around the origin.
424 CentreDataset(Vector3(xmin, ymin, zmin));
426 if (depthmax < m_DepthMin) {
427 m_DepthMin = 0;
428 m_DepthExt = 0;
429 } else {
430 m_DepthExt = depthmax - m_DepthMin;
431 m_DepthMin -= GetOffset().GetZ();
434 #if 0
435 printf("time to load = %.3f\n", (double)timer.Time());
436 #endif
438 return 0; // OK
441 void Model::CentreDataset(const Vector3& vmin)
443 // Centre the dataset around the origin.
445 m_Offset = vmin + (m_Ext * 0.5);
447 for (unsigned f = 0; f != sizeof(traverses) / sizeof(traverses[0]); ++f) {
448 list<traverse>::iterator t = traverses[f].begin();
449 while (t != traverses[f].end()) {
450 assert(t->size() > 1);
451 vector<PointInfo>::iterator pos = t->begin();
452 while (pos != t->end()) {
453 Point & point = *pos++;
454 point -= m_Offset;
456 ++t;
460 list<vector<XSect> >::iterator i = tubes.begin();
461 while (i != tubes.end()) {
462 assert(i->size() > 1);
463 vector<XSect>::iterator pos = i->begin();
464 while (pos != i->end()) {
465 Point & point = *pos++;
466 point -= m_Offset;
468 ++i;
471 list<LabelInfo*>::iterator lpos = m_Labels.begin();
472 while (lpos != m_Labels.end()) {
473 Point & point = **lpos++;
474 point -= m_Offset;