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
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
31 #include "img_hosted.h"
38 const static int img2aven_tab
[] = {
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")),
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.
68 m_HasUndergroundLegs
= 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.
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
;
87 double depthmax
= -DBL_MAX
;
91 complete_dateinfo
= true;
93 for (unsigned f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
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
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();
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
));
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);
130 result
= img_read_item(survey
, &pt
);
133 memset(n_traverses
, 0, sizeof(n_traverses
));
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
;
149 date
+= (survey
->days2
- date
) / 2;
150 if (date
< m_DateMin
) m_DateMin
= date
;
151 if (date
> datemax
) datemax
= date
;
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
);
163 if (pt
.z
< m_DepthMin
) m_DepthMin
= pt
.z
;
164 if (pt
.z
> depthmax
) depthmax
= pt
.z
;
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).
180 m_HasSurfaceLegs
= true;
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
;
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
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
));
216 pending_move
= false;
221 wxString
s(survey
->label
, wxConvUTF8
);
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
);
229 // Or if that doesn't work (ConvCP1252 doesn't like
230 // strings with some bytes in) let's just go for
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()) {
240 if (label
->IsFixedPt()) {
243 if (label
->IsExportedPt()) {
246 m_Labels
.push_back(label
);
252 // Start new current_tube.
253 tubes
.push_back(vector
<XSect
>());
254 current_tube
= &tubes
.back();
258 wxString
label(survey
->label
, wxConvUTF8
);
259 map
<wxString
, LabelInfo
*>::const_iterator p
;
260 p
= labelmap
.find(label
);
261 if (p
!= labelmap
.end()) {
264 // Initialise labelmap lazily - we may have no
266 list
<LabelInfo
*>::const_iterator i
;
267 if (labelmap
.empty()) {
268 i
= m_Labels
.begin();
270 i
= last_mapped_label
;
273 while (i
!= m_Labels
.end() && (*i
)->GetText() != label
) {
274 labelmap
[(*i
)->GetText()] = *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);
284 if (!m_Labels
.empty())
289 labelmap
[label
] = lab
;
292 int date
= survey
->days1
;
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
);
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);
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!
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
];
326 assert(t
!= traverses
[f
].rend());
327 t
->n_legs
= survey
->n_legs
;
328 t
->length
= survey
->length
;
342 // FIXME: Do we need to reset all these? - Olly
344 m_NumExportedPts
= 0;
346 m_HasUndergroundLegs
= false;
348 m_HasSurfaceLegs
= false;
352 return img_error2msg(img_error());
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
;
375 m_cs_proj
= wxString(survey
->cs
, wxConvUTF8
);
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
);
385 /* TRANSLATORS: This is the date format string used to timestamp .3d
386 * files internally. Probably best to keep it the same for all
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
);
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
) {
433 m_DepthExt
= depthmax
- m_DepthMin
;
434 m_DepthMin
-= GetOffset().GetZ();
438 printf("time to load = %.3f\n", (double)timer
.Time());
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
++;
463 list
<LabelInfo
*>::iterator lpos
= m_Labels
.begin();
464 while (lpos
!= m_Labels
.end()) {
465 Point
& point
= **lpos
++;
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.
477 // Check if a survey prefixing name is visible.
478 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
) {
479 redundant_filters
.insert(name
);
483 while (it
!= filters
.begin()) {
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
);
496 SurveyFilter::remove(const wxString
& name
)
498 if (filters
.erase(name
) == 0) {
499 redundant_filters
.erase(name
);
502 if (redundant_filters
.empty()) {
505 auto it
= redundant_filters
.upper_bound(name
);
506 while (it
!= redundant_filters
.begin()) {
508 // Check if a survey prefixed by name should be made visible.
509 const wxString
& s
= *it
;
510 if (s
.size() <= name
.size()) {
513 if (!(s
.StartsWith(name
) && s
[name
.size()] == separator
))
516 it
= redundant_filters
.erase(it
);
521 SurveyFilter::SetSeparator(wxChar separator_
)
523 if (separator_
== separator
) return;
525 separator
= separator_
;
527 if (filters
.empty()) {
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
) {
540 for (auto& s
: redundant_filters
) {
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.
557 // Check if a survey prefixing name is visible.
558 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
)