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,2019 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"
39 const static int img2aven_tab
[] = {
46 flags
&= (sizeof(img2aven_tab
) / sizeof(img2aven_tab
[0]) - 1);
47 return img2aven_tab
[flags
];
50 int Model::Load(const wxString
& file
, const wxString
& prefix
)
52 // Load the processed survey data.
53 img
* survey
= img_read_stream_survey(wxFopen(file
, wxT("rb")),
58 return img_error2msg(img_error());
61 m_IsExtendedElevation
= survey
->is_extended_elevation
;
63 // Create a list of all the leg vertices, counting them and finding the
64 // extent of the survey at the same time.
69 m_HasUndergroundLegs
= false;
72 m_HasSurfaceLegs
= false;
73 m_HasErrorInformation
= false;
75 // FIXME: discard existing presentation? ask user about saving if we do!
77 // Delete any existing list entries.
80 double xmin
= DBL_MAX
;
81 double xmax
= -DBL_MAX
;
82 double ymin
= DBL_MAX
;
83 double ymax
= -DBL_MAX
;
84 double zmin
= DBL_MAX
;
85 double zmax
= -DBL_MAX
;
88 double depthmax
= -DBL_MAX
;
92 complete_dateinfo
= true;
94 for (unsigned f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
99 // Ultimately we probably want different types (subclasses perhaps?) for
100 // underground and surface data, so we don't need to store LRUD for surface
102 traverse
* current_traverse
= NULL
;
103 vector
<XSect
> * current_tube
= NULL
;
105 map
<wxString
, LabelInfo
*> labelmap
;
106 list
<LabelInfo
*>::const_iterator last_mapped_label
= m_Labels
.begin();
109 img_point prev_pt
= {0,0,0};
110 bool current_polyline_is_surface
= false;
111 int current_flags
= 0;
112 int current_style
= 0;
113 string current_label
;
114 bool pending_move
= false;
115 // When legs within a traverse have different surface/splay/duplicate
116 // flags, we split it into contiguous traverses of each flag combination,
117 // but we need to track these so we can assign the error statistics to all
118 // of them. So we keep counts of how many of each combination we've
119 // generated for the current traverse.
120 size_t n_traverses
[8];
121 memset(n_traverses
, 0, sizeof(n_traverses
));
124 if (++items
% 200 == 0) {
125 long pos
= ftell(survey
->fh
);
126 int progress
= int((double(pos
) / double(file_size
)) * 100.0);
127 // SetProgress(progress);
132 result
= img_read_item(survey
, &pt
);
135 memset(n_traverses
, 0, sizeof(n_traverses
));
141 // Update survey extents.
142 if (pt
.x
< xmin
) xmin
= pt
.x
;
143 if (pt
.x
> xmax
) xmax
= pt
.x
;
144 if (pt
.y
< ymin
) ymin
= pt
.y
;
145 if (pt
.y
> ymax
) ymax
= pt
.y
;
146 if (pt
.z
< zmin
) zmin
= pt
.z
;
147 if (pt
.z
> zmax
) zmax
= pt
.z
;
149 int date
= survey
->days1
;
151 date
+= (survey
->days2
- date
) / 2;
152 if (date
< m_DateMin
) m_DateMin
= date
;
153 if (date
> datemax
) datemax
= date
;
155 complete_dateinfo
= false;
158 int flags
= survey
->flags
&
159 (img_FLAG_SURFACE
|img_FLAG_SPLAY
|img_FLAG_DUPLICATE
);
160 bool is_surface
= (flags
& img_FLAG_SURFACE
);
161 bool is_splay
= (flags
& img_FLAG_SPLAY
);
162 bool is_dupe
= (flags
& img_FLAG_DUPLICATE
);
165 if (pt
.z
< m_DepthMin
) m_DepthMin
= pt
.z
;
166 if (pt
.z
> depthmax
) depthmax
= pt
.z
;
173 current_flags
!= flags
||
174 current_label
!= survey
->label
||
175 current_style
!= survey
->style
) {
176 if (!current_polyline_is_surface
&& current_traverse
) {
177 //FixLRUD(*current_traverse);
180 ++n_traverses
[flags
];
181 // Start new traverse (surface or underground).
183 m_HasSurfaceLegs
= true;
185 m_HasUndergroundLegs
= true;
186 // The previous point was at a surface->ug transition.
187 if (current_polyline_is_surface
) {
188 if (prev_pt
.z
< m_DepthMin
) m_DepthMin
= prev_pt
.z
;
189 if (prev_pt
.z
> depthmax
) depthmax
= prev_pt
.z
;
192 traverses
[flags
].push_back(traverse(survey
->label
));
193 current_traverse
= &traverses
[flags
].back();
194 current_traverse
->flags
= survey
->flags
;
195 current_traverse
->style
= survey
->style
;
197 current_polyline_is_surface
= is_surface
;
198 current_flags
= flags
;
199 current_label
= survey
->label
;
200 current_style
= survey
->style
;
203 // Update survey extents. We only need to do this if
204 // there's a pending move, since for a surface <->
205 // underground transition, we'll already have handled
207 if (prev_pt
.x
< xmin
) xmin
= prev_pt
.x
;
208 if (prev_pt
.x
> xmax
) xmax
= prev_pt
.x
;
209 if (prev_pt
.y
< ymin
) ymin
= prev_pt
.y
;
210 if (prev_pt
.y
> ymax
) ymax
= prev_pt
.y
;
211 if (prev_pt
.z
< zmin
) zmin
= prev_pt
.z
;
212 if (prev_pt
.z
> zmax
) zmax
= prev_pt
.z
;
215 current_traverse
->push_back(PointInfo(prev_pt
));
218 current_traverse
->push_back(PointInfo(pt
, date
));
221 pending_move
= false;
226 wxString
s(survey
->label
, wxConvUTF8
);
228 // If label isn't valid UTF-8 then this conversion will
229 // give an empty string. In this case, assume that the
230 // label is CP1252 (the Microsoft superset of ISO8859-1).
231 static wxCSConv
ConvCP1252(wxFONTENCODING_CP1252
);
232 s
= wxString(survey
->label
, ConvCP1252
);
234 // Or if that doesn't work (ConvCP1252 doesn't like
235 // strings with some bytes in) let's just go for
237 s
= wxString(survey
->label
, wxConvISO8859_1
);
240 int flags
= img2aven(survey
->flags
);
241 LabelInfo
* label
= new LabelInfo(pt
, s
, flags
);
242 if (label
->IsEntrance()) {
245 if (label
->IsFixedPt()) {
248 if (label
->IsExportedPt()) {
251 m_Labels
.push_back(label
);
257 // Start new current_tube.
258 tubes
.push_back(vector
<XSect
>());
259 current_tube
= &tubes
.back();
263 wxString
label(survey
->label
, wxConvUTF8
);
264 map
<wxString
, LabelInfo
*>::const_iterator p
;
265 p
= labelmap
.find(label
);
266 if (p
!= labelmap
.end()) {
269 // Initialise labelmap lazily - we may have no
271 list
<LabelInfo
*>::const_iterator i
;
272 if (labelmap
.empty()) {
273 i
= m_Labels
.begin();
275 i
= last_mapped_label
;
278 while (i
!= m_Labels
.end() && (*i
)->GetText() != label
) {
279 labelmap
[(*i
)->GetText()] = *i
;
282 last_mapped_label
= i
;
283 if (i
== m_Labels
.end()) {
284 // Unattached cross-section - ignore for now.
285 printf("unattached cross-section\n");
286 if (current_tube
->size() <= 1)
287 tubes
.resize(tubes
.size() - 1);
289 if (!m_Labels
.empty())
294 labelmap
[label
] = lab
;
297 int date
= survey
->days1
;
299 date
+= (survey
->days2
- date
) / 2;
300 if (date
< m_DateMin
) m_DateMin
= date
;
301 if (date
> datemax
) datemax
= date
;
304 current_tube
->emplace_back(lab
, date
, survey
->l
, survey
->r
, survey
->u
, survey
->d
);
309 // Finish off current_tube.
310 // If there's only one cross-section in the tube, just
311 // discard it for now. FIXME: we should handle this
312 // when we come to skinning the tubes.
313 if (current_tube
&& current_tube
->size() <= 1)
314 tubes
.resize(tubes
.size() - 1);
318 case img_ERROR_INFO
: {
319 if (survey
->E
== 0.0) {
320 // Currently cavern doesn't spot all articulating traverses
321 // so we assume that any traverse with no error isn't part
322 // of a loop. FIXME: fix cavern!
325 m_HasErrorInformation
= true;
326 for (size_t f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
327 list
<traverse
>::reverse_iterator t
= traverses
[f
].rbegin();
328 size_t n
= n_traverses
[f
];
331 assert(t
!= traverses
[f
].rend());
332 t
->n_legs
= survey
->n_legs
;
333 t
->length
= survey
->length
;
334 t
->errors
[traverse::ERROR_3D
] = survey
->E
;
335 t
->errors
[traverse::ERROR_H
] = survey
->H
;
336 t
->errors
[traverse::ERROR_V
] = survey
->V
;
347 // FIXME: Do we need to reset all these? - Olly
349 m_NumExportedPts
= 0;
351 m_HasUndergroundLegs
= false;
353 m_HasSurfaceLegs
= false;
357 return img_error2msg(img_error());
363 } while (result
!= img_STOP
);
365 if (!current_polyline_is_surface
&& current_traverse
) {
366 //FixLRUD(*current_traverse);
369 // Finish off current_tube.
370 // If there's only one cross-section in the tube, just
371 // discard it for now. FIXME: we should handle this
372 // when we come to skinning the tubes.
373 if (current_tube
&& current_tube
->size() <= 1)
374 tubes
.resize(tubes
.size() - 1);
376 m_separator
= survey
->separator
;
377 m_Title
= wxString(survey
->title
, wxConvUTF8
);
378 m_DateStamp_numeric
= survey
->datestamp_numeric
;
380 m_cs_proj
= wxString(survey
->cs
, wxConvUTF8
);
382 m_cs_proj
= wxString();
384 if (strcmp(survey
->datestamp
, "?") == 0) {
385 /* TRANSLATORS: used a processed survey with no processing date/time info */
386 m_DateStamp
= wmsg(/*Date and time not available.*/108);
387 } else if (survey
->datestamp
[0] == '@') {
388 const struct tm
* tm
= localtime(&m_DateStamp_numeric
);
390 /* TRANSLATORS: This is the date format string used to timestamp .3d
391 * files internally. Probably best to keep it the same for all
393 strftime(buf
, 256, msg(/*%a,%Y.%m.%d %H:%M:%S %Z*/107), tm
);
394 m_DateStamp
= wxString(buf
, wxConvUTF8
);
396 if (m_DateStamp
.empty()) {
397 m_DateStamp
= wxString(survey
->datestamp
, wxConvUTF8
);
401 // Check we've actually loaded some legs or stations!
402 if (!m_HasUndergroundLegs
&& !m_HasSurfaceLegs
&& m_Labels
.empty()) {
403 return (/*No survey data in 3d file “%s”*/202);
406 if (traverses
[0].empty() &&
407 traverses
[1].empty() &&
408 traverses
[2].empty() &&
409 traverses
[3].empty() &&
410 traverses
[4].empty() &&
411 traverses
[5].empty() &&
412 traverses
[6].empty() &&
413 traverses
[7].empty()) {
414 // No legs, so get survey extents from stations
415 list
<LabelInfo
*>::const_iterator i
;
416 for (i
= m_Labels
.begin(); i
!= m_Labels
.end(); ++i
) {
417 if ((*i
)->GetX() < xmin
) xmin
= (*i
)->GetX();
418 if ((*i
)->GetX() > xmax
) xmax
= (*i
)->GetX();
419 if ((*i
)->GetY() < ymin
) ymin
= (*i
)->GetY();
420 if ((*i
)->GetY() > ymax
) ymax
= (*i
)->GetY();
421 if ((*i
)->GetZ() < zmin
) zmin
= (*i
)->GetZ();
422 if ((*i
)->GetZ() > zmax
) zmax
= (*i
)->GetZ();
426 m_Ext
.assign(xmax
- xmin
, ymax
- ymin
, zmax
- zmin
);
428 if (datemax
< m_DateMin
) m_DateMin
= datemax
;
429 m_DateExt
= datemax
- m_DateMin
;
431 // Centre the dataset around the origin.
432 CentreDataset(Vector3(xmin
, ymin
, zmin
));
434 if (depthmax
< m_DepthMin
) {
438 m_DepthExt
= depthmax
- m_DepthMin
;
439 m_DepthMin
-= GetOffset().GetZ();
443 printf("time to load = %.3f\n", (double)timer
.Time());
449 void Model::CentreDataset(const Vector3
& vmin
)
451 // Centre the dataset around the origin.
453 m_Offset
= vmin
+ (m_Ext
* 0.5);
455 for (unsigned f
= 0; f
!= sizeof(traverses
) / sizeof(traverses
[0]); ++f
) {
456 list
<traverse
>::iterator t
= traverses
[f
].begin();
457 while (t
!= traverses
[f
].end()) {
458 assert(t
->size() > 1);
459 vector
<PointInfo
>::iterator pos
= t
->begin();
460 while (pos
!= t
->end()) {
461 Point
& point
= *pos
++;
468 list
<LabelInfo
*>::iterator lpos
= m_Labels
.begin();
469 while (lpos
!= m_Labels
.end()) {
470 Point
& point
= **lpos
++;
476 Model::do_prepare_tubes() const
478 // Fill in "right_bearing" for each cross-section.
479 for (auto&& tube
: tubes
) {
480 assert(tube
.size() > 1);
482 XSect
* prev_pt_v
= NULL
;
483 Vector3
last_right(1.0, 0.0, 0.0);
485 vector
<XSect
>::iterator i
= tube
.begin();
486 vector
<XSect
>::size_type segment
= 0;
487 while (i
!= tube
.end()) {
488 // get the coordinates of this vertex
491 bool cover_end
= false;
495 const Vector3
up_v(0.0, 0.0, 1.0);
498 assert(i
!= tube
.end());
501 // get the coordinates of the next vertex
502 const XSect
& next_pt_v
= *i
;
504 // calculate vector from this pt to the next one
505 Vector3 leg_v
= next_pt_v
- pt_v
;
507 // obtain a vector in the LRUD plane
508 right
= leg_v
* up_v
;
509 if (right
.magnitude() == 0) {
511 // Obtain a second vector in the LRUD plane,
512 // perpendicular to the first.
513 //up = right * leg_v;
521 } else if (segment
+ 1 == tube
.size()) {
524 // Calculate vector from the previous pt to this one.
525 Vector3 leg_v
= pt_v
- *prev_pt_v
;
527 // Obtain a horizontal vector in the LRUD plane.
528 right
= leg_v
* up_v
;
529 if (right
.magnitude() == 0) {
530 right
= Vector3(last_right
.GetX(), last_right
.GetY(), 0.0);
531 // Obtain a second vector in the LRUD plane,
532 // perpendicular to the first.
533 //up = right * leg_v;
542 assert(i
!= tube
.end());
543 // Intermediate segment.
545 // Get the coordinates of the next vertex.
546 const XSect
& next_pt_v
= *i
;
548 // Calculate vectors from this vertex to the
549 // next vertex, and from the previous vertex to
551 Vector3 leg1_v
= pt_v
- *prev_pt_v
;
552 Vector3 leg2_v
= next_pt_v
- pt_v
;
554 // Obtain horizontal vectors perpendicular to
555 // both legs, then normalise and average to get
556 // a horizontal bisector.
557 Vector3 r1
= leg1_v
* up_v
;
558 Vector3 r2
= leg2_v
* up_v
;
562 if (right
.magnitude() == 0) {
563 // This is the "mid-pitch" case...
566 if (r1
.magnitude() == 0) {
569 // Rotate pitch section to minimise the
570 // "torsional stress" - FIXME: use
571 // triangles instead of rectangles?
575 // Scale to unit vectors in the LRUD plane.
578 Vector3 vec
= up
- right
;
579 for (int orient
= 0; orient
<= 3; ++orient
) {
580 Vector3 tmp
= U
[orient
] - prev_pt_v
->GetPoint();
582 double dotp
= dot(vec
, tmp
);
583 if (dotp
> maxdotp
) {
601 // Check that the above code actually permuted
602 // the vertices correctly.
605 for (int j
= 0; j
<= 3; ++j
) {
606 Vector3 tmp
= U
[j
] - *prev_pt_v
;
608 double dotp
= dot(vec
, tmp
);
609 if (dotp
> maxdotp
) {
610 maxdotp
= dotp
+ 1e-6; // Add small tolerance to stop 45 degree offset cases being flagged...
615 printf("New shift = %d!\n", shift
);
618 for (int j
= 0; j
<= 3; ++j
) {
619 Vector3 tmp
= U
[j
] - *prev_pt_v
;
621 double dotp
= dot(vec
, tmp
);
622 printf(" %d : %.8f\n", j
, dotp
);
632 // Scale to unit vectors in the LRUD plane.
636 double l
= fabs(pt_v
.GetL());
637 double r
= fabs(pt_v
.GetR());
638 double u
= fabs(pt_v
.GetU());
639 double d
= fabs(pt_v
.GetD());
641 // Produce coordinates of the corners of the LRUD "plane".
643 v
[0] = pt_v
.GetPoint() - right
* l
+ up
* u
;
644 v
[1] = pt_v
.GetPoint() + right
* r
+ up
* u
;
645 v
[2] = pt_v
.GetPoint() + right
* r
- up
* d
;
646 v
[3] = pt_v
.GetPoint() - right
* l
- up
* d
;
654 // FIXME: Store rather than recomputing on each draw?
657 pt_v
.set_right_bearing(deg(atan2(right
.GetX(), right
.GetY())));
665 SurveyFilter::add(const wxString
& name
)
667 auto it
= filters
.lower_bound(name
);
668 if (it
!= filters
.end()) {
669 // It's invalid to add a survey which is already present.
671 // Check if a survey prefixing name is visible.
672 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
) {
673 redundant_filters
.insert(name
);
677 while (it
!= filters
.begin()) {
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
);
690 SurveyFilter::remove(const wxString
& name
)
692 if (filters
.erase(name
) == 0) {
693 redundant_filters
.erase(name
);
696 if (redundant_filters
.empty()) {
699 auto it
= redundant_filters
.upper_bound(name
);
700 while (it
!= redundant_filters
.begin()) {
702 // Check if a survey prefixed by name should be made visible.
703 const wxString
& s
= *it
;
704 if (s
.size() <= name
.size()) {
707 if (!(s
.StartsWith(name
) && s
[name
.size()] == separator
))
710 it
= redundant_filters
.erase(it
);
715 SurveyFilter::SetSeparator(wxChar separator_
)
717 if (separator_
== separator
) return;
719 separator
= separator_
;
721 if (filters
.empty()) {
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
: filters
) {
734 for (auto& s
: redundant_filters
) {
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.
751 // Check if a survey prefixing name is visible.
752 if (name
.StartsWith(*it
) && name
[it
->size()] == separator
)