6 // Copyright (C) 2000-2002,2005,2006 Mark R. Shinwell
7 // Copyright (C) 2001-2024 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
29 #include "img_hosted.h"
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")),
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.
56 m_HasUndergroundLegs
= 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.
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
;
75 double depthmax
= -DBL_MAX
;
79 complete_dateinfo
= true;
81 for (unsigned f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
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
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();
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
));
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);
119 result
= img_read_item(survey
, &pt
);
122 memset(n_traverses
, 0, sizeof(n_traverses
));
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
;
138 date
+= (survey
->days2
- date
) / 2;
139 if (date
< m_DateMin
) m_DateMin
= date
;
140 if (date
> datemax
) datemax
= date
;
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
);
152 if (pt
.z
< m_DepthMin
) m_DepthMin
= pt
.z
;
153 if (pt
.z
> depthmax
) depthmax
= pt
.z
;
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).
170 m_HasSurfaceLegs
= true;
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
;
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
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
));
208 pending_move
= false;
213 wxString
s(survey
->label
, wxConvUTF8
);
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
);
221 // Or if that doesn't work (ConvCP1252 doesn't like
222 // strings with some bytes in) let's just go for
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()) {
232 if (label
->IsFixedPt()) {
235 if (label
->IsExportedPt()) {
238 m_Labels
.push_back(label
);
244 // Start new current_tube.
245 tubes
.push_back(vector
<XSect
>());
246 current_tube
= &tubes
.back();
250 wxString
label(survey
->label
, wxConvUTF8
);
251 map
<wxString
, LabelInfo
*>::const_iterator p
;
252 p
= labelmap
.find(label
);
253 if (p
!= labelmap
.end()) {
256 // Initialise labelmap lazily - we may have no
258 list
<LabelInfo
*>::const_iterator i
;
259 if (labelmap
.empty()) {
260 i
= m_Labels
.begin();
262 i
= last_mapped_label
;
265 while (i
!= m_Labels
.end() && (*i
)->GetText() != label
) {
266 labelmap
[(*i
)->GetText()] = *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);
276 if (!m_Labels
.empty())
281 labelmap
[label
] = lab
;
284 int date
= survey
->days1
;
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
);
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);
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!
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
];
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
;
334 // FIXME: Do we need to reset all these? - Olly
336 m_NumExportedPts
= 0;
338 m_HasUndergroundLegs
= false;
340 m_HasSurfaceLegs
= false;
344 return img_error2msg(img_error());
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
;
367 m_cs_proj
= wxString(survey
->cs
, wxConvUTF8
);
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
);
377 /* TRANSLATORS: This is the date format string used to timestamp .3d
378 * files internally. Probably best to keep it the same for all
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
);
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
) {
425 m_DepthExt
= depthmax
- m_DepthMin
;
426 m_DepthMin
-= GetOffset().GetZ();
430 printf("time to load = %.3f\n", (double)timer
.Time());
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
++;
455 list
<LabelInfo
*>::iterator lpos
= m_Labels
.begin();
456 while (lpos
!= m_Labels
.end()) {
457 Point
& point
= **lpos
++;
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);
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
478 bool cover_end
= false;
482 const Vector3
up_v(0.0, 0.0, 1.0);
485 assert(i
!= tube
.end());
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) {
498 // Obtain a second vector in the LRUD plane,
499 // perpendicular to the first.
500 //up = right * leg_v;
508 } else if (segment
+ 1 == tube
.size()) {
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;
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
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
;
549 if (right
.magnitude() == 0) {
550 // This is the "mid-pitch" case...
553 if (r1
.magnitude() == 0) {
556 // Rotate pitch section to minimise the
557 // "torsional stress" - FIXME: use
558 // triangles instead of rectangles?
562 // Scale to unit vectors in the LRUD plane.
565 Vector3 vec
= up
- right
;
566 for (int orient
= 0; orient
<= 3; ++orient
) {
567 Vector3 tmp
= U
[orient
] - prev_pt_v
->GetPoint();
569 double dotp
= dot(vec
, tmp
);
570 if (dotp
> maxdotp
) {
588 // Check that the above code actually permuted
589 // the vertices correctly.
592 for (int j
= 0; j
<= 3; ++j
) {
593 Vector3 tmp
= U
[j
] - *prev_pt_v
;
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...
602 printf("New shift = %d!\n", shift
);
605 for (int j
= 0; j
<= 3; ++j
) {
606 Vector3 tmp
= U
[j
] - *prev_pt_v
;
608 double dotp
= dot(vec
, tmp
);
609 printf(" %d : %.8f\n", j
, dotp
);
619 // Scale to unit vectors in the LRUD plane.
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".
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
;
641 // FIXME: Store rather than recomputing on each draw?
644 pt_v
.set_right_bearing(deg(atan2(right
.GetX(), right
.GetY())));
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.
658 // Check if a survey prefixing name is visible.
659 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
) {
660 redundant_filters
.insert(name
);
664 while (it
!= filters
.begin()) {
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
);
677 SurveyFilter::remove(const wxString
& name
)
679 if (filters
.erase(name
) == 0) {
680 redundant_filters
.erase(name
);
683 if (redundant_filters
.empty()) {
686 auto it
= redundant_filters
.upper_bound(name
);
687 while (it
!= redundant_filters
.begin()) {
689 // Check if a survey prefixed by name should be made visible.
690 const wxString
& s
= *it
;
691 if (s
.size() <= name
.size()) {
694 if (!(s
.StartsWith(name
) && s
[name
.size()] == separator
))
697 it
= redundant_filters
.erase(it
);
702 SurveyFilter::SetSeparator(wxChar separator_
)
704 if (separator_
== separator
) return;
706 separator
= separator_
;
708 if (filters
.empty()) {
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
) {
721 for (auto& s
: old_redundant_filters
) {
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.
738 // Check if a survey prefixing name is visible.
739 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
)